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 }