status-im/status-go

View on GitHub
protocol/messenger_sync_settings.go

Summary

Maintainability
A
0 mins
Test Coverage
D
69%
package protocol

import (
    "context"
    "encoding/json"

    "go.uber.org/zap"

    gocommon "github.com/status-im/status-go/common"
    "github.com/status-im/status-go/multiaccounts/errors"
    "github.com/status-im/status-go/multiaccounts/settings"
    "github.com/status-im/status-go/protocol/common"
    "github.com/status-im/status-go/protocol/protobuf"
)

// syncSettings syncs all settings that are syncable
func (m *Messenger) prepareSyncSettingsMessages(currentClock uint64, prepareForBackup bool) (resultRaw []*common.RawMessage, resultSync []*protobuf.SyncSetting, errors []error) {
    s, err := m.settings.GetSettings()
    if err != nil {
        errors = append(errors, err)
        return
    }

    logger := m.logger.Named("prepareSyncSettings")
    // Do not use the network clock, use the db value
    _, chat := m.getLastClockWithRelatedChat()

    for _, sf := range settings.SettingFieldRegister {
        if sf.CanSync(settings.FromStruct) {
            // DisplayName is backed up via `protobuf.BackedUpProfile` message.
            if prepareForBackup && sf.SyncProtobufFactory().SyncSettingProtobufType() == protobuf.SyncSetting_DISPLAY_NAME {
                continue
            }

            // Pull clock from the db
            clock, err := m.settings.GetSettingLastSynced(sf)
            if err != nil {
                logger.Error("m.settings.GetSettingLastSynced", zap.Error(err), zap.Any("SettingField", sf))
                errors = append(errors, err)
                return
            }
            if clock == 0 {
                clock = currentClock
            }

            // Build protobuf
            rm, sm, err := sf.SyncProtobufFactory().FromStruct()(s, clock, chat.ID)
            if err != nil {
                // Collect errors to give other sync messages a chance to send
                logger.Error("SyncProtobufFactory.Struct", zap.Error(err))
                errors = append(errors, err)
            }

            resultRaw = append(resultRaw, rm)
            resultSync = append(resultSync, sm)
        }
    }
    return
}

func (m *Messenger) syncSettings(rawMessageHandler RawMessageHandler) error {
    logger := m.logger.Named("syncSettings")

    clock, _ := m.getLastClockWithRelatedChat()
    rawMessages, _, errors := m.prepareSyncSettingsMessages(clock, false)

    if len(errors) != 0 {
        // return just the first error, the others have been logged
        return errors[0]
    }

    for _, rm := range rawMessages {
        _, err := rawMessageHandler(context.Background(), *rm)
        if err != nil {
            logger.Error("dispatchMessage", zap.Error(err))
            return err
        }
        logger.Debug("dispatchMessage success", zap.Any("rm", rm))
    }

    return nil
}

// extractSyncSetting parses incoming *protobuf.SyncSetting and stores the setting data if needed
func (m *Messenger) extractAndSaveSyncSetting(syncSetting *protobuf.SyncSetting) (*settings.SyncSettingField, error) {
    sf, err := settings.GetFieldFromProtobufType(syncSetting.Type)
    if err != nil {
        m.logger.Error(
            "extractSyncSetting - settings.GetFieldFromProtobufType",
            zap.Error(err),
            zap.Any("syncSetting", syncSetting),
        )
        return nil, err
    }

    spf := sf.SyncProtobufFactory()
    if spf == nil {
        m.logger.Warn("extractSyncSetting - received protobuf for setting with no SyncProtobufFactory", zap.Any("SettingField", sf))
        return nil, nil
    }
    if spf.Inactive() {
        m.logger.Warn("extractSyncSetting - received protobuf for inactive sync setting", zap.Any("SettingField", sf))
        return nil, nil
    }

    value := spf.ExtractValueFromProtobuf()(syncSetting)

    err = m.settings.SaveSyncSetting(sf, value, syncSetting.Clock)
    if err == errors.ErrNewClockOlderThanCurrent {
        m.logger.Info("extractSyncSetting - SaveSyncSetting :", zap.Error(err))
        return nil, nil
    }
    if err != nil {
        return nil, err
    }

    if v, ok := value.([]byte); ok {
        value = json.RawMessage(v)
    }

    return &settings.SyncSettingField{SettingField: sf, Value: value}, nil
}

// startSyncSettingsLoop watches the m.settings.SyncQueue and sends a sync message in response to a settings update
func (m *Messenger) startSyncSettingsLoop() {
    go func() {
        defer gocommon.LogOnPanic()
        logger := m.logger.Named("SyncSettingsLoop")

        for {
            select {
            case s := <-m.settings.GetSyncQueue():
                if s.CanSync(settings.FromInterface) {
                    logger.Debug("setting for sync received from settings.SyncQueue")

                    clock, chat := m.getLastClockWithRelatedChat()

                    // Only the messenger has access to the clock, so set the settings sync clock here.
                    err := m.settings.SetSettingLastSynced(s.SettingField, clock)
                    if err != nil {
                        logger.Error("m.settings.SetSettingLastSynced", zap.Error(err))
                        break
                    }
                    rm, _, err := s.SyncProtobufFactory().FromInterface()(s.Value, clock, chat.ID)
                    if err != nil {
                        logger.Error("SyncProtobufFactory().FromInterface", zap.Error(err), zap.Any("SyncSettingField", s))
                        break
                    }

                    _, err = m.dispatchMessage(context.Background(), *rm)
                    if err != nil {
                        logger.Error("dispatchMessage", zap.Error(err))
                        break
                    }

                    logger.Debug("message dispatched")
                }
            case <-m.quit:
                return
            }
        }
    }()
}

func (m *Messenger) startSettingsChangesLoop() {
    channel := m.settings.SubscribeToChanges()
    go func() {
        defer gocommon.LogOnPanic()
        for {
            select {
            case s := <-channel:
                switch s.GetReactName() {
                case settings.DisplayName.GetReactName():
                    m.selfContact.DisplayName = s.Value.(string)
                    m.publishSelfContactSubscriptions(&SelfContactChangeEvent{DisplayNameChanged: true})
                case settings.PreferredName.GetReactName():
                    m.selfContact.EnsName = s.Value.(string)
                    m.publishSelfContactSubscriptions(&SelfContactChangeEvent{PreferredNameChanged: true})
                case settings.Bio.GetReactName():
                    m.selfContact.Bio = s.Value.(string)
                    m.publishSelfContactSubscriptions(&SelfContactChangeEvent{BioChanged: true})
                }
            case <-m.quit:
                return
            }
        }
    }()
}