2015-10-27 10:37:03 +00:00
// Copyright (C) 2014 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
2017-02-09 06:52:18 +00:00
// You can obtain one at https://mozilla.org/MPL/2.0/.
2015-10-27 10:37:03 +00:00
package config
import (
2016-11-17 15:12:41 +00:00
"fmt"
2015-10-27 10:37:03 +00:00
"os"
"path/filepath"
"runtime"
"strings"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/protocol"
)
type FolderConfiguration struct {
ID string ` xml:"id,attr" json:"id" `
2016-03-11 09:48:46 +00:00
Label string ` xml:"label,attr" json:"label" `
2015-10-27 10:37:03 +00:00
RawPath string ` xml:"path,attr" json:"path" `
2016-05-04 11:26:36 +00:00
Type FolderType ` xml:"type,attr" json:"type" `
2015-10-27 10:37:03 +00:00
Devices [ ] FolderDeviceConfiguration ` xml:"device" json:"devices" `
RescanIntervalS int ` xml:"rescanIntervalS,attr" json:"rescanIntervalS" `
IgnorePerms bool ` xml:"ignorePerms,attr" json:"ignorePerms" `
AutoNormalize bool ` xml:"autoNormalize,attr" json:"autoNormalize" `
2017-04-12 09:01:19 +00:00
MinDiskFree Size ` xml:"minDiskFree" json:"minDiskFree" `
2015-10-27 10:37:03 +00:00
Versioning VersioningConfiguration ` xml:"versioning" json:"versioning" `
Copiers int ` xml:"copiers" json:"copiers" ` // This defines how many files are handled concurrently.
Pullers int ` xml:"pullers" json:"pullers" ` // Defines how many blocks are fetched at the same time, possibly between separate copier routines.
Hashers int ` xml:"hashers" json:"hashers" ` // Less than one sets the value to the number of cores. These are CPU bound due to hashing.
Order PullOrder ` xml:"order" json:"order" `
IgnoreDelete bool ` xml:"ignoreDelete" json:"ignoreDelete" `
ScanProgressIntervalS int ` xml:"scanProgressIntervalS" json:"scanProgressIntervalS" ` // Set to a negative value to disable. Value of 0 will get replaced with value of 2 (default value)
PullerSleepS int ` xml:"pullerSleepS" json:"pullerSleepS" `
PullerPauseS int ` xml:"pullerPauseS" json:"pullerPauseS" `
MaxConflicts int ` xml:"maxConflicts" json:"maxConflicts" `
2015-11-21 15:30:53 +00:00
DisableSparseFiles bool ` xml:"disableSparseFiles" json:"disableSparseFiles" `
2016-04-15 10:59:41 +00:00
DisableTempIndexes bool ` xml:"disableTempIndexes" json:"disableTempIndexes" `
2016-11-21 17:09:29 +00:00
Fsync bool ` xml:"fsync" json:"fsync" `
2016-12-21 18:41:25 +00:00
Paused bool ` xml:"paused" json:"paused" `
2017-01-04 21:04:13 +00:00
WeakHashThresholdPct int ` xml:"weakHashThresholdPct" json:"weakHashThresholdPct" ` // Use weak hash if more than X percent of the file has changed. Set to -1 to always use weak hash.
2015-10-27 10:37:03 +00:00
2015-11-05 08:31:36 +00:00
cachedPath string
2016-05-04 10:47:33 +00:00
2017-04-12 09:01:19 +00:00
DeprecatedReadOnly bool ` xml:"ro,attr,omitempty" json:"-" `
DeprecatedMinDiskFreePct float64 ` xml:"minDiskFreePct" json:"-" `
2015-10-27 10:37:03 +00:00
}
type FolderDeviceConfiguration struct {
2016-11-07 16:40:48 +00:00
DeviceID protocol . DeviceID ` xml:"id,attr" json:"deviceID" `
IntroducedBy protocol . DeviceID ` xml:"introducedBy,attr" json:"introducedBy" `
2015-10-27 10:37:03 +00:00
}
2016-05-04 11:26:36 +00:00
func NewFolderConfiguration ( id , path string ) FolderConfiguration {
2015-11-07 08:47:31 +00:00
f := FolderConfiguration {
ID : id ,
RawPath : path ,
}
f . prepare ( )
return f
}
2015-10-27 10:37:03 +00:00
func ( f FolderConfiguration ) Copy ( ) FolderConfiguration {
c := f
c . Devices = make ( [ ] FolderDeviceConfiguration , len ( f . Devices ) )
copy ( c . Devices , f . Devices )
2015-10-27 10:53:42 +00:00
c . Versioning = f . Versioning . Copy ( )
2015-10-27 10:37:03 +00:00
return c
}
func ( f FolderConfiguration ) Path ( ) string {
// This is intentionally not a pointer method, because things like
// cfg.Folders["default"].Path() should be valid.
2016-07-02 19:38:39 +00:00
if f . cachedPath == "" && f . RawPath != "" {
2015-11-05 08:31:36 +00:00
l . Infoln ( "bug: uncached path call (should only happen in tests)" )
return f . cleanedPath ( )
2015-10-27 10:37:03 +00:00
}
2015-11-05 08:31:36 +00:00
return f . cachedPath
2015-10-27 10:37:03 +00:00
}
func ( f * FolderConfiguration ) CreateMarker ( ) error {
if ! f . HasMarker ( ) {
marker := filepath . Join ( f . Path ( ) , ".stfolder" )
fd , err := os . Create ( marker )
if err != nil {
return err
}
fd . Close ( )
2016-11-21 17:09:29 +00:00
if err := osutil . SyncDir ( filepath . Dir ( marker ) ) ; err != nil {
l . Infof ( "fsync %q failed: %v" , filepath . Dir ( marker ) , err )
}
2015-10-27 10:37:03 +00:00
osutil . HideFile ( marker )
}
return nil
}
func ( f * FolderConfiguration ) HasMarker ( ) bool {
_ , err := os . Stat ( filepath . Join ( f . Path ( ) , ".stfolder" ) )
2016-12-18 18:57:41 +00:00
return err == nil
2015-10-27 10:37:03 +00:00
}
2017-04-23 23:50:56 +00:00
func ( f * FolderConfiguration ) CreateRoot ( ) ( err error ) {
// Directory permission bits. Will be filtered down to something
// sane by umask on Unixes.
permBits := os . FileMode ( 0777 )
if runtime . GOOS == "windows" {
// Windows has no umask so we must chose a safer set of bits to
// begin with.
permBits = 0700
}
if _ , err = os . Stat ( f . Path ( ) ) ; os . IsNotExist ( err ) {
if err = osutil . MkdirAll ( f . Path ( ) , permBits ) ; err != nil {
l . Warnf ( "Creating directory for %v: %v" ,
f . Description ( ) , err )
}
}
return err
}
2016-11-17 15:12:41 +00:00
func ( f FolderConfiguration ) Description ( ) string {
2016-12-19 09:12:06 +00:00
if f . Label == "" {
return f . ID
}
2016-11-17 15:12:41 +00:00
return fmt . Sprintf ( "%q (%s)" , f . Label , f . ID )
}
2015-10-27 10:37:03 +00:00
func ( f * FolderConfiguration ) DeviceIDs ( ) [ ] protocol . DeviceID {
deviceIDs := make ( [ ] protocol . DeviceID , len ( f . Devices ) )
for i , n := range f . Devices {
deviceIDs [ i ] = n . DeviceID
}
return deviceIDs
}
2015-11-05 08:01:47 +00:00
func ( f * FolderConfiguration ) prepare ( ) {
2016-07-02 19:38:39 +00:00
if f . RawPath != "" {
// The reason it's done like this:
// C: -> C:\ -> C:\ (issue that this is trying to fix)
// C:\somedir -> C:\somedir\ -> C:\somedir
// C:\somedir\ -> C:\somedir\\ -> C:\somedir
// This way in the tests, we get away without OS specific separators
// in the test configs.
f . RawPath = filepath . Dir ( f . RawPath + string ( filepath . Separator ) )
// If we're not on Windows, we want the path to end with a slash to
// penetrate symlinks. On Windows, paths must not end with a slash.
if runtime . GOOS != "windows" && f . RawPath [ len ( f . RawPath ) - 1 ] != filepath . Separator {
f . RawPath = f . RawPath + string ( filepath . Separator )
}
2015-11-05 08:01:47 +00:00
}
2015-11-05 08:31:36 +00:00
f . cachedPath = f . cleanedPath ( )
2015-11-05 08:01:47 +00:00
if f . RescanIntervalS > MaxRescanIntervalS {
f . RescanIntervalS = MaxRescanIntervalS
} else if f . RescanIntervalS < 0 {
f . RescanIntervalS = 0
}
2016-05-09 11:30:19 +00:00
if f . Versioning . Params == nil {
f . Versioning . Params = make ( map [ string ] string )
}
2017-01-04 21:04:13 +00:00
if f . WeakHashThresholdPct == 0 {
f . WeakHashThresholdPct = 25
}
2015-11-05 08:01:47 +00:00
}
2015-11-05 08:31:36 +00:00
func ( f * FolderConfiguration ) cleanedPath ( ) string {
2016-07-02 19:38:39 +00:00
if f . RawPath == "" {
return ""
}
2015-11-05 08:31:36 +00:00
cleaned := f . RawPath
// Attempt tilde expansion; leave unchanged in case of error
if path , err := osutil . ExpandTilde ( cleaned ) ; err == nil {
cleaned = path
}
// Attempt absolutification; leave unchanged in case of error
if ! filepath . IsAbs ( cleaned ) {
// Abs() looks like a fairly expensive syscall on Windows, while
// IsAbs() is a whole bunch of string mangling. I think IsAbs() may be
// somewhat faster in the general case, hence the outer if...
if path , err := filepath . Abs ( cleaned ) ; err == nil {
cleaned = path
}
}
// Attempt to enable long filename support on Windows. We may still not
// have an absolute path here if the previous steps failed.
if runtime . GOOS == "windows" && filepath . IsAbs ( cleaned ) && ! strings . HasPrefix ( f . RawPath , ` \\ ` ) {
return ` \\?\ ` + cleaned
}
2016-06-26 10:17:20 +00:00
// If we're not on Windows, we want the path to end with a slash to
// penetrate symlinks. On Windows, paths must not end with a slash.
if runtime . GOOS != "windows" && cleaned [ len ( cleaned ) - 1 ] != filepath . Separator {
cleaned = cleaned + string ( filepath . Separator )
}
2015-11-05 08:31:36 +00:00
return cleaned
}
2015-10-27 10:37:03 +00:00
type FolderDeviceConfigurationList [ ] FolderDeviceConfiguration
func ( l FolderDeviceConfigurationList ) Less ( a , b int ) bool {
return l [ a ] . DeviceID . Compare ( l [ b ] . DeviceID ) == - 1
}
func ( l FolderDeviceConfigurationList ) Swap ( a , b int ) {
l [ a ] , l [ b ] = l [ b ] , l [ a ]
}
func ( l FolderDeviceConfigurationList ) Len ( ) int {
return len ( l )
}