mattermost/server/channels/app/file.go

1824 lines
60 KiB
Go
Raw Permalink Normal View History

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package app
import (
"archive/zip"
"bytes"
[MM-31094] Replication Lag (#16888) * MM-31094: Adds tooling to develop and test using a MySQL instance with replication lag. Adds some lazy lookups to fallback to master if results are not found. * MM-31094: Removes mysql-read-replica from default docker services. * MM-31094: Switches (store..SessionStore).Get and (store.TeamStore).GetMember to using context.Context. * MM-31094: Updates (store.UsersStore).Get to use context. * MM-31094: Updates (store.PostStore).Get to use context. * MM-31094: Removes feature flag and config setting. * MM-31094: Rolls back some master reads. * MM-31094: Rolls a non-cache read. * MM-31094: Removes feature flag from the store. * MM-31094: Removes unused constant and struct field. * MM-31094: Removes some old feature flag references. * MM-31094: Fixes some tests. * MM-31094: App layers fix. * MM-31094: Fixes mocks. * MM-31094: Don't reparse flag. * MM-31094: No reparse. * MM-31094: Removed unused FeatureFlags field. * MM-31094: Removes unnecessary feature flags variable declarations. * MM-31094: Fixes copy-paste error. * MM-31094: Fixes logical error. * MM-30194: Removes test method from store. * Revert "MM-30194: Removes test method from store." This reverts commit d5a6e8529bd5f4d993824c828e239d009b05e567. * MM-31094: Conforming to make's strange syntax. * MM-31094: Configures helper for read replica with option. * MM-31094: Adds some missing ctx's. * MM-31094: WIP * MM-31094: Updates test names. * MM-31094: WIP * MM-31094: Removes unnecessary master reads. * MM-31094: ID case changes out of scope. * MM-31094: Removes unused context. * MM-31094: Switches to a helper. Removes some var naming changes. Fixes a merge error. * MM-31094: Removes SQLITE db driver ref. * MM-31094: Layer generate fix. * MM-31094: Removes unnecessary changes. * MM-31094: Moves test method. * MM-31094: Re-add previous fix. * MM-31094: Removes make command for dev. * MM-31094: Fix for login. Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
2021-03-12 12:37:30 -05:00
"context"
"crypto/sha256"
"encoding/base64"
"fmt"
"image"
"io"
"math"
"net/http"
"net/url"
"path/filepath"
"regexp"
"strconv"
"strings"
"sync"
feat: pluginapi: filewillbedownloaded / sendtoastmessage (#34596) * feat: filewillbedonwloaded hook * feat: error popup * chore: make generated pluginapi * tests * feat: different errors for different download types * feat: allow toast positions * fix: avoid using deprecated i18n function * feat: add plugin API to show toasts * feat: downloadType parameter * tests: updated tests * chore: make check-style * chore: i18n * chore: missing fields in tests * chore: sorted i18n for webapp * chore: run mmjstool * test: fixed webapp tests with new changes * test: missing mocks * fix: ensure one-file attachments (previews) are handler properly as thumbnails * chore: lint * test: added new logic to tests * chore: lint * Add SendToastMessage API and FileWillBeDownloaded hook - Introduced SendToastMessage method for sending toast notifications to users with customizable options. - Added FileWillBeDownloaded hook to handle file download requests, allowing plugins to control access to files. - Updated related types and constants for file download handling. - Enhanced PluginSettings to include HookTimeoutSeconds for better timeout management. * Update webapp/channels/src/components/single_image_view/single_image_view.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * chore: copilot reviews * test: head requests * chore: linted the webapp * tests: fixed path * test: fixed mocked args * allow sending message to a connection directly * fix: hook thread safety * chore: formatting * chore: remove configuration from system console * chore: release version * chore: update signature * chore: update release version * chore: addressed comments * fix: update file rejection handling to use 403 Forbidden status and include rejection reason header * Fix nil pointer panic in runFileWillBeDownloadedHook The atomic.Value in runFileWillBeDownloadedHook can be nil if no plugins implement the FileWillBeDownloaded hook. This causes a panic when trying to assert the nil interface to string. This fix adds a nil check before the type assertion, defaulting to an empty string (which allows the download) when no hooks have run. Fixes: - TestUploadDataMultipart/success panic - TestUploadDataMultipart/resume_success panic * test: move the logout test last * chore: restored accidential deletion * chore: lint * chore: make generated * refactor: move websocket events to new package * chore: go vet * chore: missing mock * chore: revert incorrect fmt * chore: import ordering * chore: npm i18n-extract * chore: update constants.tsx from master * chore: make i18n-extract * revert: conflict merge * fix: add missing isFileRejected prop to SingleImageView tests * fix: mock fetch in SingleImageView tests for async thumbnail check The component now performs an async fetch to check thumbnail availability before rendering. Tests need to mock fetch and use waitFor to handle the async state updates. * refactor: move hook logic to app layer * chore: update version to 11.5 * Scope file download rejection toast to the requesting connection Thread the Connection-Id header through RunFileWillBeDownloadedHook and sendFileDownloadRejectedEvent so the WebSocket event is sent only to the connection that initiated the download, instead of all connections for the user. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-16 11:10:39 -05:00
"sync/atomic"
"time"
"maps"
"slices"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/plugin"
"github.com/mattermost/mattermost/server/public/shared/mlog"
"github.com/mattermost/mattermost/server/public/shared/request"
"github.com/mattermost/mattermost/server/v8/channels/app/imaging"
"github.com/mattermost/mattermost/server/v8/channels/store"
"github.com/mattermost/mattermost/server/v8/channels/utils"
"github.com/mattermost/mattermost/server/v8/platform/services/docextractor"
"github.com/mattermost/mattermost/server/v8/platform/shared/filestore"
"github.com/pkg/errors"
)
const (
imageThumbnailWidth = 120
imageThumbnailHeight = 100
imagePreviewWidth = 1920
miniPreviewImageWidth = 16
miniPreviewImageHeight = 16
jpegEncQuality = 90
maxUploadInitialBufferSize = 1024 * 1024 // 1MB
maxContentExtractionSize = 1024 * 1024 // 1MB
)
func (a *App) FileBackend() filestore.FileBackend {
return a.ch.filestore
}
func (a *App) ExportFileBackend() filestore.FileBackend {
return a.ch.exportFilestore
}
func (a *App) CheckMandatoryS3Fields(settings *model.FileSettings) *model.AppError {
var fileBackendSettings filestore.FileBackendSettings
if a.License().IsCloud() && a.Config().FeatureFlags.CloudDedicatedExportUI && a.Config().FileSettings.DedicatedExportStore != nil && *a.Config().FileSettings.DedicatedExportStore {
fileBackendSettings = filestore.NewExportFileBackendSettingsFromConfig(settings, false, false)
} else {
fileBackendSettings = filestore.NewFileBackendSettingsFromConfig(settings, false, false)
}
err := fileBackendSettings.CheckMandatoryS3Fields()
if err != nil {
2022-08-18 05:01:37 -04:00
return model.NewAppError("CheckMandatoryS3Fields", "api.admin.test_s3.missing_s3_bucket", nil, "", http.StatusBadRequest).Wrap(err)
}
return nil
}
func connectionTestErrorToAppError(connTestErr error) *model.AppError {
switch err := connTestErr.(type) {
case *filestore.S3FileBackendAuthError:
2022-08-18 05:01:37 -04:00
return model.NewAppError("TestConnection", "api.file.test_connection_s3_auth.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
case *filestore.S3FileBackendNoBucketError:
2022-08-18 05:01:37 -04:00
return model.NewAppError("TestConnection", "api.file.test_connection_s3_bucket_does_not_exist.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
default:
2022-08-18 05:01:37 -04:00
return model.NewAppError("TestConnection", "api.file.test_connection.app_error", nil, "", http.StatusInternalServerError).Wrap(connTestErr)
}
}
func (a *App) TestFileStoreConnection() *model.AppError {
nErr := a.FileBackend().TestConnection()
if nErr != nil {
return connectionTestErrorToAppError(nErr)
}
return nil
}
func (a *App) TestFileStoreConnectionWithConfig(cfg *model.FileSettings) *model.AppError {
license := a.Srv().License()
insecure := a.Config().ServiceSettings.EnableInsecureOutgoingConnections
var backend filestore.FileBackend
var err error
complianceEnabled := license != nil && *license.Features.Compliance
if license.IsCloud() && a.Config().FeatureFlags.CloudDedicatedExportUI && a.Config().FileSettings.DedicatedExportStore != nil && *a.Config().FileSettings.DedicatedExportStore {
allowInsecure := a.Config().ServiceSettings.EnableInsecureOutgoingConnections != nil && *a.Config().ServiceSettings.EnableInsecureOutgoingConnections
backend, err = filestore.NewFileBackend(filestore.NewExportFileBackendSettingsFromConfig(cfg, complianceEnabled && license.IsCloud(), allowInsecure))
} else {
backend, err = filestore.NewFileBackend(filestore.NewFileBackendSettingsFromConfig(cfg, complianceEnabled, insecure != nil && *insecure))
}
if err != nil {
MM-59966 - Compliance Export overhaul - feature branch (#29789) * [MM-59089] Add a compliance export constant (#27919) * add a useful constant * i18n * another constant * another i18n * [MM-60422] Add GetChannelsWithActivityDuring (#28301) * modify GetUsersInChannelDuring to accept a slice of channelIds * add GetChannelsWithActivityDuring * add compliance export progress message; remove unused custom status * linting * tests running too fast * add batch size config settings * add store tests * linting * empty commit * i18n changes * fix i18n ordering * MM-60570 - Server-side changes consolidating the export CLI with server/ent code (#28640) * add an i18n field; add the CLI's export directory * int64 -> int * Add UntilUpdateAt for MessageExport and AnalyticsPostCount to merge * remove now-unused i18n strings * add TranslationsPreInitFromBuffer to allow CLI to use i18n * use GetBuilder to simplify; rename TranslationsPreInitFromFileBytes * [MM-59089] Improve compliance export timings (#1733 - Enterprise repo) * MM-60422 - Performance and logic fixes for Compliance Exports (#1757 - Enterprise repo) * MM-60570 - Enterprise-side changes consolidating the export CLI with server/ent code (#1769 - Enterprise repo) * merge conflicts; missed file from ent branch * MM-61038 - Add an option to sqlstore.New (#28702) remove useless comment add test add an option to sqlstore.New * MM-60976: Remove RunExport command from Mattermost binary (#28805) * remove RunExport command from mattermost binary * remove the code it was calling * fix i18n * remove test (was only testing license, not functionality) * empty commit * fix flaky GetChannelsWithActivityDuring test * MM-60063: Dedicated Export Filestore fix, redo of #1772 (enterprise) (#28803) * redo filestore fix #1772 (enterprise repo) on top of MM-59966 feature * add new e2e tests for export filestore * golint * ok, note to self: shadowing bad, actually (when there's a defer) * empty commit * MM-61137 - Message export: Support 7.8.11 era dbs (#28824) * support 7.8.11 era dbs by wrapping the store using only what we need * fix flaky GetChannelsWithActivityDuring test * add a comment * only need to define the MEFileInfoStore (the one that'll be overridden) * blank commit * MM-60974 - Message Export: Add performance metrics (#28836) * support 7.8.11 era dbs by wrapping the store using only what we need * fix flaky GetChannelsWithActivityDuring test * add a comment * only need to define the MEFileInfoStore (the one that'll be overridden) * performance metrics * cleanup unneeded named returns * blank commit * MM-60975 - Message export: Add startTime and endTime to export folder name (#28840) * support 7.8.11 era dbs by wrapping the store using only what we need * fix flaky GetChannelsWithActivityDuring test * add a comment * only need to define the MEFileInfoStore (the one that'll be overridden) * performance metrics * output startTime and endTime in export folder * empty commit * merge conflict * MM-60978 - Message export: Improve xml fields; fix delete semantics (#28873) * support 7.8.11 era dbs by wrapping the store using only what we need * fix flaky GetChannelsWithActivityDuring test * add a comment * only need to define the MEFileInfoStore (the one that'll be overridden) * performance metrics * output startTime and endTime in export folder * empty commit * add xml fields, omit when empty, tests * fix delete semantics; test (and test for update semantics) * clarify comments * simplify edited post detection, now there's no edge case. * add some spacing to help fast running tests * merge conflicts/updates needed for new deleted post semantics * linting; fixing tests from upstream merge * use SafeDereference * linting * stronger typing; better wrapped errors; better formatting * blank commit * goimports formatting * fix merge mistake * minor fixes due to changes in master * MM-61755 - Simplifying and Support reporting to the db from the CLI (#29281) * finally clean up JobData struct and stringMap; prep for CLI using db * and now simplify using StringMapToJobDataWithZeroValues * remove unused fn * create JobDataExported; clean up errors * MM-60176 - Message Export: Global relay cleanup (#29168) * move global relay logic into global_relay_export * blank commit * blank commit * improve errors * MM-60693 - Refactor CSV to use same codepath as Actiance (#29191) * move global relay logic into global_relay_export * blank commit * refactor (and simplify) ExportParams into shared * blank commit * remove unused fn * csv now uses pre-calculated joins/leaves like actiance * improve errors * remove nil post check; remove ignoredPosts metric * remove unneeded copy * MM-61696 - Refactor GlobalRelay to use same codepath as Actiance (#29225) * move global relay logic into global_relay_export * blank commit * refactor (and simplify) ExportParams into shared * blank commit * remove unused fn * csv now uses pre-calculated joins/leaves like actiance * remove newly unneeded function and its test. goodbye. * refactor GetPostAttachments for csv + global relay to share * refactor global_relay_export and fix tests (no changes to output) * improve errors * remove nil post check; remove ignoredPosts metric * remove unneeded copy * remove unneeded nil check * PR comments * MM-61715 - Generalize e2e to all export types 🤖 (#29369) * move global relay logic into global_relay_export * blank commit * refactor (and simplify) ExportParams into shared * blank commit * remove unused fn * csv now uses pre-calculated joins/leaves like actiance * remove newly unneeded function and its test. goodbye. * refactor GetPostAttachments for csv + global relay to share * refactor global_relay_export and fix tests (no changes to output) * improve errors * remove nil post check; remove ignoredPosts metric * remove unneeded copy * remove unneeded nil check * PR comments * refactor isDeletedMsg for all export types * fix start and endtime, nasty csv createAt bug; bring closer to Actiance * align unit tests with new logic (e.g. starttime / endtime) * refactor a TimestampConvert fn for code + tests * bug: pass templates to global relay (hurray for e2e tests, otherwise...) * add global relay zip to allowed list (only for tests) * test helpers * new templates for e2e tests * e2e tests... phew. * linting * merge conflicts * unexport PostToRow; add test helper marker * cleanup, shortening, thanks to PR comments * MM-61972 - Generalize export data path - Actiance (#29399) * extract and generalize the export data generation functions * finish moving test (bc of previous extraction) * lift a function from common -> shared (to break an import cycle) * actiance now takes general export data, processes it into actiance data * bring tests in line with correct sorting rules (upadateAt, messageId) * fixups, PR comments * turn strings.Repeat into a more descriptive const amended: one letter fix; bad rebase * MM-62009 - e2e clock heisenbug (#29434) * consolidate assertions; output debuggable diffs (keeping for future) * refactor test output generator to generators file * waitUntilZeroPosts + pass through until to job = fix all clock issues * simplify messages to model.NewId(); remove unneeded waitUntilZeroPosts * model.NewId() -> storetest.NewTestID() * MM-61980 - Generalize export data path - CSV (#29482) * simple refactoring * increase sleep times for (very) rare test failures * add extra information to the generic export for CSV * adj Actiance to handle new generic export (no difference in its output) * no longer need mergePosts (yay), move getJoinLeavePosts for everyone * adjust tests for new csv semantics (detailed in summary) * and need to add the new exported data to the export_data_tests * rearrange csv writing to happen after data export (more logical) * linting * remove debug statements * figured out what was wrong with global relay e2e test 3; solid now * PR comments * MM-61718 - Generalize export data path - Global Relay (#29508) * move global relay over to using the generalized export data * performance pass -- not much can be done * Update server/enterprise/message_export/global_relay_export/global_relay_export.go Co-authored-by: Claudio Costa <cstcld91@gmail.com> --------- Co-authored-by: Claudio Costa <cstcld91@gmail.com> * MM-62058 - Align CSV with Actiance (#29551) * refactoring actiance files and var names for clarity * bug found in exported attachments (we used to miss some start/ends) * changes needed for actiance due to new generic exports * bringing CSV up to actiance standards * fixing global relay b/c of new semantics (adding a note on an edge case) * aligning e2e tests, adding comments to clarify what is expected/tested * necessary changes; 1 more test for added functionality (ignoreDeleted) * comment style * MM-62059 - Align Global Relay with Actiance/CSV; many fixes (#29665) * core logic changes to general export_data and the specific export paths * unit tests and e2e tests, covering all new edge cases and all logic * linting * better var naming, const value, and cleaning up functions calls * MM-62436 - Temporarily skip cypress tests that require download link (#29772) --------- Co-authored-by: Claudio Costa <cstcld91@gmail.com>
2025-01-10 16:56:02 -05:00
return model.NewAppError("FileAttachmentBackend", "api.file.no_driver.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
nErr := backend.TestConnection()
if nErr != nil {
return connectionTestErrorToAppError(nErr)
}
return nil
}
func (a *App) ReadFile(path string) ([]byte, *model.AppError) {
return a.ch.srv.ReadFile(path)
}
func fileReader(backend filestore.FileBackend, path string) (filestore.ReadCloseSeeker, *model.AppError) {
result, nErr := backend.Reader(path)
if nErr != nil {
2022-08-18 05:01:37 -04:00
return nil, model.NewAppError("FileReader", "api.file.file_reader.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr)
}
return result, nil
}
func zipReader(backend filestore.FileBackend, path string, deflate bool) (io.ReadCloser, *model.AppError) {
result, err := backend.ZipReader(path, deflate)
if err != nil {
return nil, model.NewAppError("ZipReader", "api.file.zip_file_reader.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
return result, nil
}
func (s *Server) fileReader(path string) (filestore.ReadCloseSeeker, *model.AppError) {
return fileReader(s.FileBackend(), path)
}
func (s *Server) zipReader(path string, deflate bool) (io.ReadCloser, *model.AppError) {
return zipReader(s.FileBackend(), path, deflate)
}
func (s *Server) exportFileReader(path string) (filestore.ReadCloseSeeker, *model.AppError) {
return fileReader(s.ExportFileBackend(), path)
}
func (s *Server) exportZipReader(path string, deflate bool) (io.ReadCloser, *model.AppError) {
return zipReader(s.ExportFileBackend(), path, deflate)
}
// FileReader returns a ReadCloseSeeker for path from the FileBackend.
//
// The caller is responsible for closing the returned ReadCloseSeeker.
func (a *App) FileReader(path string) (filestore.ReadCloseSeeker, *model.AppError) {
return a.Srv().fileReader(path)
}
// ZipReader returns a ReadCloser for path. If deflate is true, the zip will use compression.
//
// The caller is responsible for closing the returned ReadCloser.
func (a *App) ZipReader(path string, deflate bool) (io.ReadCloser, *model.AppError) {
return a.Srv().zipReader(path, deflate)
}
// ExportFileReader returns a ReadCloseSeeker for path from the ExportFileBackend.
//
// The caller is responsible for closing the returned ReadCloseSeeker.
func (a *App) ExportFileReader(path string) (filestore.ReadCloseSeeker, *model.AppError) {
return a.Srv().exportFileReader(path)
}
// ExportZipReader returns a ReadCloser for path from the ExportFileBackend.
// If deflate is true, the zip will use compression.
//
// The caller is responsible for closing the returned ReadCloser.
func (a *App) ExportZipReader(path string, deflate bool) (io.ReadCloser, *model.AppError) {
return a.Srv().exportZipReader(path, deflate)
}
func (a *App) FileExists(path string) (bool, *model.AppError) {
return a.Srv().fileExists(path)
}
func (a *App) ExportFileExists(path string) (bool, *model.AppError) {
return a.Srv().exportFileExists(path)
}
func (s *Server) fileExists(path string) (bool, *model.AppError) {
return fileExists(s.FileBackend(), path)
}
func fileExists(backend filestore.FileBackend, path string) (bool, *model.AppError) {
result, nErr := backend.FileExists(path)
if nErr != nil {
2022-08-18 05:01:37 -04:00
return false, model.NewAppError("FileExists", "api.file.file_exists.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr)
}
return result, nil
}
func (s *Server) exportFileExists(path string) (bool, *model.AppError) {
return fileExists(s.ExportFileBackend(), path)
}
func (a *App) FileSize(path string) (int64, *model.AppError) {
size, nErr := a.FileBackend().FileSize(path)
if nErr != nil {
2022-08-18 05:01:37 -04:00
return 0, model.NewAppError("FileSize", "api.file.file_size.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr)
}
return size, nil
}
func fileModTime(backend filestore.FileBackend, path string) (time.Time, *model.AppError) {
modTime, nErr := backend.FileModTime(path)
if nErr != nil {
2022-08-18 05:01:37 -04:00
return time.Time{}, model.NewAppError("FileModTime", "api.file.file_mod_time.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr)
}
return modTime, nil
}
func (a *App) FileModTime(path string) (time.Time, *model.AppError) {
return fileModTime(a.FileBackend(), path)
}
func (a *App) ExportFileModTime(path string) (time.Time, *model.AppError) {
return fileModTime(a.ExportFileBackend(), path)
}
func (a *App) MoveFile(oldPath, newPath string) *model.AppError {
nErr := a.FileBackend().MoveFile(oldPath, newPath)
if nErr != nil {
2022-08-18 05:01:37 -04:00
return model.NewAppError("MoveFile", "api.file.move_file.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr)
}
return nil
}
func (a *App) WriteFileContext(ctx context.Context, fr io.Reader, path string) (int64, *model.AppError) {
return a.Srv().writeFileContext(ctx, fr, path)
}
func (a *App) WriteFile(fr io.Reader, path string) (int64, *model.AppError) {
return a.Srv().writeFile(fr, path)
}
func writeFile(backend filestore.FileBackend, fr io.Reader, path string) (int64, *model.AppError) {
result, nErr := backend.WriteFile(fr, path)
if nErr != nil {
2022-08-18 05:01:37 -04:00
return result, model.NewAppError("WriteFile", "api.file.write_file.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr)
}
return result, nil
}
func (s *Server) writeFile(fr io.Reader, path string) (int64, *model.AppError) {
return writeFile(s.FileBackend(), fr, path)
}
func (s *Server) writeExportFile(fr io.Reader, path string) (int64, *model.AppError) {
return writeFile(s.ExportFileBackend(), fr, path)
}
func (a *App) WriteExportFileContext(ctx context.Context, fr io.Reader, path string) (int64, *model.AppError) {
return a.Srv().writeExportFileContext(ctx, fr, path)
}
func (a *App) WriteExportFile(fr io.Reader, path string) (int64, *model.AppError) {
return a.Srv().writeExportFile(fr, path)
}
func writeFileContext(ctx context.Context, backend filestore.FileBackend, fr io.Reader, path string) (int64, *model.AppError) {
// Check if we can provide a custom context, otherwise just use the default method.
written, err := filestore.TryWriteFileContext(ctx, backend, fr, path)
if err != nil {
return written, model.NewAppError("WriteFile", "api.file.write_file.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
return written, nil
}
func (s *Server) writeFileContext(ctx context.Context, fr io.Reader, path string) (int64, *model.AppError) {
return writeFileContext(ctx, s.FileBackend(), fr, path)
}
func (s *Server) writeExportFileContext(ctx context.Context, fr io.Reader, path string) (int64, *model.AppError) {
return writeFileContext(ctx, s.ExportFileBackend(), fr, path)
}
func (a *App) AppendFile(fr io.Reader, path string) (int64, *model.AppError) {
result, nErr := a.FileBackend().AppendFile(fr, path)
if nErr != nil {
2022-08-18 05:01:37 -04:00
return result, model.NewAppError("AppendFile", "api.file.append_file.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr)
}
return result, nil
}
func (a *App) RemoveFile(path string) *model.AppError {
return a.Srv().removeFile(path)
}
func (a *App) RemoveExportFile(path string) *model.AppError {
return a.Srv().removeExportFile(path)
}
func removeFile(backend filestore.FileBackend, path string) *model.AppError {
nErr := backend.RemoveFile(path)
if nErr != nil {
2022-08-18 05:01:37 -04:00
return model.NewAppError("RemoveFile", "api.file.remove_file.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr)
}
return nil
}
func (s *Server) removeFile(path string) *model.AppError {
return removeFile(s.FileBackend(), path)
}
func (s *Server) removeExportFile(path string) *model.AppError {
return removeFile(s.ExportFileBackend(), path)
}
MM-16261: Synchronize plugins in HA (#11657) * MM-16272 - Synchronize plugins across cluster (#11611) * MM-16272 - Synchronize plugins across cluster * Adding a test * MM-16272 - Fixed tests * MM-16272 - PR feedback * MM-16270 - Plugin Sync (#11615) * Initial implementation for plugin synch with file store. WIP * Removed ListAll implementation. Used ListDirectory and change localstore to be consistent and return all items (files and folders) from directory * Refactored plugin filestore operations out of main install/remove plugin * Fixing error handling details * Changes to use structured logging * More logging fixes * Wording and comments improvements * Error handling and control flow improvements * Changed managed flag check to use os.stat * Added file store plugin dir and filename consts * Replaced FileRead to use a the FileReader in PluginSync * Minor styling and PR feedback changes * Minor error handling improvements * Added unit test for SyncPlugins. Changed SyncPlugins to use plugins environment to list available plugins * PR Feedback improvements * Minor err handling fix * Removing FileStorePath from PluginEventData (#11644) * Fix plugin path (#11654) * tweak path, logging Fix an issue not finding the plugins folder in S3. Tweak logging messages to add additional clarity. * Removing FileExists check when Syncing plugins. Updated localstore to not return an error when directory does not exist * PR Feedback * Install prepackaged plugins locally only (#11656) * s/uninstall/remove * Updated ClusterMessage comment * Updated PluginSync to test against s3 + local storage
2019-07-18 14:05:53 -04:00
func (a *App) ListDirectory(path string) ([]string, *model.AppError) {
return a.Srv().listDirectory(path, false)
}
func (a *App) ListExportDirectory(path string) ([]string, *model.AppError) {
return a.Srv().listExportDirectory(path, false)
}
func (a *App) ListDirectoryRecursively(path string) ([]string, *model.AppError) {
return a.Srv().listDirectory(path, true)
}
func (s *Server) listDirectory(path string, recursion bool) ([]string, *model.AppError) {
return listDirectory(s.FileBackend(), path, recursion)
}
func listDirectory(backend filestore.FileBackend, path string, recursion bool) ([]string, *model.AppError) {
var paths []string
var nErr error
if recursion {
paths, nErr = backend.ListDirectoryRecursively(path)
} else {
paths, nErr = backend.ListDirectory(path)
}
if nErr != nil {
return nil, model.NewAppError("ListExportDirectory", "api.file.list_directory.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr)
MM-16261: Synchronize plugins in HA (#11657) * MM-16272 - Synchronize plugins across cluster (#11611) * MM-16272 - Synchronize plugins across cluster * Adding a test * MM-16272 - Fixed tests * MM-16272 - PR feedback * MM-16270 - Plugin Sync (#11615) * Initial implementation for plugin synch with file store. WIP * Removed ListAll implementation. Used ListDirectory and change localstore to be consistent and return all items (files and folders) from directory * Refactored plugin filestore operations out of main install/remove plugin * Fixing error handling details * Changes to use structured logging * More logging fixes * Wording and comments improvements * Error handling and control flow improvements * Changed managed flag check to use os.stat * Added file store plugin dir and filename consts * Replaced FileRead to use a the FileReader in PluginSync * Minor styling and PR feedback changes * Minor error handling improvements * Added unit test for SyncPlugins. Changed SyncPlugins to use plugins environment to list available plugins * PR Feedback improvements * Minor err handling fix * Removing FileStorePath from PluginEventData (#11644) * Fix plugin path (#11654) * tweak path, logging Fix an issue not finding the plugins folder in S3. Tweak logging messages to add additional clarity. * Removing FileExists check when Syncing plugins. Updated localstore to not return an error when directory does not exist * PR Feedback * Install prepackaged plugins locally only (#11656) * s/uninstall/remove * Updated ClusterMessage comment * Updated PluginSync to test against s3 + local storage
2019-07-18 14:05:53 -04:00
}
return paths, nil
MM-16261: Synchronize plugins in HA (#11657) * MM-16272 - Synchronize plugins across cluster (#11611) * MM-16272 - Synchronize plugins across cluster * Adding a test * MM-16272 - Fixed tests * MM-16272 - PR feedback * MM-16270 - Plugin Sync (#11615) * Initial implementation for plugin synch with file store. WIP * Removed ListAll implementation. Used ListDirectory and change localstore to be consistent and return all items (files and folders) from directory * Refactored plugin filestore operations out of main install/remove plugin * Fixing error handling details * Changes to use structured logging * More logging fixes * Wording and comments improvements * Error handling and control flow improvements * Changed managed flag check to use os.stat * Added file store plugin dir and filename consts * Replaced FileRead to use a the FileReader in PluginSync * Minor styling and PR feedback changes * Minor error handling improvements * Added unit test for SyncPlugins. Changed SyncPlugins to use plugins environment to list available plugins * PR Feedback improvements * Minor err handling fix * Removing FileStorePath from PluginEventData (#11644) * Fix plugin path (#11654) * tweak path, logging Fix an issue not finding the plugins folder in S3. Tweak logging messages to add additional clarity. * Removing FileExists check when Syncing plugins. Updated localstore to not return an error when directory does not exist * PR Feedback * Install prepackaged plugins locally only (#11656) * s/uninstall/remove * Updated ClusterMessage comment * Updated PluginSync to test against s3 + local storage
2019-07-18 14:05:53 -04:00
}
func (s *Server) listExportDirectory(path string, recursion bool) ([]string, *model.AppError) {
return listDirectory(s.ExportFileBackend(), path, recursion)
}
func (a *App) RemoveDirectory(path string) *model.AppError {
nErr := a.FileBackend().RemoveDirectory(path)
if nErr != nil {
2022-08-18 05:01:37 -04:00
return model.NewAppError("RemoveDirectory", "api.file.remove_directory.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr)
}
return nil
}
func (a *App) getInfoForFilename(rctx request.CTX, post *model.Post, teamID, channelID, userID, oldId, filename string) *model.FileInfo {
name, _ := url.QueryUnescape(filename)
pathPrefix := fmt.Sprintf("teams/%s/channels/%s/users/%s/%s/", teamID, channelID, userID, oldId)
path := pathPrefix + name
// Open the file and populate the fields of the FileInfo
data, err := a.ReadFile(path)
if err != nil {
rctx.Logger().Error(
"File not found when migrating post to use FileInfos",
mlog.String("post_id", post.Id),
mlog.String("filename", filename),
mlog.String("path", path),
mlog.Err(err),
)
return nil
}
info, err := getInfoForBytes(name, bytes.NewReader(data), len(data))
if err != nil {
rctx.Logger().Warn(
"Unable to fully decode file info when migrating post to use FileInfos",
mlog.String("post_id", post.Id),
mlog.String("filename", filename),
mlog.Err(err),
)
}
// Generate a new ID because with the old system, you could very rarely get multiple posts referencing the same file
info.Id = model.NewId()
info.CreatorId = post.UserId
info.PostId = post.Id
info.ChannelId = post.ChannelId
info.CreateAt = post.CreateAt
info.UpdateAt = post.UpdateAt
info.Path = path
if info.IsImage() && !info.IsSvg() {
nameWithoutExtension := name[:strings.LastIndex(name, ".")]
info.PreviewPath = pathPrefix + nameWithoutExtension + "_preview." + getFileExtFromMimeType(info.MimeType)
info.ThumbnailPath = pathPrefix + nameWithoutExtension + "_thumb." + getFileExtFromMimeType(info.MimeType)
}
return info
}
func (a *App) findTeamIdForFilename(rctx request.CTX, post *model.Post, id, filename string) string {
name, _ := url.QueryUnescape(filename)
// This post is in a direct channel so we need to figure out what team the files are stored under.
teams, err := a.Srv().Store().Team().GetTeamsByUserId(post.UserId)
if err != nil {
rctx.Logger().Error("Unable to get teams when migrating post to use FileInfo", mlog.Err(err), mlog.String("post_id", post.Id))
return ""
}
if len(teams) == 1 {
// The user has only one team so the post must've been sent from it
return teams[0].Id
}
for _, team := range teams {
path := fmt.Sprintf("teams/%s/channels/%s/users/%s/%s/%s", team.Id, post.ChannelId, post.UserId, id, name)
if ok, err := a.FileExists(path); ok && err == nil {
// Found the team that this file was posted from
return team.Id
}
}
return ""
}
var (
fileMigrationLock sync.Mutex
oldFilenameMatchExp = regexp.MustCompile(`^\/([a-z\d]{26})\/([a-z\d]{26})\/([a-z\d]{26})\/([^\/]+)$`)
)
// Parse the path from the Filename of the form /{channelID}/{userID}/{uid}/{nameWithExtension}
func parseOldFilenames(rctx request.CTX, filenames []string, channelID, userID string) [][]string {
parsed := [][]string{}
for _, filename := range filenames {
matches := oldFilenameMatchExp.FindStringSubmatch(filename)
if len(matches) != 5 {
rctx.Logger().Error("Failed to parse old Filename", mlog.String("filename", filename))
continue
}
if matches[1] != channelID {
rctx.Logger().Error("ChannelId in Filename does not match", mlog.String("channel_id", channelID), mlog.String("matched", matches[1]))
} else if matches[2] != userID {
rctx.Logger().Error("UserId in Filename does not match", mlog.String("user_id", userID), mlog.String("matched", matches[2]))
} else {
parsed = append(parsed, matches[1:])
}
}
return parsed
}
// MigrateFilenamesToFileInfos creates and stores FileInfos for a post created before the FileInfos table existed.
func (a *App) MigrateFilenamesToFileInfos(rctx request.CTX, post *model.Post) []*model.FileInfo {
if len(post.Filenames) == 0 {
rctx.Logger().Warn("Unable to migrate post to use FileInfos with an empty Filenames field", mlog.String("post_id", post.Id))
return []*model.FileInfo{}
}
channel, errCh := a.Srv().Store().Channel().Get(post.ChannelId, true)
// There's a weird bug that rarely happens where a post ends up with duplicate Filenames so remove those
filenames := utils.RemoveDuplicatesFromStringArray(post.Filenames)
if errCh != nil {
rctx.Logger().Error(
"Unable to get channel when migrating post to use FileInfos",
mlog.String("post_id", post.Id),
mlog.String("channel_id", post.ChannelId),
mlog.Err(errCh),
)
return []*model.FileInfo{}
}
// Parse and validate filenames before further processing
parsedFilenames := parseOldFilenames(rctx, filenames, post.ChannelId, post.UserId)
if len(parsedFilenames) == 0 {
rctx.Logger().Error("Unable to parse filenames")
return []*model.FileInfo{}
}
// Find the team that was used to make this post since its part of the file path that isn't saved in the Filename
var teamID string
if channel.TeamId == "" {
// This post was made in a cross-team DM channel, so we need to find where its files were saved
teamID = a.findTeamIdForFilename(rctx, post, parsedFilenames[0][2], parsedFilenames[0][3])
} else {
teamID = channel.TeamId
}
// Create FileInfo objects for this post
infos := make([]*model.FileInfo, 0, len(filenames))
if teamID == "" {
rctx.Logger().Error(
"Unable to find team id for files when migrating post to use FileInfos",
mlog.String("filenames", strings.Join(filenames, ",")),
mlog.String("post_id", post.Id),
)
} else {
for _, parsed := range parsedFilenames {
info := a.getInfoForFilename(rctx, post, teamID, parsed[0], parsed[1], parsed[2], parsed[3])
if info == nil {
continue
}
infos = append(infos, info)
}
}
// Lock to prevent only one migration thread from trying to update the post at once, preventing duplicate FileInfos from being created
fileMigrationLock.Lock()
defer fileMigrationLock.Unlock()
result, nErr := a.Srv().Store().Post().Get(rctx, post.Id, model.GetPostsOptions{}, "", a.Config().GetSanitizeOptions())
if nErr != nil {
rctx.Logger().Error("Unable to get post when migrating post to use FileInfos", mlog.Err(nErr), mlog.String("post_id", post.Id))
return []*model.FileInfo{}
}
if newPost := result.Posts[post.Id]; len(newPost.Filenames) != len(post.Filenames) {
// Another thread has already created FileInfos for this post, so just return those
var fileInfos []*model.FileInfo
fileInfos, nErr = a.Srv().Store().FileInfo().GetForPost(post.Id, true, false, false)
if nErr != nil {
rctx.Logger().Error("Unable to get FileInfos for migrated post", mlog.Err(nErr), mlog.String("post_id", post.Id))
return []*model.FileInfo{}
}
rctx.Logger().Debug("Post already migrated to use FileInfos", mlog.String("post_id", post.Id))
return fileInfos
}
rctx.Logger().Debug("Migrating post to use FileInfos", mlog.String("post_id", post.Id))
savedInfos := make([]*model.FileInfo, 0, len(infos))
fileIDs := make([]string, 0, len(filenames))
for _, info := range infos {
if _, nErr = a.Srv().Store().FileInfo().Save(rctx, info); nErr != nil {
rctx.Logger().Error(
"Unable to save file info when migrating post to use FileInfos",
mlog.String("post_id", post.Id),
mlog.String("file_info_id", info.Id),
mlog.String("file_info_path", info.Path),
mlog.Err(nErr),
)
continue
}
savedInfos = append(savedInfos, info)
fileIDs = append(fileIDs, info.Id)
}
// Copy and save the updated post
2020-05-15 11:57:15 -04:00
newPost := post.Clone()
newPost.Filenames = []string{}
newPost.FileIds = fileIDs
// Update Posts to clear Filenames and set FileIds
if _, nErr = a.Srv().Store().Post().Update(rctx, newPost, post); nErr != nil {
rctx.Logger().Error(
"Unable to save migrated post when migrating to use FileInfos",
mlog.String("new_file_ids", strings.Join(newPost.FileIds, ",")),
mlog.String("old_filenames", strings.Join(post.Filenames, ",")),
mlog.String("post_id", post.Id),
mlog.Err(nErr),
)
return []*model.FileInfo{}
}
return savedInfos
}
2017-11-09 15:46:20 -05:00
func (a *App) GeneratePublicLink(siteURL string, info *model.FileInfo) string {
hash := GeneratePublicLinkHash(info.Id, *a.Config().FileSettings.PublicLinkSalt)
return fmt.Sprintf("%s/files/%v/public?h=%s", siteURL, info.Id, hash)
}
func GeneratePublicLinkHash(fileID, salt string) string {
hash := sha256.New()
hash.Write([]byte(salt))
hash.Write([]byte(fileID))
return base64.RawURLEncoding.EncodeToString(hash.Sum(nil))
}
// UploadFile uploads a single file in form of a completely constructed byte array for a channel.
func (a *App) UploadFile(rctx request.CTX, data []byte, channelID string, filename string) (*model.FileInfo, *model.AppError) {
return a.UploadFileForUserAndTeam(rctx, data, channelID, filename, "", "")
}
func (a *App) UploadFileForUserAndTeam(rctx request.CTX, data []byte, channelID string, filename string, rawUserId string, rawTeamId string) (*model.FileInfo, *model.AppError) {
_, err := a.GetChannel(rctx, channelID)
if err != nil && channelID != "" {
return nil, model.NewAppError("UploadFile", "api.file.upload_file.incorrect_channelId.app_error",
map[string]any{"channelId": channelID}, "", http.StatusBadRequest)
}
userId := rawUserId
if userId == "" {
userId = "nouser"
}
teamId := rawTeamId
if teamId == "" {
teamId = "noteam"
}
info, _, appError := a.DoUploadFileExpectModification(rctx, time.Now(), teamId, channelID, userId, filename, data, true)
if appError != nil {
return nil, appError
}
if info.PreviewPath != "" || info.ThumbnailPath != "" {
previewPathList := []string{info.PreviewPath}
thumbnailPathList := []string{info.ThumbnailPath}
imageDataList := [][]byte{data}
a.HandleImages(rctx, previewPathList, thumbnailPathList, imageDataList)
}
return info, nil
}
func (a *App) DoUploadFile(rctx request.CTX, now time.Time, rawTeamId string, rawChannelId string, rawUserId string, rawFilename string, data []byte, extractContent bool) (*model.FileInfo, *model.AppError) {
info, _, err := a.DoUploadFileExpectModification(rctx, now, rawTeamId, rawChannelId, rawUserId, rawFilename, data, extractContent)
return info, err
}
func UploadFileSetTeamId(teamID string) func(t *UploadFileTask) {
return func(t *UploadFileTask) {
t.TeamId = filepath.Base(teamID)
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
}
}
func UploadFileSetUserId(userID string) func(t *UploadFileTask) {
return func(t *UploadFileTask) {
t.UserId = filepath.Base(userID)
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
}
}
func UploadFileSetTimestamp(timestamp time.Time) func(t *UploadFileTask) {
return func(t *UploadFileTask) {
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
t.Timestamp = timestamp
}
}
func UploadFileSetContentLength(contentLength int64) func(t *UploadFileTask) {
return func(t *UploadFileTask) {
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
t.ContentLength = contentLength
}
}
func UploadFileSetClientId(clientId string) func(t *UploadFileTask) {
return func(t *UploadFileTask) {
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
t.ClientId = clientId
}
}
func UploadFileSetRaw() func(t *UploadFileTask) {
return func(t *UploadFileTask) {
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
t.Raw = true
}
}
func UploadFileSetExtractContent(value bool) func(t *UploadFileTask) {
return func(t *UploadFileTask) {
t.ExtractContent = value
}
}
type UploadFileTask struct {
Logger mlog.LoggerIFace
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
// File name.
Name string
ChannelId string
TeamId string
UserId string
// Time stamp to use when creating the file.
Timestamp time.Time
// The value of the Content-Length http header, when available.
ContentLength int64
// The file data stream.
Input io.Reader
// An optional, client-assigned Id field.
ClientId string
// If Raw, do not execute special processing for images, just upload
// the file. Plugins are still invoked.
Raw bool
// Whether or not to extract file attachments content
// This is used by the bulk import process.
ExtractContent bool
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
//=============================================================
// Internal state
buf *bytes.Buffer
limit int64
limitedInput io.Reader
teeInput io.Reader
fileinfo *model.FileInfo
maxFileSize int64
maxImageRes int64
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
// Cached image data that (may) get initialized in preprocessImage and
// is used in postprocessImage
decoded image.Image
imageType string
imageOrientation int
// Testing: overridable dependency functions
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
pluginsEnvironment *plugin.Environment
writeFile func(io.Reader, string) (int64, *model.AppError)
saveToDatabase func(request.CTX, *model.FileInfo) (*model.FileInfo, error)
imgDecoder *imaging.Decoder
imgEncoder *imaging.Encoder
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
}
func (t *UploadFileTask) init(a *App) {
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
t.buf = &bytes.Buffer{}
if t.ContentLength > 0 {
t.limit = t.ContentLength
} else {
t.limit = t.maxFileSize
}
if t.ContentLength > 0 && t.ContentLength < maxUploadInitialBufferSize {
t.buf.Grow(int(t.ContentLength))
} else {
t.buf.Grow(maxUploadInitialBufferSize)
}
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
t.fileinfo = model.NewInfo(filepath.Base(t.Name))
t.fileinfo.Id = model.NewId()
t.fileinfo.CreatorId = t.UserId
t.fileinfo.CreateAt = t.Timestamp.UnixNano() / int64(time.Millisecond)
t.fileinfo.Path = t.pathPrefix() + t.Name
Channel Bookmarks (#25449) * create ChannelBookmarks table * ChannelBookmark model * channel bookamrks Store layer * add GetBookmarksForAllChannelByIdSince * add channel bookmarks to test store * Add channel bookmarks to app layer * remove index for createAt in channel bookmarks migrations * remove createAt from select channel bookmark query and enable store delete bookmark test * update reponse of UpdateBookmark * rename db migration files * channel bookmarks store update sort order * channel bookmarks app layer update sort order * fix lint & tests * Fix lint and introduce util functions to insert / remove from slice * remove model etag * i18n * defer remove file info after test run * Fix tests passing the request context * fix migrations * fix TestRetry * Add bookmark permissions (#25560) * Adds channel bookmarks permissions * Fix linter * Remove unnecessary empty lines * Remove scss change as it's not necessary anymore * Fix mock store * Fix mock store and add role entry * Fix test * Adds cypress test and update permissions migration to update admin roles * Adds channel bookmarks roles to default admin roles * Adds bookmark permissions to default role permissions constant in webapp * Update mmctl test * Update permission test after normalising the roles * fix store tests * fix app layer tests * Add new bookmark endpoint (#25624) * Adds channel bookmarks api scaffold and create endpoint * Applies review comments to the API docs * Adds websocket test to create channel bookmark --------- Co-authored-by: Mattermost Build <build@mattermost.com> * MM-54426 exclude Channel Bookmarks files from data retention (#25656) * Augment channel APIs to include bookmarks (#25567) * update files docs for server 9.4 * Adds update channel bookmark endpoint (#25653) * Adds update channel bookmark sort order endpoint (#25686) * Adds update channel bookmark endpoint * Updates edit app method to return the right deleted bookmark and adds tests * Adds the update channel bookmark sort order endpoint * Fix repeated test after merge * Assign right permissions to each test * Update store and app layer to return specific errors and add tests * Adds delete channel bookmark endpoint (#25693) * Updates edit app method to return the right deleted bookmark and adds tests * Fix repeated test after merge * Updates edit app method to return the right deleted bookmark and adds tests * Adds delete channel bookmark endpoint * Adds list channel bookmarks endpoint (#25700) * Add channel moderation to bookmarks (#25716) * fix migrations index * fix getChannelsForTeamForUser * fix getChannelsForTeamForUser * fix bad merge client4 * fix file api with bookmark permission * add ChannelBookmarks feature flag * add missing translations * Set DB column for type as enum * use custom type for bookmark query using sqlx * use transaction when saving bookmark * return NewErrNotFound instead of Sql.ErrNoRows * use squirrel for IN query * add a limit of 1K for records in GetBookmarksForAllChannelByIdSince * UpdateSortOrder with one single query instead of multiple updates * fix shadow declaration * fix channel bookmarks permission string definition in admin console * fix another shadow declaration * Fix model conversion * add SplitSliceInChunks * remove include bookmarks in channels api * Cap amount of bookmarks per channel * add etag back to get channels * feedback review * update file info when replacing a bookmark file * return 501 not implemented when the license is not available * add detail message when getting channel member on bookmark api * start audit before permission check on create bookmark api * use require.Eventuallyf for testing WS events * remove unnecessary log in app layer * use require instead of assert to avoid panics * enforce limit when querying bookmarks since * prevent to create/update bookmark if file is already attached * fix lint * delete file when a bookmark is deleted * Dot allow to set a fileId and a url at the same time to a bookmark * fix query to delete a file that belongs to a bookmark * do not patch the bookmark type * Server side FeatureFlag check (#26145) * use ff in server, set ff to false * turn on FF for unit tests * defer unset FF for unit tests * turn ff on for testing * only allow attaching files that were uploaded for bookmark * Set feature flag off as default * fix lint * update email templates as PR failed * revert templates * force the assignment of ID when creating a bookmark * Fix unit tests --------- Co-authored-by: Miguel de la Cruz <miguel@mcrx.me> Co-authored-by: Mattermost Build <build@mattermost.com> Co-authored-by: Caleb Roseland <caleb@calebroseland.com> Co-authored-by: Scott Bishel <scott.bishel@mattermost.com>
2024-03-12 10:36:05 -04:00
t.fileinfo.ChannelId = t.ChannelId
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
t.limitedInput = &io.LimitedReader{
R: t.Input,
N: t.limit + 1,
}
t.teeInput = io.TeeReader(t.limitedInput, t.buf)
t.pluginsEnvironment = a.GetPluginsEnvironment()
t.writeFile = a.WriteFile
t.saveToDatabase = a.Srv().Store().FileInfo().Save
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
}
// UploadFileX uploads a single file as specified in t. It applies the upload
// constraints, executes plugins and image processing logic as needed. It
// returns a filled-out FileInfo and an optional error. A plugin may reject the
// upload, returning a rejection error. In this case FileInfo would have
// contained the last "good" FileInfo before the execution of that plugin.
func (a *App) UploadFileX(rctx request.CTX, channelID, name string, input io.Reader,
opts ...func(*UploadFileTask),
) (*model.FileInfo, *model.AppError) {
t := &UploadFileTask{
Logger: rctx.Logger(),
ChannelId: filepath.Base(channelID),
Name: filepath.Base(name),
Input: input,
maxFileSize: *a.Config().FileSettings.MaxFileSize,
maxImageRes: *a.Config().FileSettings.MaxImageResolution,
imgDecoder: a.ch.imgDecoder,
imgEncoder: a.ch.imgEncoder,
ExtractContent: true,
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
}
for _, o := range opts {
o(t)
}
rctx = rctx.WithLogFields(
mlog.String("file_name", name),
mlog.String("channel_id", channelID),
mlog.String("user_id", t.UserId),
)
if *a.Config().FileSettings.DriverName == "" {
return nil, t.newAppError("api.file.upload_file.storage.app_error", http.StatusNotImplemented)
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
}
if t.ContentLength > t.maxFileSize {
return nil, t.newAppError("api.file.upload_file.too_large_detailed.app_error", http.StatusRequestEntityTooLarge, "Length", t.ContentLength, "Limit", t.maxFileSize)
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
}
t.init(a)
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
var aerr *model.AppError
if !t.Raw && t.fileinfo.IsImage() {
aerr = t.preprocessImage()
if aerr != nil {
return t.fileinfo, aerr
}
}
written, aerr := t.writeFile(io.MultiReader(t.buf, t.limitedInput), t.fileinfo.Path)
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
if aerr != nil {
return nil, aerr
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
}
if written > t.maxFileSize {
if fileErr := a.RemoveFile(t.fileinfo.Path); fileErr != nil {
rctx.Logger().Error("Failed to remove file", mlog.Err(fileErr))
}
return nil, t.newAppError("api.file.upload_file.too_large_detailed.app_error", http.StatusRequestEntityTooLarge, "Length", t.ContentLength, "Limit", t.maxFileSize)
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
}
t.fileinfo.Size = written
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
file, aerr := a.FileReader(t.fileinfo.Path)
if aerr != nil {
return nil, aerr
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
}
defer file.Close()
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
aerr = a.runPluginsHook(rctx, t.fileinfo, file)
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
if aerr != nil {
return nil, aerr
}
if !t.Raw && t.fileinfo.IsImage() {
file, aerr = a.FileReader(t.fileinfo.Path)
if aerr != nil {
return nil, aerr
}
defer file.Close()
t.postprocessImage(file)
}
if _, err := t.saveToDatabase(rctx, t.fileinfo); err != nil {
var appErr *model.AppError
switch {
case errors.As(err, &appErr):
return nil, appErr
default:
2022-08-18 05:01:37 -04:00
return nil, model.NewAppError("UploadFileX", "app.file_info.save.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
}
if *a.Config().FileSettings.ExtractContent && t.ExtractContent {
infoCopy := *t.fileinfo
2022-09-15 04:08:16 -04:00
a.Srv().GoBuffered(func() {
err := a.ExtractContentFromFileInfo(rctx, &infoCopy)
if err != nil {
rctx.Logger().Error("Failed to extract file content", mlog.Err(err), mlog.String("fileInfoId", infoCopy.Id))
}
})
}
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
return t.fileinfo, nil
}
func (t *UploadFileTask) preprocessImage() *model.AppError {
// If SVG, attempt to extract dimensions and then return
if t.fileinfo.IsSvg() {
svgInfo, err := imaging.ParseSVG(t.teeInput)
if err != nil {
t.Logger.Warn("Failed to parse SVG", mlog.Err(err))
}
if svgInfo.Width > 0 && svgInfo.Height > 0 {
t.fileinfo.Width = svgInfo.Width
t.fileinfo.Height = svgInfo.Height
}
t.fileinfo.HasPreviewImage = false
return nil
}
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
// If we fail to decode, return "as is".
cfg, format, err := t.imgDecoder.DecodeConfig(t.teeInput)
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
if err != nil {
return nil
}
t.fileinfo.Width = cfg.Width
t.fileinfo.Height = cfg.Height
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
if err = checkImageResolutionLimit(cfg.Width, cfg.Height, t.maxImageRes); err != nil {
2025-04-09 05:38:36 -04:00
return t.newAppError("api.file.upload_file.large_image_detailed.app_error", http.StatusBadRequest).Wrap(err)
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
}
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
t.fileinfo.HasPreviewImage = true
nameWithoutExtension := t.Name[:strings.LastIndex(t.Name, ".")]
t.fileinfo.PreviewPath = t.pathPrefix() + nameWithoutExtension + "_preview." + getFileExtFromMimeType(t.fileinfo.MimeType)
t.fileinfo.ThumbnailPath = t.pathPrefix() + nameWithoutExtension + "_thumb." + getFileExtFromMimeType(t.fileinfo.MimeType)
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
// check the image orientation with goexif; consume the bytes we
// already have first, then keep Tee-ing from input.
// TODO: try to reuse exif's .Raw buffer rather than Tee-ing
if t.imageOrientation, err = imaging.GetImageOrientation(io.MultiReader(bytes.NewReader(t.buf.Bytes()), t.teeInput), format); err == nil &&
(t.imageOrientation == imaging.RotatedCWMirrored ||
t.imageOrientation == imaging.RotatedCCW ||
t.imageOrientation == imaging.RotatedCCWMirrored ||
t.imageOrientation == imaging.RotatedCW) {
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
t.fileinfo.Width, t.fileinfo.Height = t.fileinfo.Height, t.fileinfo.Width
} else if err != nil {
t.Logger.Warn("Failed to get image orientation", mlog.Err(err))
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
}
// For animated GIFs disable the preview; since we have to Decode gifs
// anyway, cache the decoded image for later.
if t.fileinfo.MimeType == "image/gif" {
image, format, err := t.imgDecoder.Decode(io.MultiReader(bytes.NewReader(t.buf.Bytes()), t.teeInput))
if err == nil && image != nil {
t.fileinfo.HasPreviewImage = false
t.decoded = image
t.imageType = format
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
}
}
return nil
}
func (t *UploadFileTask) postprocessImage(file io.Reader) {
// don't try to process SVG files
if t.fileinfo.IsSvg() {
return
}
decoded, imgType := t.decoded, t.imageType
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
if decoded == nil {
var err error
var release func()
decoded, imgType, release, err = t.imgDecoder.DecodeMemBounded(file)
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
if err != nil {
t.Logger.Error("Unable to decode image", mlog.Err(err))
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
return
}
defer release()
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
}
decoded = imaging.MakeImageUpright(decoded, t.imageOrientation)
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
if decoded == nil {
return
}
writeImage := func(img image.Image, path string) {
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
r, w := io.Pipe()
go func() {
var err error
// It's okay to access imgType in a separate goroutine,
// because imgType is only written once and never written again.
if imgType == "png" {
err = t.imgEncoder.EncodePNG(w, img)
} else {
err = t.imgEncoder.EncodeJPEG(w, img, jpegEncQuality)
}
if err != nil {
t.Logger.Error("Unable to encode image as jpeg", mlog.String("path", path), mlog.Err(err))
w.CloseWithError(err)
} else {
w.Close()
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
}
}()
_, aerr := t.writeFile(r, path)
if aerr != nil {
t.Logger.Error("Unable to upload", mlog.String("path", path), mlog.Err(aerr))
r.CloseWithError(aerr) // always returns nil
return
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
}
}
var wg sync.WaitGroup
wg.Add(3)
// Generating thumbnail and preview regardless of HasPreviewImage value.
// This is needed on mobile in case of animated GIFs.
go func() {
defer wg.Done()
writeImage(imaging.GenerateThumbnail(decoded, imageThumbnailWidth, imageThumbnailHeight), t.fileinfo.ThumbnailPath)
}()
go func() {
defer wg.Done()
writeImage(imaging.GeneratePreview(decoded, imagePreviewWidth), t.fileinfo.PreviewPath)
}()
2020-10-02 04:14:57 -04:00
go func() {
defer wg.Done()
if t.fileinfo.MiniPreview == nil {
if miniPreview, err := imaging.GenerateMiniPreviewImage(decoded,
miniPreviewImageWidth, miniPreviewImageHeight, jpegEncQuality); err != nil {
t.Logger.Info("Unable to generate mini preview image", mlog.Err(err))
} else {
t.fileinfo.MiniPreview = &miniPreview
}
2020-10-02 04:14:57 -04:00
}
}()
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
wg.Wait()
}
func (t UploadFileTask) pathPrefix() string {
Channel Bookmarks (#25449) * create ChannelBookmarks table * ChannelBookmark model * channel bookamrks Store layer * add GetBookmarksForAllChannelByIdSince * add channel bookmarks to test store * Add channel bookmarks to app layer * remove index for createAt in channel bookmarks migrations * remove createAt from select channel bookmark query and enable store delete bookmark test * update reponse of UpdateBookmark * rename db migration files * channel bookmarks store update sort order * channel bookmarks app layer update sort order * fix lint & tests * Fix lint and introduce util functions to insert / remove from slice * remove model etag * i18n * defer remove file info after test run * Fix tests passing the request context * fix migrations * fix TestRetry * Add bookmark permissions (#25560) * Adds channel bookmarks permissions * Fix linter * Remove unnecessary empty lines * Remove scss change as it's not necessary anymore * Fix mock store * Fix mock store and add role entry * Fix test * Adds cypress test and update permissions migration to update admin roles * Adds channel bookmarks roles to default admin roles * Adds bookmark permissions to default role permissions constant in webapp * Update mmctl test * Update permission test after normalising the roles * fix store tests * fix app layer tests * Add new bookmark endpoint (#25624) * Adds channel bookmarks api scaffold and create endpoint * Applies review comments to the API docs * Adds websocket test to create channel bookmark --------- Co-authored-by: Mattermost Build <build@mattermost.com> * MM-54426 exclude Channel Bookmarks files from data retention (#25656) * Augment channel APIs to include bookmarks (#25567) * update files docs for server 9.4 * Adds update channel bookmark endpoint (#25653) * Adds update channel bookmark sort order endpoint (#25686) * Adds update channel bookmark endpoint * Updates edit app method to return the right deleted bookmark and adds tests * Adds the update channel bookmark sort order endpoint * Fix repeated test after merge * Assign right permissions to each test * Update store and app layer to return specific errors and add tests * Adds delete channel bookmark endpoint (#25693) * Updates edit app method to return the right deleted bookmark and adds tests * Fix repeated test after merge * Updates edit app method to return the right deleted bookmark and adds tests * Adds delete channel bookmark endpoint * Adds list channel bookmarks endpoint (#25700) * Add channel moderation to bookmarks (#25716) * fix migrations index * fix getChannelsForTeamForUser * fix getChannelsForTeamForUser * fix bad merge client4 * fix file api with bookmark permission * add ChannelBookmarks feature flag * add missing translations * Set DB column for type as enum * use custom type for bookmark query using sqlx * use transaction when saving bookmark * return NewErrNotFound instead of Sql.ErrNoRows * use squirrel for IN query * add a limit of 1K for records in GetBookmarksForAllChannelByIdSince * UpdateSortOrder with one single query instead of multiple updates * fix shadow declaration * fix channel bookmarks permission string definition in admin console * fix another shadow declaration * Fix model conversion * add SplitSliceInChunks * remove include bookmarks in channels api * Cap amount of bookmarks per channel * add etag back to get channels * feedback review * update file info when replacing a bookmark file * return 501 not implemented when the license is not available * add detail message when getting channel member on bookmark api * start audit before permission check on create bookmark api * use require.Eventuallyf for testing WS events * remove unnecessary log in app layer * use require instead of assert to avoid panics * enforce limit when querying bookmarks since * prevent to create/update bookmark if file is already attached * fix lint * delete file when a bookmark is deleted * Dot allow to set a fileId and a url at the same time to a bookmark * fix query to delete a file that belongs to a bookmark * do not patch the bookmark type * Server side FeatureFlag check (#26145) * use ff in server, set ff to false * turn on FF for unit tests * defer unset FF for unit tests * turn ff on for testing * only allow attaching files that were uploaded for bookmark * Set feature flag off as default * fix lint * update email templates as PR failed * revert templates * force the assignment of ID when creating a bookmark * Fix unit tests --------- Co-authored-by: Miguel de la Cruz <miguel@mcrx.me> Co-authored-by: Mattermost Build <build@mattermost.com> Co-authored-by: Caleb Roseland <caleb@calebroseland.com> Co-authored-by: Scott Bishel <scott.bishel@mattermost.com>
2024-03-12 10:36:05 -04:00
if t.UserId == model.BookmarkFileOwner {
return model.BookmarkFileOwner +
"/teams/" + t.TeamId +
"/channels/" + t.ChannelId +
"/" + t.fileinfo.Id + "/"
}
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
return t.Timestamp.Format("20060102") +
"/teams/" + t.TeamId +
"/channels/" + t.ChannelId +
"/users/" + t.UserId +
"/" + t.fileinfo.Id + "/"
}
func (t UploadFileTask) newAppError(id string, httpStatus int, extra ...any) *model.AppError {
params := map[string]any{
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
"Name": t.Name,
"Filename": t.Name,
"ChannelId": t.ChannelId,
"TeamId": t.TeamId,
"UserId": t.UserId,
"ContentLength": t.ContentLength,
"ClientId": t.ClientId,
}
if t.fileinfo != nil {
params["Width"] = t.fileinfo.Width
params["Height"] = t.fileinfo.Height
}
for i := 0; i+1 < len(extra); i += 2 {
params[fmt.Sprintf("%v", extra[i])] = extra[i+1]
}
return model.NewAppError("uploadFileTask", id, params, "", httpStatus)
MM-7633: Optimize memory utilization during file uploads (#9835) * MM-7633: Optimize memory utilization during file uploads Refactored the file upload code to reduce redundant buffering and stream directly to the file store. Added tests. Benchmark results: ``` levs-mbp:mattermost-server levb$ go test -v -run nothing -bench Upload -benchmem ./app ... BenchmarkUploadFile/random-5Mb-gif-raw-ish_DoUploadFile-4 10 122598031 ns/op 21211370 B/op 1008 allocs/op BenchmarkUploadFile/random-5Mb-gif-raw_UploadFileTask-4 100 20211926 ns/op 5678750 B/op 126 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFiles-4 2 1037051184 ns/op 81806360 B/op 3705013 allocs/op BenchmarkUploadFile/random-5Mb-gif-UploadFileTask-4 2 933644431 ns/op 67015868 B/op 3704410 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw-ish_DoUploadFile-4 100 13110509 ns/op 6032614 B/op 8052 allocs/op BenchmarkUploadFile/random-2Mb-jpg-raw_UploadFileTask-4 100 10729867 ns/op 1738303 B/op 125 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFiles-4 2 925274912 ns/op 70326352 B/op 3718856 allocs/op BenchmarkUploadFile/random-2Mb-jpg-UploadFileTask-4 2 995033336 ns/op 58113796 B/op 3710943 allocs/op BenchmarkUploadFile/zero-10Mb-raw-ish_DoUploadFile-4 30 50777211 ns/op 54791929 B/op 2714 allocs/op BenchmarkUploadFile/zero-10Mb-raw_UploadFileTask-4 50 36387339 ns/op 10503920 B/op 126 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFiles-4 30 48657678 ns/op 54791948 B/op 2719 allocs/op BenchmarkUploadFile/zero-10Mb-UploadFileTask-4 50 37506467 ns/op 31492060 B/op 131 allocs/op ... ``` https://mattermost.atlassian.net/browse/MM-7633 https://github.com/mattermost/mattermost-server/issues/7801 [Place an '[x]' (no spaces) in all applicable fields. Please remove unrelated fields.] - [x] Added or updated unit tests (required for all new features) - [ ] Added API documentation (required for all new APIs) - [ ] All new/modified APIs include changes to the drivers *N/A*??? - [x] Includes text changes and localization file ([.../i18n/en.json](https://github.com/mattermost/mattermost-server/blob/master/i18n/en.json)) updates Overview of changes: - api4 - Replaced `uploadFile` handler with `uploadFileStream` that reduces unnecessary buffering. - Added/refactored tests for the new API. - Refactored apitestlib/Check...Status functions. - app - Added App.UploadFileTask, a more efficient refactor of UploadFile. - Consistently set `FileInfo.HasPreviewImage` - Added benchmarks for the new and prior implementations - Replaced passing around `*image.Image` with `image.Image` in the existing code. - model - Added a more capable `client4.UploadFiles` API to match the new server API’s capabilities. - I18n - Replaced `api.file.upload_file.bad_parse.app_error` with a more generic `api.file.upload_file.read_request.app_error` - plugin - Removed type `plugin.multiPluginHookRunnerFunc` in favor of using `func(hooks Hooks) bool` explicitly, to help with testing - tests - Added test files for testing images Still remaining, but can be separate PRs - please let me know the preferred course of action - Investigate JS client API - how does it do multipart? - Performance loss from old code on (small) image processing? - Deprecate the old functions, change other API implementations to use UploadFileTask Definitely separate future PRs - should I file tickets foe these? - Only invoke t.readAll() if there are indeed applicable plugins to run - Find a way to leverage goexif buffer rather than re-reading Suggested long-term improvements - should I file separate tickets for these? - Actually allow uploading of large (GB-sized) files. This may require a change in how the file is passed to plugins. - (Many) api4 tests should probably be subtests and share a server setup - will be much faster - Performance improvements in image processing (goexif/thumbnail/preview) (maybe use https://mattermost.atlassian.net/browse/MM-10188 for this) Questions: 1. I am commiting MBs of test images, are there better alternatives? I can probably create much less dense images that would take up considerably less space, even at pretty large sizes 2. I18n: Do I need to do anything special for the string change? Or just wait until it gets picked up and translated/updated? 3. The image dimensions are flipped in resulting FileInfo to match the actual orientation. Is this by design? Should add a test for it, perhaps? 4. What to do in the case of partial success? S3 but not DB, some files but not others? For now, just doing what the old code did, I think. 5. Make maxUploadDrainBytes configurable? Also, should this be the systemic behavior of all APIs with non-empty body? Otherwise dropped altogether? Check all other ioutil.ReadAll() from sockets. Find a way to set a total byte limit on request Body? * WIP - Fixed for GetPluginsEnvironment() changes * WIP - PR feedback 1. Refactored checkHTTPStatus to improve failure messages 2. Use `var name []type` rather than `name := ([]type)(nil)` 3. Replaced single-letter `p` with a more intention-revealing `part` 4. Added tests for full image size, `HasPreviewImage` * WIP - rebased (c.Session->c.App.Session) * WIP - PR feedback: eliminated use of Request.MultipartReader Instead of hacking the request object to use r.MultipartReader now have own functions `parseMultipartRequestHeader` and `multipartReader` eliminating the need to hack the request object to use Request.MultipartReader limitations. * WIP - PR feedback: UploadFileX with functional options * WIP - PR feedback: style * WIP - PR feedback: errors cleanup * WIP - clarified File Upload benchmarks * WIP - PR feedback: display the value of erroneous formname * WIP - PR feedback: fixed handling of multiple channel_ids * WIP - rebased from master - fixed tests * PR Feedback * PR feedback - moved client4.UploadFiles to _test for now
2018-12-13 16:32:07 -05:00
}
func (a *App) DoUploadFileExpectModification(rctx request.CTX, now time.Time, rawTeamId string, rawChannelId string, rawUserId string, rawFilename string, data []byte, extractContent bool) (*model.FileInfo, []byte, *model.AppError) {
filename := filepath.Base(rawFilename)
teamID := filepath.Base(rawTeamId)
channelID := filepath.Base(rawChannelId)
userID := filepath.Base(rawUserId)
info, err := getInfoForBytes(filename, bytes.NewReader(data), len(data))
if err != nil {
err.StatusCode = http.StatusBadRequest
return nil, data, err
}
if orientation, err := imaging.GetImageOrientation(bytes.NewReader(data), info.MimeType); err == nil &&
(orientation == imaging.RotatedCWMirrored ||
orientation == imaging.RotatedCCW ||
orientation == imaging.RotatedCCWMirrored ||
orientation == imaging.RotatedCW) {
info.Width, info.Height = info.Height, info.Width
} else if err != nil {
rctx.Logger().Warn("Failed to get image orientation", mlog.Err(err))
}
info.Id = model.NewId()
info.CreatorId = userID
info.CreateAt = now.UnixNano() / int64(time.Millisecond)
pathPrefix := now.Format("20060102") + "/teams/" + teamID + "/channels/" + channelID + "/users/" + userID + "/" + info.Id + "/"
Channel Bookmarks (#25449) * create ChannelBookmarks table * ChannelBookmark model * channel bookamrks Store layer * add GetBookmarksForAllChannelByIdSince * add channel bookmarks to test store * Add channel bookmarks to app layer * remove index for createAt in channel bookmarks migrations * remove createAt from select channel bookmark query and enable store delete bookmark test * update reponse of UpdateBookmark * rename db migration files * channel bookmarks store update sort order * channel bookmarks app layer update sort order * fix lint & tests * Fix lint and introduce util functions to insert / remove from slice * remove model etag * i18n * defer remove file info after test run * Fix tests passing the request context * fix migrations * fix TestRetry * Add bookmark permissions (#25560) * Adds channel bookmarks permissions * Fix linter * Remove unnecessary empty lines * Remove scss change as it's not necessary anymore * Fix mock store * Fix mock store and add role entry * Fix test * Adds cypress test and update permissions migration to update admin roles * Adds channel bookmarks roles to default admin roles * Adds bookmark permissions to default role permissions constant in webapp * Update mmctl test * Update permission test after normalising the roles * fix store tests * fix app layer tests * Add new bookmark endpoint (#25624) * Adds channel bookmarks api scaffold and create endpoint * Applies review comments to the API docs * Adds websocket test to create channel bookmark --------- Co-authored-by: Mattermost Build <build@mattermost.com> * MM-54426 exclude Channel Bookmarks files from data retention (#25656) * Augment channel APIs to include bookmarks (#25567) * update files docs for server 9.4 * Adds update channel bookmark endpoint (#25653) * Adds update channel bookmark sort order endpoint (#25686) * Adds update channel bookmark endpoint * Updates edit app method to return the right deleted bookmark and adds tests * Adds the update channel bookmark sort order endpoint * Fix repeated test after merge * Assign right permissions to each test * Update store and app layer to return specific errors and add tests * Adds delete channel bookmark endpoint (#25693) * Updates edit app method to return the right deleted bookmark and adds tests * Fix repeated test after merge * Updates edit app method to return the right deleted bookmark and adds tests * Adds delete channel bookmark endpoint * Adds list channel bookmarks endpoint (#25700) * Add channel moderation to bookmarks (#25716) * fix migrations index * fix getChannelsForTeamForUser * fix getChannelsForTeamForUser * fix bad merge client4 * fix file api with bookmark permission * add ChannelBookmarks feature flag * add missing translations * Set DB column for type as enum * use custom type for bookmark query using sqlx * use transaction when saving bookmark * return NewErrNotFound instead of Sql.ErrNoRows * use squirrel for IN query * add a limit of 1K for records in GetBookmarksForAllChannelByIdSince * UpdateSortOrder with one single query instead of multiple updates * fix shadow declaration * fix channel bookmarks permission string definition in admin console * fix another shadow declaration * Fix model conversion * add SplitSliceInChunks * remove include bookmarks in channels api * Cap amount of bookmarks per channel * add etag back to get channels * feedback review * update file info when replacing a bookmark file * return 501 not implemented when the license is not available * add detail message when getting channel member on bookmark api * start audit before permission check on create bookmark api * use require.Eventuallyf for testing WS events * remove unnecessary log in app layer * use require instead of assert to avoid panics * enforce limit when querying bookmarks since * prevent to create/update bookmark if file is already attached * fix lint * delete file when a bookmark is deleted * Dot allow to set a fileId and a url at the same time to a bookmark * fix query to delete a file that belongs to a bookmark * do not patch the bookmark type * Server side FeatureFlag check (#26145) * use ff in server, set ff to false * turn on FF for unit tests * defer unset FF for unit tests * turn ff on for testing * only allow attaching files that were uploaded for bookmark * Set feature flag off as default * fix lint * update email templates as PR failed * revert templates * force the assignment of ID when creating a bookmark * Fix unit tests --------- Co-authored-by: Miguel de la Cruz <miguel@mcrx.me> Co-authored-by: Mattermost Build <build@mattermost.com> Co-authored-by: Caleb Roseland <caleb@calebroseland.com> Co-authored-by: Scott Bishel <scott.bishel@mattermost.com>
2024-03-12 10:36:05 -04:00
if userID == model.BookmarkFileOwner {
pathPrefix = model.BookmarkFileOwner + "/teams/" + teamID + "/channels/" + channelID + "/" + info.Id + "/"
}
info.Path = pathPrefix + filename
if info.IsImage() && !info.IsSvg() {
if limitErr := checkImageResolutionLimit(info.Width, info.Height, *a.Config().FileSettings.MaxImageResolution); limitErr != nil {
2022-08-18 05:01:37 -04:00
err := model.NewAppError("uploadFile", "api.file.upload_file.large_image.app_error", map[string]any{"Filename": filename}, "", http.StatusBadRequest).Wrap(limitErr)
return nil, data, err
}
nameWithoutExtension := filename[:strings.LastIndex(filename, ".")]
info.PreviewPath = pathPrefix + nameWithoutExtension + "_preview." + getFileExtFromMimeType(info.MimeType)
info.ThumbnailPath = pathPrefix + nameWithoutExtension + "_thumb." + getFileExtFromMimeType(info.MimeType)
}
var rejectionError *model.AppError
pluginContext := pluginContext(rctx)
a.ch.RunMultiHook(func(hooks plugin.Hooks, _ *model.Manifest) bool {
var newBytes bytes.Buffer
replacementInfo, rejectionReason := hooks.FileWillBeUploaded(pluginContext, info, bytes.NewReader(data), &newBytes)
if rejectionReason != "" {
rejectionError = model.NewAppError("DoUploadFile", "File rejected by plugin. "+rejectionReason, nil, "", http.StatusBadRequest)
return false
}
if replacementInfo != nil {
info = replacementInfo
}
if newBytes.Len() != 0 {
data = newBytes.Bytes()
info.Size = int64(len(data))
}
return true
}, plugin.FileWillBeUploadedID)
if rejectionError != nil {
return nil, data, rejectionError
}
if _, err := a.WriteFile(bytes.NewReader(data), info.Path); err != nil {
return nil, data, err
}
if _, err := a.Srv().Store().FileInfo().Save(rctx, info); err != nil {
var appErr *model.AppError
switch {
case errors.As(err, &appErr):
return nil, data, appErr
default:
2022-08-18 05:01:37 -04:00
return nil, data, model.NewAppError("DoUploadFileExpectModification", "app.file_info.save.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
}
// The extra boolean extractContent is used to turn off extraction
// during the import process. It is unnecessary overhead during the import,
// and something we can do without.
if *a.Config().FileSettings.ExtractContent && extractContent {
infoCopy := *info
2022-09-15 04:08:16 -04:00
a.Srv().GoBuffered(func() {
err := a.ExtractContentFromFileInfo(rctx, &infoCopy)
if err != nil {
rctx.Logger().Error("Failed to extract file content", mlog.Err(err), mlog.String("fileInfoId", infoCopy.Id))
}
})
}
return info, data, nil
}
func (a *App) HandleImages(rctx request.CTX, previewPathList []string, thumbnailPathList []string, fileData [][]byte) {
wg := new(sync.WaitGroup)
for i := range fileData {
img, imgType, release, err := prepareImage(rctx, a.ch.imgDecoder, bytes.NewReader(fileData[i]))
if err != nil {
rctx.Logger().Debug("Failed to prepare image", mlog.Err(err))
continue
}
wg.Add(2)
go func(img image.Image, imgType, path string) {
defer wg.Done()
a.generateThumbnailImage(rctx, img, imgType, path)
}(img, imgType, thumbnailPathList[i])
go func(img image.Image, imgType, path string) {
defer wg.Done()
a.generatePreviewImage(rctx, img, imgType, path)
}(img, imgType, previewPathList[i])
wg.Wait()
release()
}
}
func prepareImage(rctx request.CTX, imgDecoder *imaging.Decoder, imgData io.ReadSeeker) (img image.Image, imgType string, release func(), err error) {
// Decode image bytes into Image object
img, imgType, release, err = imgDecoder.DecodeMemBounded(imgData)
if err != nil {
return nil, "", nil, fmt.Errorf("prepareImage: failed to decode image: %w", err)
}
if _, err = imgData.Seek(0, io.SeekStart); err != nil {
return nil, "", nil, fmt.Errorf("prepareImage: failed to seek image data: %w", err)
}
// Flip the image to be upright
orientation, err := imaging.GetImageOrientation(imgData, imgType)
if err != nil {
rctx.Logger().Debug("GetImageOrientation failed", mlog.Err(err))
}
img = imaging.MakeImageUpright(img, orientation)
return img, imgType, release, nil
}
func (a *App) generateThumbnailImage(rctx request.CTX, img image.Image, imgType, thumbnailPath string) {
var buf bytes.Buffer
thumb := imaging.GenerateThumbnail(img, imageThumbnailWidth, imageThumbnailHeight)
if imgType == "png" {
if err := a.ch.imgEncoder.EncodePNG(&buf, thumb); err != nil {
rctx.Logger().Error("Unable to encode image as png", mlog.String("path", thumbnailPath), mlog.Err(err))
return
}
} else {
if err := a.ch.imgEncoder.EncodeJPEG(&buf, thumb, jpegEncQuality); err != nil {
rctx.Logger().Error("Unable to encode image as jpeg", mlog.String("path", thumbnailPath), mlog.Err(err))
return
}
}
if _, err := a.WriteFile(&buf, thumbnailPath); err != nil {
rctx.Logger().Error("Unable to upload thumbnail", mlog.String("path", thumbnailPath), mlog.Err(err))
return
}
}
func (a *App) generatePreviewImage(rctx request.CTX, img image.Image, imgType, previewPath string) {
var buf bytes.Buffer
preview := imaging.GeneratePreview(img, imagePreviewWidth)
if imgType == "png" {
if err := a.ch.imgEncoder.EncodePNG(&buf, preview); err != nil {
rctx.Logger().Error("Unable to encode image as preview png", mlog.Err(err), mlog.String("path", previewPath))
return
}
} else {
if err := a.ch.imgEncoder.EncodeJPEG(&buf, preview, jpegEncQuality); err != nil {
rctx.Logger().Error("Unable to encode image as preview jpg", mlog.Err(err), mlog.String("path", previewPath))
return
}
}
if _, err := a.WriteFile(&buf, previewPath); err != nil {
rctx.Logger().Error("Unable to upload preview", mlog.Err(err), mlog.String("path", previewPath))
return
}
}
2020-10-02 04:14:57 -04:00
// generateMiniPreview updates mini preview if needed
// will save fileinfo with the preview added
func (a *App) generateMiniPreview(rctx request.CTX, fi *model.FileInfo) {
if fi.IsImage() && !fi.IsSvg() && fi.MiniPreview == nil {
file, appErr := a.FileReader(fi.Path)
if appErr != nil {
rctx.Logger().Debug("Error reading image file", mlog.Err(appErr))
2020-10-02 04:14:57 -04:00
return
}
defer file.Close()
img, _, release, err := prepareImage(rctx, a.ch.imgDecoder, file)
if err != nil {
rctx.Logger().Debug("generateMiniPreview: prepareImage failed", mlog.Err(err),
mlog.String("fileinfo_id", fi.Id), mlog.String("channel_id", fi.ChannelId),
mlog.String("creator_id", fi.CreatorId))
2020-10-02 04:14:57 -04:00
return
}
defer release()
var miniPreview []byte
if miniPreview, err = imaging.GenerateMiniPreviewImage(img,
miniPreviewImageWidth, miniPreviewImageHeight, jpegEncQuality); err != nil {
rctx.Logger().Info("Unable to generate mini preview image", mlog.Err(err))
} else {
fi.MiniPreview = &miniPreview
}
if _, err = a.Srv().Store().FileInfo().Upsert(rctx, fi); err != nil {
rctx.Logger().Debug("Creating mini preview failed", mlog.Err(err))
2020-10-02 04:14:57 -04:00
} else {
a.Srv().Store().FileInfo().InvalidateFileInfosForPostCache(fi.PostId, false)
2020-10-02 04:14:57 -04:00
}
}
}
func (a *App) generateMiniPreviewForInfos(rctx request.CTX, fileInfos []*model.FileInfo) {
2020-10-02 04:14:57 -04:00
wg := new(sync.WaitGroup)
wg.Add(len(fileInfos))
for _, fileInfo := range fileInfos {
go func(fi *model.FileInfo) {
defer wg.Done()
a.generateMiniPreview(rctx, fi)
2020-10-02 04:14:57 -04:00
}(fileInfo)
}
wg.Wait()
}
func (s *Server) getFileInfo(fileID string) (*model.FileInfo, *model.AppError) {
fileInfo, err := s.Store().FileInfo().Get(fileID)
if err != nil {
var nfErr *store.ErrNotFound
switch {
case errors.As(err, &nfErr):
2022-08-18 05:01:37 -04:00
return nil, model.NewAppError("GetFileInfo", "app.file_info.get.app_error", nil, "", http.StatusNotFound).Wrap(err)
default:
2022-08-18 05:01:37 -04:00
return nil, model.NewAppError("GetFileInfo", "app.file_info.get.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
}
return fileInfo, nil
}
func (a *App) GetFileInfo(rctx request.CTX, fileID string) (*model.FileInfo, *model.AppError) {
fileInfo, appErr := a.Srv().getFileInfo(fileID)
if appErr != nil {
return nil, appErr
}
firstInaccessibleFileTime, appErr := a.isInaccessibleFile(fileInfo)
if appErr != nil {
return nil, appErr
}
if firstInaccessibleFileTime > 0 {
return nil, model.NewAppError("GetFileInfo", "app.file.cloud.get.app_error", nil, "", http.StatusForbidden)
}
a.generateMiniPreview(rctx, fileInfo)
return fileInfo, appErr
}
func (a *App) SetFileSearchableContent(rctx request.CTX, fileID string, data string) *model.AppError {
fileInfo, appErr := a.Srv().getFileInfo(fileID)
if appErr != nil {
return appErr
}
err := a.Srv().Store().FileInfo().SetContent(rctx, fileInfo.Id, data)
if err != nil {
var nfErr *store.ErrNotFound
switch {
case errors.As(err, &nfErr):
return model.NewAppError("SetFileSearchableContent", "app.file_info.set_searchable_content.app_error", nil, "", http.StatusNotFound).Wrap(err)
default:
return model.NewAppError("SetFileSearchableContent", "app.file_info.set_searchable_content.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
}
return nil
}
func (a *App) GetFileInfos(rctx request.CTX, page, perPage int, opt *model.GetFileInfosOptions) ([]*model.FileInfo, *model.AppError) {
fileInfos, err := a.Srv().Store().FileInfo().GetWithOptions(page, perPage, opt)
if err != nil {
var invErr *store.ErrInvalidInput
var ltErr *store.ErrLimitExceeded
switch {
case errors.As(err, &invErr):
2022-08-18 05:01:37 -04:00
return nil, model.NewAppError("GetFileInfos", "app.file_info.get_with_options.app_error", nil, "", http.StatusBadRequest).Wrap(err)
case errors.As(err, &ltErr):
2022-08-18 05:01:37 -04:00
return nil, model.NewAppError("GetFileInfos", "app.file_info.get_with_options.app_error", nil, "", http.StatusBadRequest).Wrap(err)
default:
2022-08-18 05:01:37 -04:00
return nil, model.NewAppError("GetFileInfos", "app.file_info.get_with_options.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
}
filterOptions := filterFileOptions{}
if opt != nil && (opt.SortBy == "" || opt.SortBy == model.FileinfoSortByCreated) {
filterOptions.assumeSortedCreatedAt = true
}
fileInfos, _, appErr := a.getFilteredAccessibleFiles(fileInfos, filterOptions)
if appErr != nil {
return nil, appErr
}
a.generateMiniPreviewForInfos(rctx, fileInfos)
2020-10-02 04:14:57 -04:00
return fileInfos, nil
}
func (a *App) GetFile(rctx request.CTX, fileID string) ([]byte, *model.AppError) {
info, err := a.GetFileInfo(rctx, fileID)
if err != nil {
return nil, err
}
data, err := a.ReadFile(info.Path)
if err != nil {
return nil, err
}
return data, nil
}
func (a *App) CopyFileInfos(rctx request.CTX, userID string, fileIDs []string) ([]string, *model.AppError) {
var newFileIds []string
now := model.GetMillis()
for _, fileID := range fileIDs {
fileInfo, err := a.Srv().Store().FileInfo().Get(fileID)
if err != nil {
var nfErr *store.ErrNotFound
switch {
case errors.As(err, &nfErr):
2022-08-18 05:01:37 -04:00
return nil, model.NewAppError("CopyFileInfos", "app.file_info.get.app_error", nil, "", http.StatusNotFound).Wrap(err)
default:
2022-08-18 05:01:37 -04:00
return nil, model.NewAppError("CopyFileInfos", "app.file_info.get.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
}
fileInfo.Id = model.NewId()
fileInfo.CreatorId = userID
fileInfo.CreateAt = now
fileInfo.UpdateAt = now
fileInfo.PostId = ""
fileInfo.ChannelId = ""
if _, err := a.Srv().Store().FileInfo().Save(rctx, fileInfo); err != nil {
var appErr *model.AppError
switch {
case errors.As(err, &appErr):
return nil, appErr
default:
2022-08-18 05:01:37 -04:00
return nil, model.NewAppError("CopyFileInfos", "app.file_info.save.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
}
newFileIds = append(newFileIds, fileInfo.Id)
}
return newFileIds, nil
}
// WriteZipFile writes a zip file to the provided writer from the provided file data.
func (a *App) WriteZipFile(w io.Writer, fileDatas []model.FileData) error {
zipWriter := zip.NewWriter(w)
// Populate zip file with file data
err := populateZipfile(zipWriter, fileDatas)
if err != nil {
return fmt.Errorf("failed to populate zip file: %w", err)
}
return nil
}
// This is a implementation of Go's example of writing files to zip (with slight modification)
// https://golang.org/src/archive/zip/example_test.go
func populateZipfile(w *zip.Writer, fileDatas []model.FileData) error {
defer w.Close()
for _, fd := range fileDatas {
f, err := w.CreateHeader(&zip.FileHeader{
Name: fd.Filename,
Method: zip.Deflate,
Modified: time.Now(),
})
if err != nil {
return err
}
_, err = f.Write(fd.Body)
if err != nil {
return err
}
}
return nil
}
func (a *App) SearchFilesInTeamForUser(rctx request.CTX, terms string, userId string, teamId string, isOrSearch bool, includeDeletedChannels bool, timeZoneOffset int, page, perPage int) (*model.FileInfoList, bool, *model.AppError) {
paramsList := model.ParseSearchParams(strings.TrimSpace(terms), timeZoneOffset)
if !*a.Config().ServiceSettings.EnableFileSearch {
return nil, false, model.NewAppError("SearchFilesInTeamForUser", "store.sql_file_info.search.disabled", nil, fmt.Sprintf("teamId=%v userId=%v", teamId, userId), http.StatusNotImplemented)
}
finalParamsList := []*model.SearchParams{}
for _, params := range paramsList {
params.OrTerms = isOrSearch
MM-63240: Always allow viewing archived channels (#32162) * server: allow access to channel bookmarks in an archived channel * server: allow access to posts in archived channels * server: allow accessing channel members for archived channels * server: allow autocompleting/searching archived channels * server: allow access to files from archived channels * server: fix access issue on database error * server: allow access to archived channels * server: remove TeamSettings.ExperimentalViewArchivedChannels from telemetry * server: remove ExperimentalViewArchivedChannels from client config * webapp: simplify delete channel * webapp: simplify channel settings modal * webapp: do not redirect away from archived channel * webapp: rhs, always search posts from archived channels * webapp: switch channels, always support archived channels * webapp: search channel provider, always support archived channels * webapp: browse channels, always support archived channels * webapp, search results? fixup? * webapp, confusing type issue * webapp: unarchive, no need to report view archived * webapp: command test, no need for ExperimentalViewArchivedChannels in config * webapp: remove ExperimentalViewArchivedChannels from system console * webapp: redux, do not delete posts, also fix LEAVE_CHANNEL * update e2e tests * server: fail startup if ExperimentalViewArchivedChannels is not enabled * extract i18n * updated snapshots * update tests * simplify posts reducer * updated tests * additional e2e tests * Fix locale consistency in Jest tests Added consistent locale environment variables (LC_ALL=en_US.UTF-8 LANG=en_US.UTF-8) to all Jest test scripts to prevent locale-dependent date formatting differences across development environments. This resolves snapshot test failures where DateTime.toLocaleString() would produce different date formats on different systems (e.g., "6/8/2025" vs "08/06/2025" vs "2025-06-08"). Updated test scripts: - test, test:watch, test:updatesnapshot, test:debug, test-ci Updated snapshot to consistent en_US format. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * Remove includeArchivedChannels parameter from GetMemberForPost * Remove unnecessary includeDeleted variable assignments * Deprecate ExperimentalViewArchivedChannels config field --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Mattermost Build <build@mattermost.com>
2025-08-15 12:50:20 -04:00
params.IncludeDeletedChannels = includeDeletedChannels
// Don't allow users to search for "*"
if params.Terms != "*" {
// Convert channel names to channel IDs
params.InChannels = a.convertChannelNamesToChannelIds(rctx, params.InChannels, userId, teamId, includeDeletedChannels)
params.ExcludedChannels = a.convertChannelNamesToChannelIds(rctx, params.ExcludedChannels, userId, teamId, includeDeletedChannels)
// Convert usernames to user IDs
params.FromUsers = a.convertUserNameToUserIds(rctx, params.FromUsers)
params.ExcludedUsers = a.convertUserNameToUserIds(rctx, params.ExcludedUsers)
finalParamsList = append(finalParamsList, params)
}
}
// If the processed search params are empty, return empty search results.
if len(finalParamsList) == 0 {
return model.NewFileInfoList(), true, nil
}
fileInfoSearchResults, nErr := a.Srv().Store().FileInfo().Search(rctx, finalParamsList, userId, teamId, page, perPage)
if nErr != nil {
var appErr *model.AppError
switch {
case errors.As(nErr, &appErr):
return nil, false, appErr
default:
return nil, false, model.NewAppError("SearchFilesInTeamForUser", "app.post.search.app_error", nil, "", http.StatusInternalServerError).Wrap(nErr)
}
}
if appErr := a.filterInaccessibleFiles(fileInfoSearchResults, filterFileOptions{assumeSortedCreatedAt: true}); appErr != nil {
return nil, false, appErr
}
allFilesHaveMembership, appErr := a.FilterFilesByChannelPermissions(rctx, fileInfoSearchResults, userId)
if appErr != nil {
return nil, false, appErr
}
return fileInfoSearchResults, allFilesHaveMembership, nil
}
func (a *App) FilterFilesByChannelPermissions(rctx request.CTX, fileList *model.FileInfoList, userID string) (bool, *model.AppError) {
if fileList == nil || fileList.FileInfos == nil || len(fileList.FileInfos) == 0 {
return true, nil // On an empty file list, we consider all files as having membership
}
channels := make(map[string]*model.Channel)
for _, fileInfo := range fileList.FileInfos {
if fileInfo.ChannelId != "" {
channels[fileInfo.ChannelId] = nil
}
}
if len(channels) > 0 {
channelIDs := slices.Collect(maps.Keys(channels))
channelList, err := a.GetChannels(rctx, channelIDs)
if err != nil && err.StatusCode != http.StatusNotFound {
return false, err
}
for _, channel := range channelList {
channels[channel.Id] = channel
}
}
channelReadPermission := make(map[string]bool)
filteredFiles := make(map[string]*model.FileInfo)
filteredOrder := []string{}
allFilesHaveMembership := true
for _, fileID := range fileList.Order {
fileInfo, ok := fileList.FileInfos[fileID]
if !ok {
continue
}
if _, ok := channelReadPermission[fileInfo.ChannelId]; !ok {
channel := channels[fileInfo.ChannelId]
allowed := false
isMember := true
if channel != nil {
allowed, isMember = a.HasPermissionToReadChannel(rctx, userID, channel)
}
channelReadPermission[fileInfo.ChannelId] = allowed
if allowed {
allFilesHaveMembership = allFilesHaveMembership && isMember
}
}
if channelReadPermission[fileInfo.ChannelId] {
filteredFiles[fileID] = fileInfo
filteredOrder = append(filteredOrder, fileID)
}
}
fileList.FileInfos = filteredFiles
fileList.Order = filteredOrder
return allFilesHaveMembership, nil
}
func (a *App) ExtractContentFromFileInfo(rctx request.CTX, fileInfo *model.FileInfo) error {
// We don't process images.
if fileInfo.IsImage() {
return nil
}
file, aerr := a.FileReader(fileInfo.Path)
if aerr != nil {
return errors.Wrap(aerr, "failed to open file for extract file content")
}
defer file.Close()
text, err := docextractor.Extract(rctx.Logger(), fileInfo.Name, file, docextractor.ExtractSettings{
ArchiveRecursion: *a.Config().FileSettings.ArchiveRecursion,
MaxFileSize: *a.Config().FileSettings.MaxFileSize,
})
if err != nil {
return errors.Wrap(err, "failed to extract file content")
}
if text != "" {
if len(text) > maxContentExtractionSize {
text = text[0:maxContentExtractionSize]
}
if storeErr := a.Srv().Store().FileInfo().SetContent(rctx, fileInfo.Id, text); storeErr != nil {
return errors.Wrap(storeErr, "failed to save the extracted file content")
}
reloadFileInfo, storeErr := a.Srv().Store().FileInfo().Get(fileInfo.Id)
if storeErr != nil {
rctx.Logger().Warn("Failed to invalidate the fileInfo cache.", mlog.Err(storeErr), mlog.String("file_info_id", fileInfo.Id))
} else {
a.Srv().Store().FileInfo().InvalidateFileInfosForPostCache(reloadFileInfo.PostId, false)
}
}
return nil
}
// GetLastAccessibleFileTime returns CreateAt time(from cache) of the last accessible post as per the cloud limit
func (a *App) GetLastAccessibleFileTime() (int64, *model.AppError) {
license := a.Srv().License()
if !license.IsCloud() {
return 0, nil
}
system, err := a.Srv().Store().System().GetByName(model.SystemLastAccessibleFileTime)
if err != nil {
var nfErr *store.ErrNotFound
switch {
case errors.As(err, &nfErr):
// All files are accessible
return 0, nil
default:
return 0, model.NewAppError("GetLastAccessibleFileTime", "app.system.get_by_name.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
}
lastAccessibleFileTime, err := strconv.ParseInt(system.Value, 10, 64)
if err != nil {
return 0, model.NewAppError("GetLastAccessibleFileTime", "common.parse_error_int64", map[string]any{"Value": system.Value}, "", http.StatusInternalServerError).Wrap(err)
}
return lastAccessibleFileTime, nil
}
// ComputeLastAccessibleFileTime updates cache with CreateAt time of the last accessible file as per the cloud plan's limit.
// Use GetLastAccessibleFileTime() to access the result.
func (a *App) ComputeLastAccessibleFileTime() error {
limit, appErr := a.getCloudFilesSizeLimit()
if appErr != nil {
return appErr
}
if limit == 0 {
// All files are accessible - we must check if a previous value was set so we can clear it
systemValue, err := a.Srv().Store().System().GetByName(model.SystemLastAccessibleFileTime)
if err != nil {
var nfErr *store.ErrNotFound
switch {
case errors.As(err, &nfErr):
// All files are already accessible
return nil
default:
return model.NewAppError("ComputeLastAccessibleFileTime", "app.system.get_by_name.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
}
if systemValue != nil {
// Previous value was set, so we must clear it
if _, err := a.Srv().Store().System().PermanentDeleteByName(model.SystemLastAccessibleFileTime); err != nil {
return model.NewAppError("ComputeLastAccessibleFileTime", "app.system.permanent_delete_by_name.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
}
return nil
}
createdAt, err := a.Srv().GetStore().FileInfo().GetUptoNSizeFileTime(limit)
if err != nil {
var nfErr *store.ErrNotFound
if !errors.As(err, &nfErr) {
return model.NewAppError("ComputeLastAccessibleFileTime", "app.last_accessible_file.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
}
// Update Cache
err = a.Srv().Store().System().SaveOrUpdate(&model.System{
Name: model.SystemLastAccessibleFileTime,
Value: strconv.FormatInt(createdAt, 10),
})
if err != nil {
return model.NewAppError("ComputeLastAccessibleFileTime", "app.system.save.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
return nil
}
// getCloudFilesSizeLimit returns size in bytes
func (a *App) getCloudFilesSizeLimit() (int64, *model.AppError) {
license := a.Srv().License()
if license == nil || !license.IsCloud() {
return 0, nil
}
// limits is in bits
limits, err := a.Cloud().GetCloudLimits("")
if err != nil {
return 0, model.NewAppError("getCloudFilesSizeLimit", "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
if limits == nil || limits.Files == nil || limits.Files.TotalStorage == nil {
// Cloud limit is not applicable
return 0, nil
}
return int64(math.Ceil(float64(*limits.Files.TotalStorage) / 8)), nil
}
func getFileExtFromMimeType(mimeType string) string {
if mimeType == "image/png" {
return "png"
}
return "jpg"
}
func (a *App) PermanentDeleteFilesByPost(rctx request.CTX, postID string) *model.AppError {
fileInfos, err := a.Srv().Store().FileInfo().GetForPost(postID, false, true, true)
if err != nil {
return model.NewAppError("PermanentDeleteFilesByPost", "app.file_info.get_by_post_id.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
if len(fileInfos) == 0 {
rctx.Logger().Debug("No files found for post", mlog.String("post_id", postID))
return nil
}
a.RemoveFilesFromFileStore(rctx, fileInfos)
err = a.Srv().Store().FileInfo().PermanentDeleteForPost(rctx, postID)
if err != nil {
return model.NewAppError("PermanentDeleteFilesByPost", "app.file_info.permanent_delete_for_post.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
}
a.Srv().Store().FileInfo().InvalidateFileInfosForPostCache(postID, true)
a.Srv().Store().FileInfo().InvalidateFileInfosForPostCache(postID, false)
return nil
}
func (a *App) RemoveFilesFromFileStore(rctx request.CTX, fileInfos []*model.FileInfo) {
for _, info := range fileInfos {
a.RemoveFileFromFileStore(rctx, info.Path)
if info.PreviewPath != "" {
a.RemoveFileFromFileStore(rctx, info.PreviewPath)
}
if info.ThumbnailPath != "" {
a.RemoveFileFromFileStore(rctx, info.ThumbnailPath)
}
}
}
func (a *App) RemoveFileFromFileStore(rctx request.CTX, path string) {
res, appErr := a.FileExists(path)
if appErr != nil {
rctx.Logger().Warn(
"Error checking existence of file",
mlog.String("path", path),
mlog.Err(appErr),
)
return
}
if !res {
rctx.Logger().Warn("File not found", mlog.String("path", path))
return
}
appErr = a.RemoveFile(path)
if appErr != nil {
rctx.Logger().Warn(
"Unable to remove file",
mlog.String("path", path),
mlog.Err(appErr),
)
return
}
}
feat: pluginapi: filewillbedownloaded / sendtoastmessage (#34596) * feat: filewillbedonwloaded hook * feat: error popup * chore: make generated pluginapi * tests * feat: different errors for different download types * feat: allow toast positions * fix: avoid using deprecated i18n function * feat: add plugin API to show toasts * feat: downloadType parameter * tests: updated tests * chore: make check-style * chore: i18n * chore: missing fields in tests * chore: sorted i18n for webapp * chore: run mmjstool * test: fixed webapp tests with new changes * test: missing mocks * fix: ensure one-file attachments (previews) are handler properly as thumbnails * chore: lint * test: added new logic to tests * chore: lint * Add SendToastMessage API and FileWillBeDownloaded hook - Introduced SendToastMessage method for sending toast notifications to users with customizable options. - Added FileWillBeDownloaded hook to handle file download requests, allowing plugins to control access to files. - Updated related types and constants for file download handling. - Enhanced PluginSettings to include HookTimeoutSeconds for better timeout management. * Update webapp/channels/src/components/single_image_view/single_image_view.tsx Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * chore: copilot reviews * test: head requests * chore: linted the webapp * tests: fixed path * test: fixed mocked args * allow sending message to a connection directly * fix: hook thread safety * chore: formatting * chore: remove configuration from system console * chore: release version * chore: update signature * chore: update release version * chore: addressed comments * fix: update file rejection handling to use 403 Forbidden status and include rejection reason header * Fix nil pointer panic in runFileWillBeDownloadedHook The atomic.Value in runFileWillBeDownloadedHook can be nil if no plugins implement the FileWillBeDownloaded hook. This causes a panic when trying to assert the nil interface to string. This fix adds a nil check before the type assertion, defaulting to an empty string (which allows the download) when no hooks have run. Fixes: - TestUploadDataMultipart/success panic - TestUploadDataMultipart/resume_success panic * test: move the logout test last * chore: restored accidential deletion * chore: lint * chore: make generated * refactor: move websocket events to new package * chore: go vet * chore: missing mock * chore: revert incorrect fmt * chore: import ordering * chore: npm i18n-extract * chore: update constants.tsx from master * chore: make i18n-extract * revert: conflict merge * fix: add missing isFileRejected prop to SingleImageView tests * fix: mock fetch in SingleImageView tests for async thumbnail check The component now performs an async fetch to check thumbnail availability before rendering. Tests need to mock fetch and use waitFor to handle the async state updates. * refactor: move hook logic to app layer * chore: update version to 11.5 * Scope file download rejection toast to the requesting connection Thread the Connection-Id header through RunFileWillBeDownloadedHook and sendFileDownloadRejectedEvent so the WebSocket event is sent only to the connection that initiated the download, instead of all connections for the user. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-16 11:10:39 -05:00
// sendFileDownloadRejectedEvent sends a websocket event to notify the user that their file download was rejected.
// When connectionID is provided, the event is only sent to that specific connection.
func (a *App) sendFileDownloadRejectedEvent(info *model.FileInfo, userID string, connectionID string, rejectionReason string, downloadType model.FileDownloadType) {
if userID == "" {
a.Log().Debug("Skipping websocket event for public file download rejection")
return
}
message := model.NewWebSocketEvent(model.WebsocketEventFileDownloadRejected, "", info.ChannelId, userID, nil, "")
if connectionID != "" {
message.GetBroadcast().ConnectionId = connectionID
}
message.Add("file_id", info.Id)
message.Add("file_name", info.Name)
message.Add("rejection_reason", rejectionReason)
message.Add("channel_id", info.ChannelId)
message.Add("post_id", info.PostId)
message.Add("download_type", string(downloadType))
a.Publish(message)
}
// RunFileWillBeDownloadedHook executes the FileWillBeDownloaded hook with a timeout.
// Returns empty string to allow download, or a rejection reason to block it.
func (a *App) RunFileWillBeDownloadedHook(rctx request.CTX, fileInfo *model.FileInfo, userID string, connectionID string, downloadType model.FileDownloadType) string {
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(model.PluginSettingsDefaultHookTimeoutSeconds)*time.Second)
defer cancel()
var rejectionReason atomic.Value
done := make(chan struct{})
pluginCtx := pluginContext(rctx)
a.Srv().Go(func() {
defer close(done)
a.ch.RunMultiHook(func(hooks plugin.Hooks, _ *model.Manifest) bool {
rejectionReasonFromHook := hooks.FileWillBeDownloaded(pluginCtx, fileInfo, userID, downloadType)
rejectionReason.Store(rejectionReasonFromHook)
a.Log().Debug("FileWillBeDownloaded hook called",
mlog.String("file_id", fileInfo.Id),
mlog.String("user_id", userID),
mlog.String("download_type", string(downloadType)),
mlog.String("rejection_reason", rejectionReasonFromHook))
return rejectionReasonFromHook == ""
}, plugin.FileWillBeDownloadedID)
})
select {
case <-done:
rejectionReasonString := ""
if loaded := rejectionReason.Load(); loaded != nil {
rejectionReasonString = loaded.(string)
}
if rejectionReasonString != "" {
a.sendFileDownloadRejectedEvent(fileInfo, userID, connectionID, rejectionReasonString, downloadType)
}
return rejectionReasonString
case <-ctx.Done():
timeoutMessage := rctx.T("api.file.get_file.plugin_hook_timeout")
a.Log().Warn("FileWillBeDownloaded hook timed out, blocking download",
mlog.String("file_id", fileInfo.Id),
mlog.String("user_id", userID))
a.sendFileDownloadRejectedEvent(fileInfo, userID, connectionID, timeoutMessage, downloadType)
return timeoutMessage
}
}