Previously, for every config change, we would re-initialize plugins irrespective of whether there would be any actual plugin related changed or not. Now, we compute the difference between previous and new configs and check whether there has been any changes to plugin related configuration, and re-init plugins only if that's true. This improves performance as plugin re-initialization is quite costly. https://community-daily.mattermost.com/boards/workspace/zyoahc9uapdn3xdptac6jb69ic/285b80a3-257d-41f6-8cf4-ed80ca9d92e5/495cdb4d-c13a-4992-8eb9-80cfee2819a4/bed59298-27da-4b74-8be5-cdeb4c50c3e2 ```release-note NONE ```
182 строки
5.1 KiB
Go
182 строки
5.1 KiB
Go
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
package app
|
|
|
|
import (
|
|
"runtime"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
|
|
"github.com/mattermost/mattermost-server/v6/app/imaging"
|
|
"github.com/mattermost/mattermost-server/v6/app/request"
|
|
"github.com/mattermost/mattermost-server/v6/config"
|
|
"github.com/mattermost/mattermost-server/v6/einterfaces"
|
|
"github.com/mattermost/mattermost-server/v6/model"
|
|
"github.com/mattermost/mattermost-server/v6/plugin"
|
|
"github.com/mattermost/mattermost-server/v6/services/imageproxy"
|
|
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
// Channels contains all channels related state.
|
|
type Channels struct {
|
|
srv *Server
|
|
|
|
postActionCookieSecret []byte
|
|
|
|
pluginCommandsLock sync.RWMutex
|
|
pluginCommands []*PluginCommand
|
|
pluginsLock sync.RWMutex
|
|
pluginsEnvironment *plugin.Environment
|
|
pluginConfigListenerID string
|
|
|
|
imageProxy *imageproxy.ImageProxy
|
|
|
|
asymmetricSigningKey atomic.Value
|
|
clientConfig atomic.Value
|
|
clientConfigHash atomic.Value
|
|
limitedClientConfig atomic.Value
|
|
|
|
// cached counts that are used during notice condition validation
|
|
cachedPostCount int64
|
|
cachedUserCount int64
|
|
cachedDBMSVersion string
|
|
// previously fetched notices
|
|
cachedNotices model.ProductNotices
|
|
|
|
AccountMigration einterfaces.AccountMigrationInterface
|
|
Compliance einterfaces.ComplianceInterface
|
|
DataRetention einterfaces.DataRetentionInterface
|
|
MessageExport einterfaces.MessageExportInterface
|
|
|
|
// These are used to prevent concurrent upload requests
|
|
// for a given upload session which could cause inconsistencies
|
|
// and data corruption.
|
|
uploadLockMapMut sync.Mutex
|
|
uploadLockMap map[string]bool
|
|
|
|
imgDecoder *imaging.Decoder
|
|
imgEncoder *imaging.Encoder
|
|
|
|
dndTaskMut sync.Mutex
|
|
dndTask *model.ScheduledTask
|
|
}
|
|
|
|
func init() {
|
|
RegisterProduct("channels", func(s *Server) (Product, error) {
|
|
return NewChannels(s)
|
|
})
|
|
}
|
|
|
|
func NewChannels(s *Server) (*Channels, error) {
|
|
ch := &Channels{
|
|
srv: s,
|
|
imageProxy: imageproxy.MakeImageProxy(s, s.httpService, s.Log),
|
|
uploadLockMap: map[string]bool{},
|
|
}
|
|
// We are passing a partially filled Channels struct so that the enterprise
|
|
// methods can have access to app methods.
|
|
// Otherwise, passing server would mean it has to call s.Channels(),
|
|
// which would be nil at this point.
|
|
if complianceInterface != nil {
|
|
ch.Compliance = complianceInterface(New(ServerConnector(ch)))
|
|
}
|
|
if messageExportInterface != nil {
|
|
ch.MessageExport = messageExportInterface(New(ServerConnector(ch)))
|
|
}
|
|
if dataRetentionInterface != nil {
|
|
ch.DataRetention = dataRetentionInterface(New(ServerConnector(ch)))
|
|
}
|
|
if accountMigrationInterface != nil {
|
|
ch.AccountMigration = accountMigrationInterface(New(ServerConnector(ch)))
|
|
}
|
|
|
|
var imgErr error
|
|
ch.imgDecoder, imgErr = imaging.NewDecoder(imaging.DecoderOptions{
|
|
ConcurrencyLevel: runtime.NumCPU(),
|
|
})
|
|
if imgErr != nil {
|
|
return nil, errors.Wrap(imgErr, "failed to create image decoder")
|
|
}
|
|
ch.imgEncoder, imgErr = imaging.NewEncoder(imaging.EncoderOptions{
|
|
ConcurrencyLevel: runtime.NumCPU(),
|
|
})
|
|
if imgErr != nil {
|
|
return nil, errors.Wrap(imgErr, "failed to create image encoder")
|
|
}
|
|
|
|
// Setup routes.
|
|
pluginsRoute := ch.srv.Router.PathPrefix("/plugins/{plugin_id:[A-Za-z0-9\\_\\-\\.]+}").Subrouter()
|
|
pluginsRoute.HandleFunc("", ch.ServePluginRequest)
|
|
pluginsRoute.HandleFunc("/public/{public_file:.*}", ch.ServePluginPublicRequest)
|
|
pluginsRoute.HandleFunc("/{anything:.*}", ch.ServePluginRequest)
|
|
|
|
return ch, nil
|
|
}
|
|
|
|
func (ch *Channels) Start() error {
|
|
// Start plugins
|
|
ctx := request.EmptyContext()
|
|
ch.initPlugins(ctx, *ch.srv.Config().PluginSettings.Directory, *ch.srv.Config().PluginSettings.ClientDirectory)
|
|
|
|
ch.AddConfigListener(func(prevCfg, cfg *model.Config) {
|
|
// We compute the difference between configs
|
|
// to ensure we don't re-init plugins unnecessarily.
|
|
diffs, err := config.Diff(prevCfg, cfg)
|
|
if err != nil {
|
|
mlog.Warn("Error in comparing configs", mlog.Err(err))
|
|
return
|
|
}
|
|
|
|
hasDiff := false
|
|
// TODO: This could be a method on ConfigDiffs itself
|
|
for _, diff := range diffs {
|
|
if strings.HasPrefix(diff.Path, "PluginSettings.") {
|
|
hasDiff = true
|
|
break
|
|
}
|
|
}
|
|
|
|
// Do only if some plugin related settings has changed.
|
|
if hasDiff {
|
|
if *cfg.PluginSettings.Enable {
|
|
ch.initPlugins(ctx, *cfg.PluginSettings.Directory, *ch.srv.Config().PluginSettings.ClientDirectory)
|
|
} else {
|
|
ch.ShutDownPlugins()
|
|
}
|
|
}
|
|
|
|
})
|
|
|
|
if err := ch.ensureAsymmetricSigningKey(); err != nil {
|
|
return errors.Wrapf(err, "unable to ensure asymmetric signing key")
|
|
}
|
|
|
|
if err := ch.ensurePostActionCookieSecret(); err != nil {
|
|
return errors.Wrapf(err, "unable to ensure PostAction cookie secret")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (ch *Channels) Stop() error {
|
|
ch.ShutDownPlugins()
|
|
|
|
ch.dndTaskMut.Lock()
|
|
if ch.dndTask != nil {
|
|
ch.dndTask.Cancel()
|
|
}
|
|
ch.dndTaskMut.Unlock()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (ch *Channels) AddConfigListener(listener func(*model.Config, *model.Config)) string {
|
|
return ch.srv.AddConfigListener(listener)
|
|
}
|
|
|
|
func (ch *Channels) RemoveConfigListener(id string) {
|
|
ch.srv.RemoveConfigListener(id)
|
|
}
|