github.com/mutagen-io/mutagen@v0.18.0-rc1/pkg/synchronization/configuration.go (about)

     1  package synchronization
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  
     7  	"github.com/mutagen-io/mutagen/pkg/comparison"
     8  	"github.com/mutagen-io/mutagen/pkg/filesystem"
     9  	"github.com/mutagen-io/mutagen/pkg/synchronization/compression"
    10  	"github.com/mutagen-io/mutagen/pkg/synchronization/core"
    11  	"github.com/mutagen-io/mutagen/pkg/synchronization/hashing"
    12  )
    13  
    14  // EnsureValid ensures that Configuration's invariants are respected. The
    15  // validation of the configuration depends on whether or not it is
    16  // endpoint-specific.
    17  func (c *Configuration) EnsureValid(endpointSpecific bool) error {
    18  	// A nil configuration is not considered valid.
    19  	if c == nil {
    20  		return errors.New("nil configuration")
    21  	}
    22  
    23  	// Validate the synchronization mode.
    24  	if endpointSpecific {
    25  		if !c.SynchronizationMode.IsDefault() {
    26  			return errors.New("synchronization mode cannot be specified on an endpoint-specific basis")
    27  		}
    28  	} else {
    29  		if !(c.SynchronizationMode.IsDefault() || c.SynchronizationMode.Supported()) {
    30  			return errors.New("unknown or unsupported synchronization mode")
    31  		}
    32  	}
    33  
    34  	// Verify that the hashing algorithm is unspecified or supported.
    35  	if endpointSpecific {
    36  		if !c.HashingAlgorithm.IsDefault() {
    37  			return errors.New("hashing algorithm cannot be specified on an endpoint-specific basis")
    38  		}
    39  	} else {
    40  		if !c.HashingAlgorithm.IsDefault() {
    41  			supportStatus := c.HashingAlgorithm.SupportStatus()
    42  			if supportStatus == hashing.AlgorithmSupportStatusUnsupported {
    43  				return errors.New("unknown or unsupported hashing algorithm")
    44  			} else if supportStatus == hashing.AlgorithmSupportStatusRequiresLicense {
    45  				return errors.New("hashing algorithm requires Mutagen Pro license")
    46  			}
    47  		}
    48  	}
    49  
    50  	// The maximum entry count doesn't need to be validated - any of its values
    51  	// are technically valid regardless of the source.
    52  
    53  	// The maximum staging file size doesn't need to be validated - any of its
    54  	// values are technically valid regardless of the source.
    55  
    56  	// Verify that the probe mode is unspecified or supported.
    57  	if !(c.ProbeMode.IsDefault() || c.ProbeMode.Supported()) {
    58  		return errors.New("unknown or unsupported probe mode")
    59  	}
    60  
    61  	// Verify that the scan mode is unspecified or supported.
    62  	if !(c.ScanMode.IsDefault() || c.ScanMode.Supported()) {
    63  		return errors.New("unknown or unsupported scan mode")
    64  	}
    65  
    66  	// Verify that the staging mode is unspecified or supported.
    67  	if !(c.StageMode.IsDefault() || c.StageMode.Supported()) {
    68  		return errors.New("unknown or unsupported staging mode")
    69  	}
    70  
    71  	// Verify that the symbolic link mode is unspecified or supported.
    72  	if endpointSpecific {
    73  		if !c.SymbolicLinkMode.IsDefault() {
    74  			return errors.New("symbolic link mode cannot be specified on an endpoint-specific basis")
    75  		}
    76  	} else {
    77  		if !(c.SymbolicLinkMode.IsDefault() || c.SymbolicLinkMode.Supported()) {
    78  			return errors.New("unknown or unsupported symbolic link mode")
    79  		}
    80  	}
    81  
    82  	// Verify that the watch mode is unspecified or supported.
    83  	if !(c.WatchMode.IsDefault() || c.WatchMode.Supported()) {
    84  		return errors.New("unknown or unsupported watch mode")
    85  	}
    86  
    87  	// The watch polling interval doesn't need to be validated - any of its
    88  	// values are technically valid regardless of the source.
    89  
    90  	// Verify that the ignore syntax is unspecified or supported.
    91  	if endpointSpecific {
    92  		if !c.IgnoreSyntax.IsDefault() {
    93  			return errors.New("ignore syntax cannot be specified on an endpoint-specific basis")
    94  		}
    95  	} else {
    96  		if !(c.IgnoreSyntax.IsDefault() || c.IgnoreSyntax.Supported()) {
    97  			return errors.New("unknown or unsupported ignore syntax")
    98  		}
    99  	}
   100  
   101  	// Verify that default ignores are unset for endpoint-specific
   102  	// configurations. This field is deprecated, but existing sessions may have
   103  	// it set, in which case we'll just prepend it to the nominal list of
   104  	// ignores when running the session. We don't bother rejecting its presence
   105  	// based on source. This is the only meaningful validation that we can do
   106  	// here because we don't yet know the ignore syntax being used (even if it's
   107  	// not a default value, it could be overridden in a different configuration
   108  	// that will be merged on top of this one). These ignores will eventually be
   109  	// validated at endpoint initialization time, but there's no convenient way
   110  	// to do it earlier in the session creation or loading process.
   111  	if endpointSpecific && len(c.DefaultIgnores) > 0 {
   112  		return errors.New("default ignores cannot be specified on an endpoint-specific basis (and are deprecated)")
   113  	}
   114  
   115  	// Verify that ignores are unset for endpoint-specific configurations. This
   116  	// is the only meaningful validation that we can do here because we don't
   117  	// yet know the ignore syntax being used (even if it's not a default value,
   118  	// it could be overridden in a different configuration that will be merged
   119  	// on top of this one). These ignores will eventually be validated at
   120  	// endpoint initialization time, but there's no convenient way to do it
   121  	// earlier in the session creation or loading process.
   122  	if endpointSpecific && len(c.Ignores) > 0 {
   123  		return errors.New("ignores cannot be specified on an endpoint-specific basis")
   124  	}
   125  
   126  	// Verify that the VCS ignore mode is unspecified or supported.
   127  	if endpointSpecific {
   128  		if !c.IgnoreVCSMode.IsDefault() {
   129  			return errors.New("VCS ignore mode cannot be specified on an endpoint-specific basis")
   130  		}
   131  	} else {
   132  		if !(c.IgnoreVCSMode.IsDefault() || c.IgnoreVCSMode.Supported()) {
   133  			return errors.New("unknown or unsupported VCS ignore mode")
   134  		}
   135  	}
   136  
   137  	// Verify that the permissions mode is unspecified or supported. Also
   138  	// determine the effective permissions mode for validating file and
   139  	// directory modes.
   140  	var effectivePermissionsMode core.PermissionsMode
   141  	if endpointSpecific {
   142  		if !c.PermissionsMode.IsDefault() {
   143  			return errors.New("permissions mode cannot be specified on an endpoint-specific basis")
   144  		}
   145  	} else {
   146  		if c.PermissionsMode.IsDefault() {
   147  			// HACK: We don't have a reference to the session version in this
   148  			// method, so we compute the default permissions mode by using the
   149  			// default session version for the current version of Mutagen. For
   150  			// more information on the reasoning behind this, see the note in
   151  			// Version.DefaultPermissionsMode.
   152  			effectivePermissionsMode = DefaultVersion.DefaultPermissionsMode()
   153  		} else if c.PermissionsMode.Supported() {
   154  			effectivePermissionsMode = c.PermissionsMode
   155  		} else {
   156  			return errors.New("unknown or unsupported permissions mode")
   157  		}
   158  	}
   159  
   160  	// Verify that the default file mode is valid for the effective permissions
   161  	// mode.
   162  	if c.DefaultFileMode != 0 {
   163  		if err := core.EnsureDefaultFileModeValid(
   164  			effectivePermissionsMode,
   165  			filesystem.Mode(c.DefaultFileMode),
   166  		); err != nil {
   167  			return fmt.Errorf("invalid default file permission mode specified: %w", err)
   168  		}
   169  	}
   170  
   171  	// Verify that the default directory mode is valid for the effective
   172  	// permissions mode.
   173  	if c.DefaultDirectoryMode != 0 {
   174  		if err := core.EnsureDefaultDirectoryModeValid(
   175  			effectivePermissionsMode,
   176  			filesystem.Mode(c.DefaultDirectoryMode),
   177  		); err != nil {
   178  			return fmt.Errorf("invalid default directory permission mode specified: %w", err)
   179  		}
   180  	}
   181  
   182  	// Verify the default owner specification.
   183  	if c.DefaultOwner != "" {
   184  		if kind, _ := filesystem.ParseOwnershipIdentifier(c.DefaultOwner); kind == filesystem.OwnershipIdentifierKindInvalid {
   185  			return errors.New("invalid default owner specification")
   186  		}
   187  	}
   188  
   189  	// Verify the default group specification.
   190  	if c.DefaultGroup != "" {
   191  		if kind, _ := filesystem.ParseOwnershipIdentifier(c.DefaultGroup); kind == filesystem.OwnershipIdentifierKindInvalid {
   192  			return errors.New("invalid default group specification")
   193  		}
   194  	}
   195  
   196  	// Verify that the compression algorithm is unspecified or supported.
   197  	if !c.CompressionAlgorithm.IsDefault() {
   198  		supportStatus := c.CompressionAlgorithm.SupportStatus()
   199  		if supportStatus == compression.AlgorithmSupportStatusUnsupported {
   200  			return errors.New("unknown or unsupported compression algorithm")
   201  		} else if supportStatus == compression.AlgorithmSupportStatusRequiresLicense {
   202  			return errors.New("compression algorithm requires Mutagen Pro license")
   203  		}
   204  	}
   205  
   206  	// Success.
   207  	return nil
   208  }
   209  
   210  // Equal returns whether or not the configuration is equivalent to another. The
   211  // result of this method is only valid if both configurations are valid.
   212  func (c *Configuration) Equal(other *Configuration) bool {
   213  	// Ensure that both are non-nil.
   214  	if c == nil || other == nil {
   215  		return false
   216  	}
   217  
   218  	// Perform an equivalence check.
   219  	return c.SynchronizationMode == other.SynchronizationMode &&
   220  		c.HashingAlgorithm == other.HashingAlgorithm &&
   221  		c.MaximumEntryCount == other.MaximumEntryCount &&
   222  		c.MaximumStagingFileSize == other.MaximumStagingFileSize &&
   223  		c.ProbeMode == other.ProbeMode &&
   224  		c.ScanMode == other.ScanMode &&
   225  		c.StageMode == other.StageMode &&
   226  		c.SymbolicLinkMode == other.SymbolicLinkMode &&
   227  		c.WatchMode == other.WatchMode &&
   228  		c.WatchPollingInterval == other.WatchPollingInterval &&
   229  		c.IgnoreSyntax == other.IgnoreSyntax &&
   230  		comparison.StringSlicesEqual(c.DefaultIgnores, other.DefaultIgnores) &&
   231  		comparison.StringSlicesEqual(c.Ignores, other.Ignores) &&
   232  		c.IgnoreVCSMode == other.IgnoreVCSMode &&
   233  		c.PermissionsMode == other.PermissionsMode &&
   234  		c.DefaultFileMode == other.DefaultFileMode &&
   235  		c.DefaultDirectoryMode == other.DefaultDirectoryMode &&
   236  		c.DefaultOwner == other.DefaultOwner &&
   237  		c.DefaultGroup == other.DefaultGroup &&
   238  		c.CompressionAlgorithm == other.CompressionAlgorithm
   239  }
   240  
   241  // MergeConfigurations merges two configurations of differing priorities. Both
   242  // configurations must be non-nil.
   243  func MergeConfigurations(lower, higher *Configuration) *Configuration {
   244  	// Create the resulting configuration.
   245  	result := &Configuration{}
   246  
   247  	// Merge the synchronization mode.
   248  	if !higher.SynchronizationMode.IsDefault() {
   249  		result.SynchronizationMode = higher.SynchronizationMode
   250  	} else {
   251  		result.SynchronizationMode = lower.SynchronizationMode
   252  	}
   253  
   254  	// Merge the hashing algorithm.
   255  	if !higher.HashingAlgorithm.IsDefault() {
   256  		result.HashingAlgorithm = higher.HashingAlgorithm
   257  	} else {
   258  		result.HashingAlgorithm = lower.HashingAlgorithm
   259  	}
   260  
   261  	// Merge the maximum entry count.
   262  	if higher.MaximumEntryCount != 0 {
   263  		result.MaximumEntryCount = higher.MaximumEntryCount
   264  	} else {
   265  		result.MaximumEntryCount = lower.MaximumEntryCount
   266  	}
   267  
   268  	// Merge the maximum staging file size.
   269  	if higher.MaximumStagingFileSize != 0 {
   270  		result.MaximumStagingFileSize = higher.MaximumStagingFileSize
   271  	} else {
   272  		result.MaximumStagingFileSize = lower.MaximumStagingFileSize
   273  	}
   274  
   275  	// Merge the probing mode.
   276  	if !higher.ProbeMode.IsDefault() {
   277  		result.ProbeMode = higher.ProbeMode
   278  	} else {
   279  		result.ProbeMode = lower.ProbeMode
   280  	}
   281  
   282  	// Merge the scanning mode.
   283  	if !higher.ScanMode.IsDefault() {
   284  		result.ScanMode = higher.ScanMode
   285  	} else {
   286  		result.ScanMode = lower.ScanMode
   287  	}
   288  
   289  	// Merge the staging mode.
   290  	if !higher.StageMode.IsDefault() {
   291  		result.StageMode = higher.StageMode
   292  	} else {
   293  		result.StageMode = lower.StageMode
   294  	}
   295  
   296  	// Merge the symbolic link mode.
   297  	if !higher.SymbolicLinkMode.IsDefault() {
   298  		result.SymbolicLinkMode = higher.SymbolicLinkMode
   299  	} else {
   300  		result.SymbolicLinkMode = lower.SymbolicLinkMode
   301  	}
   302  
   303  	// Merge the watching mode.
   304  	if !higher.WatchMode.IsDefault() {
   305  		result.WatchMode = higher.WatchMode
   306  	} else {
   307  		result.WatchMode = lower.WatchMode
   308  	}
   309  
   310  	// Merge the polling interval.
   311  	if higher.WatchPollingInterval != 0 {
   312  		result.WatchPollingInterval = higher.WatchPollingInterval
   313  	} else {
   314  		result.WatchPollingInterval = lower.WatchPollingInterval
   315  	}
   316  
   317  	// Merge the ignore syntax.
   318  	if !higher.IgnoreSyntax.IsDefault() {
   319  		result.IgnoreSyntax = higher.IgnoreSyntax
   320  	} else {
   321  		result.IgnoreSyntax = lower.IgnoreSyntax
   322  	}
   323  
   324  	// Merge default ignores. In theory, at most one of these should be
   325  	// non-empty, but we'll still implement it as if they both might have
   326  	// content.
   327  	result.DefaultIgnores = append(result.DefaultIgnores, lower.DefaultIgnores...)
   328  	result.DefaultIgnores = append(result.DefaultIgnores, higher.DefaultIgnores...)
   329  
   330  	// Merge ignores.
   331  	result.Ignores = append(result.Ignores, lower.Ignores...)
   332  	result.Ignores = append(result.Ignores, higher.Ignores...)
   333  
   334  	// Merge the VCS ignore mode.
   335  	if !higher.IgnoreVCSMode.IsDefault() {
   336  		result.IgnoreVCSMode = higher.IgnoreVCSMode
   337  	} else {
   338  		result.IgnoreVCSMode = lower.IgnoreVCSMode
   339  	}
   340  
   341  	// Merge the permissions mode.
   342  	if !higher.PermissionsMode.IsDefault() {
   343  		result.PermissionsMode = higher.PermissionsMode
   344  	} else {
   345  		result.PermissionsMode = lower.PermissionsMode
   346  	}
   347  
   348  	// Merge the default file mode.
   349  	if higher.DefaultFileMode != 0 {
   350  		result.DefaultFileMode = higher.DefaultFileMode
   351  	} else {
   352  		result.DefaultFileMode = lower.DefaultFileMode
   353  	}
   354  
   355  	// Merge the default directory mode.
   356  	if higher.DefaultDirectoryMode != 0 {
   357  		result.DefaultDirectoryMode = higher.DefaultDirectoryMode
   358  	} else {
   359  		result.DefaultDirectoryMode = lower.DefaultDirectoryMode
   360  	}
   361  
   362  	// Merge the default owner.
   363  	if higher.DefaultOwner != "" {
   364  		result.DefaultOwner = higher.DefaultOwner
   365  	} else {
   366  		result.DefaultOwner = lower.DefaultOwner
   367  	}
   368  
   369  	// Merge the default group.
   370  	if higher.DefaultGroup != "" {
   371  		result.DefaultGroup = higher.DefaultGroup
   372  	} else {
   373  		result.DefaultGroup = lower.DefaultGroup
   374  	}
   375  
   376  	// Merge the compression algorithm.
   377  	if !higher.CompressionAlgorithm.IsDefault() {
   378  		result.CompressionAlgorithm = higher.CompressionAlgorithm
   379  	} else {
   380  		result.CompressionAlgorithm = lower.CompressionAlgorithm
   381  	}
   382  
   383  	// Done.
   384  	return result
   385  }