Compare commits

..
53 Commits
Author SHA1 Message Date
Jakob Borg fb4fdaf4c0 Merge pull request from GHSA-x462-89pf-6r5h 2021-04-06 08:03:22 +02:00
Simon FreiandGitHub 0d7a77ba85 lib/model: Reset conn when enc token is missing (fixes #7198) (#7518) 2021-03-23 10:38:40 +01:00
Simon FreiandGitHub 924b96856f lib: Handle adding enc folders on an existing conn (fixes #7509) (#7510) 2021-03-22 21:50:19 +01:00
Audrius ButkeviciusandGitHub f7929229c8 cmd/strelaysrv: Increase default strelaysrv network buffer size (fixes #7514) (#7517)
Increases throughput at the cost of more memory per connection
2021-03-22 21:47:51 +01:00
Simon FreiandGitHub 6b25eb2e79 lib/ur: Prevent panic when blocksResult is nil (ref #7495) (#7515) 2021-03-22 15:07:41 +01:00
Simon FreiandGitHub bc08a951f1 lib/model: Encrypted fileinfo trailer needs to be in wire format (#7505) 2021-03-21 10:34:08 +01:00
Lars LehtonenandGitHub a87c5515bd lib/model: Error check in test (#7508) 2021-03-21 10:32:17 +01:00
Jakob BorgandGitHub ebcd22b02b lib/osutil: Fix raising max FDs on macOS (#7507)
There was a logic mistake, so the limit in question wasn't used. On my
macOS this doesn't seem to matter, the hard limit returned is 2^63-1 and
setting the soft limit to that works. However I'm assuming that's not
the case for older macOSes since it was so nicely documented, so we
should still have this working. (10240 FDs should be enough for
anybody.)
2021-03-20 16:32:36 +01:00
Audrius ButkeviciusandGitHub 4b02b7e6f1 lib/connections: Silence "connected to myself" messages. (#7500) 2021-03-17 23:53:20 +01:00
Jakob Borg fdd823d2cb lib/osutil: Remove unused code 2021-03-17 23:18:07 +01:00
Jakob BorgandGitHub 8ef504f745 all: Simplify some method calls (#7499)
strings.Replace(a, b, c, -1) -> strings.ReplaceAll(a, b, c)

(Go 1.12) and who knows what was up with that dialQueue.Sort() thing.
2021-03-17 23:12:26 +01:00
Jakob BorgandGitHub 960e850a78 cmd/uraggregate: Ignore errors harder (#7498)
We were already ignoring them, it just didn't look like it.
2021-03-17 22:55:43 +01:00
Jakob BorgandGitHub ea701a4e9e cmd/syncthing: Error handling on upgrade (#7497)
dat innocuous little colon
2021-03-17 22:52:43 +01:00
deepsource-autofix[bot]GitHubdeepsource-autofix[bot] <62050782+deepsource-autofix[bot]@users.noreply.github.com>
6c573a5762 Remove unnecessary guard around delete (#7496)
Co-authored-by: deepsource-autofix[bot] <62050782+deepsource-autofix[bot]@users.noreply.github.com>
2021-03-17 22:23:36 +01:00
Jakob BorgandGitHub 3ac858b150 all: Remove miscellaneous vestigial code (#7495) 2021-03-17 22:23:12 +01:00
Jakob BorgandGitHub f4372710bf all: Remove crypto/md5 (#7493)
This is a mostly pointless change to make security scanners and static
analysis tools happy, as they all hate seeing md5. None of our md5 uses
were security relevant, but still. Only visible effect of this change is
that our temp file names for very long file names become slightly longer
than they were previously...
2021-03-17 22:22:49 +01:00
Jakob BorgandGitHub f39477bbd5 lib/api: Missing error handling in API delete-device (#7494) 2021-03-17 22:08:44 +01:00
Jakob BorgandGitHub 6e5514419d lib/db: Fix some omitted error checks, unused variable (#7489) 2021-03-17 21:41:07 +01:00
tomasz1986andGitHub f014b7b919 gui: Improve Receive Encrypted folder type requirements text (#7473)
The current text gives an impression that we are currently using a
Receive Encrypted folder, even if we are not. Thus, make the current
text displayed only when the folder is in fact Receive Encrypted, and
add a new string to be displayed when using different folder types.

Signed-off-by: Tomasz Wilczyński <twilczynski@naver.com>
2021-03-17 21:36:11 +01:00
Jakob BorgandGitHub 81484699db lib/model: Actually break puller loop on context cancel (#7492)
Current break does nothing (breaks the select).
2021-03-17 21:34:52 +01:00
Jakob Borg 6d93d9c488 deepsource: Test patterns 2021-03-17 21:12:51 +01:00
deepsource-autofix[bot]GitHubdeepsource-autofix[bot] <62050782+deepsource-autofix[bot]@users.noreply.github.com>
0930bccf88 cmd/ursrv, lib/scanner: Remove unnecessary slicing of slices (#7491)
Co-authored-by: deepsource-autofix[bot] <62050782+deepsource-autofix[bot]@users.noreply.github.com>
2021-03-17 21:04:36 +01:00
Jakob BorgandGitHub e321bd3941 lib/*/auto: Add noassets files (#7490)
This adds a couple of dummy asset files protected by the "noassets"
build tag. The purpose is that it should be possible for, for example,
CI tools and static analysis things to compile and analyze the source
tree without our custom asset generation step. Also makes `go test -tags
noassets ./...` work without building assets first.
2021-03-17 21:03:35 +01:00
Simon FreiandGitHub 4b02937862 lib/model: Add missing lock on availability func (#7487) 2021-03-17 20:46:13 +01:00
Jakob Borg 21e6849f2d Add DeepSource for static analysis 2021-03-17 20:05:20 +01:00
Simon Frei 39c2d1bc1a cmd/syncthing: Check os.Args length (fixes #7486) 2021-03-17 15:51:14 +01:00
Simon FreiandGitHub cd21b8dfa5 cmd/syncthing/cli: Remove db reset and adjust others (#7484) 2021-03-17 09:06:37 +01:00
Simon FreiandGitHub 40fbdc87ce cmd/syncthing: Refactor cli subcommand to allow flags (#7454) 2021-03-17 09:04:50 +01:00
Jakob Borg 1814f4693d gui, man, authors: Update docs, translations, and contributors 2021-03-17 07:45:40 +01:00
Simon FreiandGitHub 3f2b584c4e lib/model: Don't use ignore patterns for recv-enc folders (fixes #7469) (#7472) 2021-03-16 15:04:11 +01:00
Jakob BorgandGitHub e0dd737822 gui: Fix validation and help texts in versioning editor (fixes #7481) (#7482) 2021-03-16 09:30:07 +01:00
Simon FreiandGitHub d2d4fcc1df lib/protocol: Improve messages when an error occurs receiving (ref #7466) (#7470) 2021-03-15 19:14:09 +01:00
Simon FreiandGitHub 273ee09925 lib/db, lib/model: Allow needing invalid files (fixes #7474) (#7476) 2021-03-15 07:58:01 +01:00
tomasz1986andGitHub bb886868d2 gui: Use grey background for disabled options in form-control (#7468)
Disabled options are currently barely distinguishable from enabled
ones. This changes their background to grey, following the Bootstrap
defaults already used for disabled <select>.

Signed-off-by: Tomasz Wilczyński <twilczynski@naver.com>
2021-03-14 08:14:19 +01:00
Simon FreiandGitHub f80ee472c2 lib/protocol: Set invalid flag on encrypted file infos (fixes #7466) (#7467) 2021-03-13 16:57:36 +01:00
tomasz1986andGitHub a12ede3bbe gui: Fix missing trashcanClean in simple versioning (#7465) 2021-03-13 10:14:26 +01:00
Simon FreiandGitHub 97a8777d03 lib/fs: Check both old and new path when renaming (fixes #7426) (#7463) 2021-03-12 21:15:50 +01:00
Simon FreiandGitHub 8a4c00d82e lib/model: Send failure report on CC encryption check error (#7460) 2021-03-12 12:21:54 +01:00
Simon FreiandGitHub 31f859e909 lib/model: Return correct error in puller-iteration (ref #7424) (#7461) 2021-03-12 12:21:28 +01:00
Jakob BorgandGitHub 4d979a1ce9 all: Truncate some timestamps (fixes #7457) (#7459)
This truncates times meant for API consumption to second precision,
where fractions won't typically matter or add any value. Exception to
this is timestamps on logs and events, and of course I'm not touching
things like file metadata.

I'm not 100% certain this is an exhaustive change, but it's the things I
found by grepping and following the breadcrumbs from lib/api...

I also considered general-but-ugly solutions, like having the API
serializer itself do reflection magic or even regexps on returned
objects, but decided against it because aurgh...
2021-03-12 10:35:10 +01:00
Simon FreiandGitHub 4465cdf8bc lib/api: Fix body of renamed config/restart-required endpoint (ref #7402) (#7453) 2021-03-11 15:54:05 +01:00
Simon FreiandGitHub 3938b61c3f lib/fs: Expose fs option on interface (fixes #7385, ref #7381) (#7389) 2021-03-11 15:23:56 +01:00
Jakob BorgandGitHub cdef503db6 all: Make config.Wrapper an actual suture.Service (fixes #7451) (#7452) 2021-03-11 14:51:00 +01:00
Jakob BorgandGitHub df08984a58 lib/api: Sanitize names used in certificates (fixes #7434) (#7435) 2021-03-11 13:15:03 +01:00
Simon FreiandGitHub cf838c71f7 gui: Check if versioning object exists (fixes #7449) (#7450) 2021-03-11 11:21:35 +01:00
Simon FreiandGitHub 9a001051d6 cmd/ursrv, lib/ur: Collect and present encryption usage (#7448) 2021-03-10 22:26:56 +00:00
Jakob Borg 5548a8eb7a gui, man, authors: Update docs, translations, and contributors 2021-03-10 07:45:45 +01:00
Simon FreiandGitHub 727df34aa1 cmd/syncthing: Correct name of HiddenConsole flag (fixes #7446) (#7447) 2021-03-10 07:31:31 +01:00
Simon FreiandGitHub 9587a523b3 build: Update notify (#7444) 2021-03-08 13:36:03 +01:00
Jakob BorgandGitHub 22e44642a0 next-gen-gui: Add button to restore default theme (#7433)
This adds a button in the top right that changes the config back to the
default theme.

Code wise, it takes the header that was previously a part of the
dashboard component and moves it to the app component, and then adds the
button there. Possibly the header should be a component of it's own, but
that's more of refactor that can happen separately I think.

The config change uses the new config API to just patch the relevant
setting.

I'm not doing an automatic reload because 1) I don't want to figure out
how to do it correctly and 2) this doesn't work reliably anyway, as
for example the current gen GUI does a reload and ends up with
connection refused as the API service is still reloading...
2021-03-08 12:54:08 +01:00
greatroarandGitHub c00520281b lib/protocol: Optimize FileKey (#7440) 2021-03-07 18:44:21 +01:00
Simon FreiandGitHub 587c89d979 cmd/syncthing, etc: Consistent failure restart backoff (#7439) 2021-03-07 15:25:55 +01:00
Simon FreiandGitHub 310fba4c12 lib: Return error from db.FileSet.Snapshot (fixes #7419, ref #5907) (#7424) 2021-03-07 13:43:22 +01:00
128 changed files with 1839 additions and 984 deletions
+12
View File
@@ -0,0 +1,12 @@
version = 1
exclude_patterns = ["*.pb.go"]
test_patterns = ["*_test.go"]
[[analyzers]]
name = "go"
enabled = true
[analyzers.meta]
import_paths = ["github.com/syncthing/syncthing"]
build_tags = ["noassets"]
+7 -10
View File
@@ -7,7 +7,6 @@
package main
import (
"crypto/md5"
"errors"
"flag"
"fmt"
@@ -15,6 +14,8 @@ import (
"log"
"os"
"path/filepath"
"github.com/syncthing/syncthing/lib/sha256"
)
func main() {
@@ -74,7 +75,7 @@ type fileInfo struct {
name string
mode os.FileMode
mod int64
hash [16]byte
hash [sha256.Size]byte
}
func (f fileInfo) String() string {
@@ -106,11 +107,7 @@ func startWalker(dir string, res chan<- fileInfo, abort <-chan struct{}) chan er
if err != nil {
return err
}
h := md5.New()
h.Write([]byte(tgt))
hash := h.Sum(nil)
copy(f.hash[:], hash)
f.hash = sha256.Sum256([]byte(tgt))
} else if info.IsDir() {
f = fileInfo{
name: rn,
@@ -123,7 +120,7 @@ func startWalker(dir string, res chan<- fileInfo, abort <-chan struct{}) chan er
mode: info.Mode(),
mod: info.ModTime().Unix(),
}
sum, err := md5file(path)
sum, err := sha256file(path)
if err != nil {
return err
}
@@ -150,14 +147,14 @@ func startWalker(dir string, res chan<- fileInfo, abort <-chan struct{}) chan er
return errc
}
func md5file(fname string) (hash [16]byte, err error) {
func sha256file(fname string) (hash [sha256.Size]byte, err error) {
f, err := os.Open(fname)
if err != nil {
return
}
defer f.Close()
h := md5.New()
h := sha256.New()
io.Copy(h, f)
hb := h.Sum(nil)
copy(hash[:], hb)
+15
View File
@@ -0,0 +1,15 @@
// Copyright (C) 2021 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,
// You can obtain one at https://mozilla.org/MPL/2.0/.
//+build noassets
package auto
import "github.com/syncthing/syncthing/lib/assets"
func Assets() map[string]assets.Asset {
return nil
}
+3 -2
View File
@@ -10,7 +10,6 @@ import (
"encoding/json"
"flag"
"fmt"
"github.com/syncthing/syncthing/lib/protocol"
"io"
"io/ioutil"
"log"
@@ -23,6 +22,8 @@ import (
"strings"
"time"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/golang/groupcache/lru"
"github.com/oschwald/geoip2-golang"
"github.com/prometheus/client_golang/prometheus"
@@ -475,7 +476,7 @@ func handleRelayTest(request request) {
updateMetrics(request.relay.uri.Host, *stats, location)
}
request.relay.Stats = stats
request.relay.StatsRetrieved = time.Now()
request.relay.StatsRetrieved = time.Now().Truncate(time.Second)
request.relay.Location = location
timer, ok := evictionTimers[request.relay.uri.Host]
+3 -1
View File
@@ -99,7 +99,7 @@ func main() {
flag.IntVar(&natRenewal, "nat-renewal", 30, "NAT renewal frequency in minutes")
flag.IntVar(&natTimeout, "nat-timeout", 10, "NAT discovery timeout in seconds")
flag.BoolVar(&pprofEnabled, "pprof", false, "Enable the built in profiling on the status server")
flag.IntVar(&networkBufferSize, "network-buffer", 2048, "Network buffer size (two of these per proxied connection)")
flag.IntVar(&networkBufferSize, "network-buffer", 65536, "Network buffer size (two of these per proxied connection)")
showVersion := flag.Bool("version", false, "Show version")
flag.Parse()
@@ -186,6 +186,7 @@ func main() {
}
wrapper := config.Wrap("config", config.New(id), id, events.NoopLogger)
go wrapper.Serve(context.TODO())
wrapper.Modify(func(cfg *config.Configuration) {
cfg.Options.NATLeaseM = natLease
cfg.Options.NATRenewalM = natRenewal
@@ -232,6 +233,7 @@ func main() {
uri, err := url.Parse(fmt.Sprintf("relay://%s/?id=%s&pingInterval=%s&networkTimeout=%s&sessionLimitBps=%d&globalLimitBps=%d&statusAddr=%s&providedBy=%s", mapping.Address(), id, pingInterval, networkTimeout, sessionLimitBps, globalLimitBps, statusAddr, providedBy))
if err != nil {
log.Fatalln("Failed to construct URI", err)
return
}
log.Println("URI:", uri.String())
+1 -1
View File
@@ -37,7 +37,7 @@ type result struct {
func main() {
flag.Parse()
prefix := strings.ToUpper(strings.Replace(flag.Arg(0), "-", "", -1))
prefix := strings.ToUpper(strings.ReplaceAll(flag.Arg(0), "-", ""))
if len(prefix) > 7 {
prefix = prefix[:7] + "-" + prefix[7:]
}
+22 -23
View File
@@ -7,31 +7,15 @@
package main
import (
"bytes"
"crypto/md5"
"flag"
"fmt"
"io"
"os"
"time"
"github.com/syncthing/syncthing/lib/sha256"
)
func getmd5(filePath string) ([]byte, error) {
var result []byte
file, err := os.Open(filePath)
if err != nil {
return result, err
}
defer file.Close()
hash := md5.New()
if _, err := io.Copy(hash, file); err != nil {
return result, err
}
return hash.Sum(result), nil
}
func main() {
period := flag.Duration("period", 200*time.Millisecond, "Sleep period between checks")
flag.Parse()
@@ -46,7 +30,7 @@ func main() {
exists := true
size := int64(0)
mtime := time.Time{}
hash := []byte{}
var hash [sha256.Size]byte
for {
time.Sleep(*period)
@@ -72,7 +56,7 @@ func main() {
if !exists {
size = 0
mtime = time.Time{}
hash = []byte{}
hash = [sha256.Size]byte{}
continue
}
@@ -83,12 +67,12 @@ func main() {
newSize := fi.Size()
newMtime := fi.ModTime()
newHash, err := getmd5(file)
newHash, err := sha256file(file)
if err != nil {
fmt.Println("getmd5:", err)
fmt.Println("sha256file:", err)
}
if newSize != size || newMtime != mtime || !bytes.Equal(newHash, hash) {
if newSize != size || newMtime != mtime || newHash != hash {
fmt.Println(file, "Size:", newSize, "Mtime:", newMtime, "Hash:", fmt.Sprintf("%x", newHash))
hash = newHash
size = newSize
@@ -96,3 +80,18 @@ func main() {
}
}
}
func sha256file(fname string) (hash [sha256.Size]byte, err error) {
f, err := os.Open(fname)
if err != nil {
return
}
defer f.Close()
h := sha256.New()
io.Copy(h, f)
hb := h.Sum(nil)
copy(hash[:], hb)
return
}
+60 -32
View File
@@ -10,33 +10,39 @@ import (
"bufio"
"crypto/tls"
"encoding/json"
"log"
"io/ioutil"
"os"
"reflect"
"strings"
"github.com/AudriusButkevicius/recli"
"github.com/alecthomas/kong"
"github.com/flynn-archive/go-shlex"
"github.com/mattn/go-isatty"
"github.com/pkg/errors"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/locations"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/svcutil"
"github.com/urfave/cli"
)
type CLI struct {
GUIAddress string `name:"gui-address" placeholder:"URL" help:"Override GUI address (e.g. \"http://192.0.2.42:8443\")"`
GUIAPIKey string `name:"gui-apikey" placeholder:"API-KEY" help:"Override GUI API key"`
HomeDir string `name:"home" placeholder:"PATH" help:"Set configuration and data directory"`
ConfDir string `name:"conf" placeholder:"PATH" help:"Set configuration directory (config and keys)"`
Args []string `arg:"" optional:""`
type preCli struct {
GUIAddress string `name:"gui-address"`
GUIAPIKey string `name:"gui-apikey"`
HomeDir string `name:"home"`
ConfDir string `name:"conf"`
}
func (c *CLI) Run() error {
func Run() error {
// This is somewhat a hack around a chicken and egg problem. We need to set
// the home directory and potentially other flags to know where the
// syncthing instance is running in order to get it's config ... which we
// then use to construct the actual CLI ... at which point it's too late to
// add flags there...
c := preCli{}
parseFlags(&c)
// Not set as default above because the strings can be really long.
var err error
homeSet := c.HomeDir != ""
@@ -50,8 +56,7 @@ func (c *CLI) Run() error {
err = locations.SetBaseDir(locations.ConfigBaseDir, c.ConfDir)
}
if err != nil {
log.Println("Command line options:", err)
os.Exit(svcutil.ExitError.AsInt())
return errors.Wrap(err, "Command line options:")
}
guiCfg := config.GUIConfiguration{
RawAddress: c.GUIAddress,
@@ -136,28 +141,26 @@ func (c *CLI) Run() error {
// Construct the actual CLI
app := cli.NewApp()
app.Name = "syncthing cli"
app.HelpName = app.Name
app.Author = "The Syncthing Authors"
app.Usage = "Syncthing command line interface"
app.Flags = fakeFlags
app.Metadata = map[string]interface{}{
"client": client,
}
app.Commands = []cli.Command{
{
Name: "config",
HideHelp: true,
Usage: "Configuration modification command group",
Subcommands: commands,
app.Commands = []cli.Command{{
Name: "cli",
Usage: "Syncthing command line interface",
Flags: fakeFlags,
Subcommands: []cli.Command{
{
Name: "config",
HideHelp: true,
Usage: "Configuration modification command group",
Subcommands: commands,
},
showCommand,
operationCommand,
errorsCommand,
},
showCommand,
operationCommand,
errorsCommand,
}
// It expects to be give os.Args which has argv[0] set to executable name, so fake it.
c.Args = append([]string{"cli"}, c.Args...)
}}
tty := isatty.IsTerminal(os.Stdin.Fd()) || isatty.IsCygwinTerminal(os.Stdin.Fd())
if !tty {
@@ -171,7 +174,7 @@ func (c *CLI) Run() error {
if len(input) == 0 {
continue
}
err = app.Run(append(c.Args, input...))
err = app.Run(append(os.Args, input...))
if err != nil {
return err
}
@@ -181,7 +184,7 @@ func (c *CLI) Run() error {
return err
}
} else {
err = app.Run(c.Args)
err = app.Run(os.Args)
if err != nil {
return err
}
@@ -206,3 +209,28 @@ func (c *CLI) Run() error {
}
return nil
}
func parseFlags(c *preCli) error {
// kong only needs to parse the global arguments after "cli" and before the
// subcommand (if any).
if len(os.Args) <= 2 {
return nil
}
args := os.Args[2:]
for i := 0; i < len(args); i++ {
if !strings.HasPrefix(args[i], "--") {
args = args[:i]
break
}
if !strings.Contains(args[i], "=") {
i++
}
}
// We don't want kong to print anything nor os.Exit (e.g. on -h)
parser, err := kong.New(c, kong.Writers(ioutil.Discard, ioutil.Discard), kong.Exit(func(int) {}))
if err != nil {
return err
}
_, err = parser.Parse(args)
return err
}
+1 -6
View File
@@ -27,11 +27,6 @@ var operationCommand = cli.Command{
Usage: "Shutdown syncthing",
Action: expects(0, emptyPost("system/shutdown")),
},
{
Name: "reset",
Usage: "Reset syncthing deleting all folders and devices",
Action: expects(0, emptyPost("system/reset")),
},
{
Name: "upgrade",
Usage: "Upgrade syncthing (if a newer version is available)",
@@ -39,7 +34,7 @@ var operationCommand = cli.Command{
},
{
Name: "folder-override",
Usage: "Override changes on folder (remote for sendonly, local for receiveonly)",
Usage: "Override changes on folder (remote for sendonly, local for receiveonly). WARNING: Destructive - deletes/changes your data.",
ArgsUsage: "[folder id]",
Action: expects(1, foldersOverride),
},
+1 -1
View File
@@ -23,7 +23,7 @@ var showCommand = cli.Command{
{
Name: "config-status",
Usage: "Show configuration status, whether or not a restart is required for changes to take effect",
Action: expects(0, dumpOutput("system/config/insync")),
Action: expects(0, dumpOutput("config/restart-required")),
},
{
Name: "system",
+6 -1
View File
@@ -18,6 +18,7 @@ import (
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/scanner"
)
@@ -79,7 +80,7 @@ func (c *CLI) walk() error {
dstFs = fs.NewFilesystem(fs.FilesystemTypeBasic, c.To)
}
return srcFs.Walk("/", func(path string, info fs.FileInfo, err error) error {
return srcFs.Walk(".", func(path string, info fs.FileInfo, err error) error {
if err != nil {
return err
}
@@ -142,6 +143,10 @@ func (c *CLI) process(srcFs fs.Filesystem, dstFs fs.Filesystem, path string) err
return fmt.Errorf("%s: loading metadata trailer: %w", path, err)
}
// Workaround for a bug in <= v1.15.0-rc.5 where we stored names
// in native format, while protocol expects wire format (slashes).
encFi.Name = osutil.NormalizedFilename(encFi.Name)
plainFi, err := protocol.DecryptFileInfo(*encFi, c.folderKey)
if err != nil {
return fmt.Errorf("%s: decrypting metadata: %w", path, err)
+18 -10
View File
@@ -38,6 +38,7 @@ import (
"github.com/syncthing/syncthing/lib/build"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/db/backend"
"github.com/syncthing/syncthing/lib/dialer"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/fs"
@@ -131,14 +132,16 @@ var (
// The entrypoint struct is the main entry point for the command line parser. The
// commands and options here are top level commands to syncthing.
// Cli is just a placeholder for the help text (see main).
var entrypoint struct {
Serve serveOptions `cmd:"" help:"Run Syncthing"`
Decrypt decrypt.CLI `cmd:"" help:"Decrypt or verify an encrypted folder"`
Cli cli.CLI `cmd:"" help:"Command line interface for Syncthing"`
Cli struct{} `cmd:"" help:"Command line interface for Syncthing"`
}
// serveOptions are the options for the `syncthing serve` command.
type serveOptions struct {
buildServeOptions
AllowNewerConfig bool `help:"Allow loading newer than current config version"`
Audit bool `help:"Write events to audit file"`
AuditFile string `name:"auditfile" placeholder:"PATH" help:"Specify audit file (use \"-\" for stdout, \"--\" for stderr)"`
@@ -149,7 +152,6 @@ type serveOptions struct {
GenerateDir string `name:"generate" placeholder:"PATH" help:"Generate key and config in specified dir, then exit"`
GUIAddress string `name:"gui-address" placeholder:"URL" help:"Override GUI address (e.g. \"http://192.0.2.42:8443\")"`
GUIAPIKey string `name:"gui-apikey" placeholder:"API-KEY" help:"Override GUI API key"`
HideConsole bool `help:"Hide console window (Windows only)"`
HomeDir string `name:"home" placeholder:"PATH" help:"Set configuration and data directory"`
LogFile string `name:"logfile" default:"${logFile}" placeholder:"PATH" help:"Log file name (see below)"`
LogFlags int `name:"logflags" default:"${logFlags}" placeholder:"BITS" help:"Select information in log line prefix (see below)"`
@@ -211,6 +213,17 @@ func defaultVars() kong.Vars {
}
func main() {
// The "cli" subcommand uses a different command line parser, and e.g. help
// gets mangled when integrating it as a subcommand -> detect it here at the
// beginning.
if len(os.Args) > 1 && os.Args[1] == "cli" {
if err := cli.Run(); err != nil {
fmt.Println(err)
os.Exit(1)
}
return
}
// First some massaging of the raw command line to fit the new model.
// Basically this means adding the default command at the front, and
// converting -options to --options.
@@ -248,10 +261,6 @@ func main() {
}
func helpHandler(options kong.HelpOptions, ctx *kong.Context) error {
// If we're looking for CLI help, pass the arguments down to the CLI library to print it's own help.
if ctx.Command() == "cli" {
return ctx.Run()
}
if err := kong.DefaultHelpPrinter(options, ctx); err != nil {
return err
}
@@ -384,7 +393,8 @@ func (options serveOptions) Run() error {
release, err := checkUpgrade()
if err == nil {
// Use leveldb database locks to protect against concurrent upgrades
ldb, err := syncthing.OpenDBBackend(locations.Get(locations.Database), config.TuningAuto)
var ldb backend.Backend
ldb, err = syncthing.OpenDBBackend(locations.Get(locations.Database), config.TuningAuto)
if err != nil {
err = upgradeViaRest()
} else {
@@ -603,9 +613,7 @@ func syncthingMain(options serveOptions) {
l.Warnln("Failed to initialize config:", err)
os.Exit(svcutil.ExitError.AsInt())
}
if cfgService, ok := cfgWrapper.(suture.Service); ok {
earlyService.Add(cfgService)
}
earlyService.Add(cfgWrapper)
// Candidate builds should auto upgrade. Make sure the option is set,
// unless we are in a build where it's disabled or the STNOUPGRADE
+7 -6
View File
@@ -36,8 +36,9 @@ var (
)
const (
countRestarts = 4
loopThreshold = 60 * time.Second
restartCounts = 4
restartPause = 1 * time.Second
restartLoopThreshold = 60 * time.Second
logFileAutoCloseDelay = 5 * time.Second
logFileMaxOpenTime = time.Minute
panicUploadMaxWait = 30 * time.Second
@@ -84,7 +85,7 @@ func monitorMain(options serveOptions) {
}
args := os.Args
var restarts [countRestarts]time.Time
var restarts [restartCounts]time.Time
stopSign := make(chan os.Signal, 1)
signal.Notify(stopSign, os.Interrupt, sigTerm)
@@ -97,8 +98,8 @@ func monitorMain(options serveOptions) {
for {
maybeReportPanics()
if t := time.Since(restarts[0]); t < loopThreshold {
l.Warnf("%d restarts in %v; not retrying further", countRestarts, t)
if t := time.Since(restarts[0]); t < restartLoopThreshold {
l.Warnf("%d restarts in %v; not retrying further", restartCounts, t)
os.Exit(svcutil.ExitError.AsInt())
}
@@ -193,7 +194,7 @@ func monitorMain(options serveOptions) {
}
l.Infoln("Syncthing exited:", err)
time.Sleep(1 * time.Second)
time.Sleep(restartPause)
if first {
// Let the next child process know that this is not the first time
+13
View File
@@ -0,0 +1,13 @@
// Copyright (C) 2021 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,
// You can obtain one at https://mozilla.org/MPL/2.0/.
// +build !windows
package main
type buildServeOptions struct {
HideConsole bool `hidden:""`
}
+11
View File
@@ -0,0 +1,11 @@
// Copyright (C) 2021 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,
// You can obtain one at https://mozilla.org/MPL/2.0/.
package main
type buildServeOptions struct {
HideConsole bool `name:"no-console" help:"Hide console window"`
}
+6 -6
View File
@@ -135,30 +135,30 @@ func setupDB(db *sql.DB) error {
row := db.QueryRow(`SELECT 'UniqueDayVersionIndex'::regclass`)
if err := row.Scan(&t); err != nil {
_, err = db.Exec(`CREATE UNIQUE INDEX UniqueDayVersionIndex ON VersionSummary (Day, Version)`)
_, _ = db.Exec(`CREATE UNIQUE INDEX UniqueDayVersionIndex ON VersionSummary (Day, Version)`)
}
row = db.QueryRow(`SELECT 'VersionDayIndex'::regclass`)
if err := row.Scan(&t); err != nil {
_, err = db.Exec(`CREATE INDEX VersionDayIndex ON VersionSummary (Day)`)
_, _ = db.Exec(`CREATE INDEX VersionDayIndex ON VersionSummary (Day)`)
}
row = db.QueryRow(`SELECT 'MovementDayIndex'::regclass`)
if err := row.Scan(&t); err != nil {
_, err = db.Exec(`CREATE INDEX MovementDayIndex ON UserMovement (Day)`)
_, _ = db.Exec(`CREATE INDEX MovementDayIndex ON UserMovement (Day)`)
}
row = db.QueryRow(`SELECT 'PerformanceDayIndex'::regclass`)
if err := row.Scan(&t); err != nil {
_, err = db.Exec(`CREATE INDEX PerformanceDayIndex ON Performance (Day)`)
_, _ = db.Exec(`CREATE INDEX PerformanceDayIndex ON Performance (Day)`)
}
row = db.QueryRow(`SELECT 'BlockStatsDayIndex'::regclass`)
if err := row.Scan(&t); err != nil {
_, err = db.Exec(`CREATE INDEX BlockStatsDayIndex ON BlockStats (Day)`)
_, _ = db.Exec(`CREATE INDEX BlockStatsDayIndex ON BlockStats (Day)`)
}
return err
return nil
}
func maxIndexedDay(db *sql.DB, table string) time.Time {
+4 -3
View File
@@ -89,7 +89,7 @@ var funcs = map[string]interface{}{
parts = append(parts, part)
}
if len(input) > 0 {
parts = append(parts, input[:])
parts = append(parts, input)
}
return parts[whichPart-1]
},
@@ -725,8 +725,8 @@ func getReport(db *sql.DB) map[string]interface{} {
if rep.NATType != "" {
natType := rep.NATType
natType = strings.Replace(natType, "unknown", "Unknown", -1)
natType = strings.Replace(natType, "Symetric", "Symmetric", -1)
natType = strings.ReplaceAll(natType, "unknown", "Unknown")
natType = strings.ReplaceAll(natType, "Symetric", "Symmetric")
add(featureGroups["Various"]["v3"], "NAT Type", natType, 1)
}
@@ -745,6 +745,7 @@ func getReport(db *sql.DB) map[string]interface{} {
inc(features["Folder"]["v3"], "Weak hash, custom threshold", rep.FolderUsesV3.CustomWeakHashThreshold)
inc(features["Folder"]["v3"], "Filesystem watcher", rep.FolderUsesV3.FsWatcherEnabled)
inc(features["Folder"]["v3"], "Case sensitive FS", rep.FolderUsesV3.CaseSensitiveFS)
inc(features["Folder"]["v3"], "Mode, receive encrypted", rep.FolderUsesV3.ReceiveEncrypted)
add(featureGroups["Folder"]["v3"], "Conflicts", "Disabled", rep.FolderUsesV3.ConflictsDisabled)
add(featureGroups["Folder"]["v3"], "Conflicts", "Unlimited", rep.FolderUsesV3.ConflictsUnlimited)
+3 -1
View File
@@ -7,7 +7,9 @@ After=network.target
User=%i
ExecStart=/usr/bin/syncthing serve --no-browser --no-restart --logflags=0
Restart=on-failure
RestartSec=5
RestartSec=1
StartLimitIntervalSec=60
StartLimitBurst=4
SuccessExitStatus=3 4
RestartForceExitStatus=3 4
+3 -1
View File
@@ -5,7 +5,9 @@ Documentation=man:syncthing(1)
[Service]
ExecStart=/usr/bin/syncthing serve --no-browser --no-restart --logflags=0
Restart=on-failure
RestartSec=5
RestartSec=1
StartLimitIntervalSec=60
StartLimitBurst=4
SuccessExitStatus=3 4
RestartForceExitStatus=3 4
+3 -1
View File
@@ -28,6 +28,7 @@ require (
github.com/lucas-clemente/quic-go v0.19.3
github.com/maruel/panicparse v1.5.1
github.com/mattn/go-isatty v0.0.12
github.com/maxbrunsfeld/counterfeiter/v6 v6.3.0 // indirect
github.com/minio/sha256-simd v0.1.1
github.com/miscreant/miscreant.go v0.0.0-20200214223636-26d376326b75
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
@@ -39,7 +40,7 @@ require (
github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0
github.com/sasha-s/go-deadlock v0.2.0
github.com/shirou/gopsutil/v3 v3.20.11
github.com/syncthing/notify v0.0.0-20201210100135-17de26665ddc
github.com/syncthing/notify v0.0.0-20210308121556-f45149b04939
github.com/syndtr/goleveldb v1.0.1-0.20200815071216-d9e9293bd0f7
github.com/thejerf/suture/v4 v4.0.0
github.com/urfave/cli v1.22.4
@@ -49,6 +50,7 @@ require (
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4
golang.org/x/text v0.3.4
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e
golang.org/x/tools v0.1.0 // indirect
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b // indirect
)
+15 -2
View File
@@ -261,6 +261,8 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/maxbrunsfeld/counterfeiter/v6 v6.3.0 h1:8E6DrFvII6QR4eJ3PkFvV+lc03P+2qwqTPLm1ax7694=
github.com/maxbrunsfeld/counterfeiter/v6 v6.3.0/go.mod h1:fcEyUyXZXoV4Abw8DX0t7wyL8mCDxXyU4iAFZfT3IHw=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
@@ -380,6 +382,7 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/sasha-s/go-deadlock v0.2.0 h1:lMqc+fUb7RrFS3gQLtoQsJ7/6TV/pAIFvBsqX73DK8Y=
github.com/sasha-s/go-deadlock v0.2.0/go.mod h1:StQn567HiB1fF2yJ44N9au7wOhrPS3iZqiDbRupzT10=
github.com/sclevine/spec v1.4.0/go.mod h1:LvpgJaFyvQzRvc1kaDs0bulYwzC70PbiYjC4QnFHkOM=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shirou/gopsutil/v3 v3.20.11 h1:NeVf1K0cgxsWz+N3671ojRptdgzvp7BXL3KV21R0JnA=
@@ -429,8 +432,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/syncthing/notify v0.0.0-20201210100135-17de26665ddc h1:b6b5XVKqpwxC6keIYThA5/XhFue4zNWRwv8FfqlKoxA=
github.com/syncthing/notify v0.0.0-20201210100135-17de26665ddc/go.mod h1:Sn4ChoS7e4FxjCN1XHPVBT43AgnRLbuaB8pEc1Zcdjg=
github.com/syncthing/notify v0.0.0-20210308121556-f45149b04939 h1:InjitJPCBfhc1/DP0Z8OglJq5qvQD+J0o64TFyenf68=
github.com/syncthing/notify v0.0.0-20210308121556-f45149b04939/go.mod h1:J0q59IWjLtpRIJulohwqEZvjzwOfTEPp8SVhDJl+y0Y=
github.com/syndtr/goleveldb v1.0.1-0.20200815071216-d9e9293bd0f7 h1:udtnv1cokhJYqnUfCMCppJ71bFN9VKfG1BQ6UsYZnx8=
github.com/syndtr/goleveldb v1.0.1-0.20200815071216-d9e9293bd0f7/go.mod h1:u2MKkTVTVJWe5D1rCvame8WqhBd88EuIwODJZ1VHCPM=
github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
@@ -446,6 +449,7 @@ github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMI
github.com/vitrun/qart v0.0.0-20160531060029-bf64b92db6b0 h1:okhMind4q9H1OxF44gNegWkiP4H/gsTFLalHFa4OOUI=
github.com/vitrun/qart v0.0.0-20160531060029-bf64b92db6b0/go.mod h1:TTbGUfE+cXXceWtbTHq6lqcTvYPBKLNejBEbnUsQJtU=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
@@ -483,6 +487,8 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -507,6 +513,8 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201026091529-146b70c837a4/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102 h1:42cLlJJdEh+ySyeUUbEQ5bsTiq8voBeTuweGVkY6Puw=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -520,6 +528,7 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -582,7 +591,11 @@ golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBn
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+6
View File
@@ -437,3 +437,9 @@ ul.three-columns li, ul.two-columns li {
.form-horizontal {
margin-bottom: 10px;
}
/* Use the same style as Bootstrap uses for disabled <select>. */
.form-control option[disabled] {
background-color: #eeeeee;
opacity: 1;
}
+1 -1
View File
@@ -212,7 +212,7 @@
"Outgoing Rate Limit (KiB/s)": "Limite du débit d'envoi (Kio/s)",
"Override Changes": "Écraser les changements",
"Path": "Chemin",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Dans l'écran de \"Personnalisation\" des valeurs par défaut (menu Actions/Configuration), ce champ indique le chemin dans lequel les partages acceptés automatiquement seront créés, ainsi que chemin de base suggéré lors de l'enregistrement des nouveaux partages. Le caractère tilde (~) est un raccourci pour {{tilde}}.\nEn création/acceptation manuelle d'un nouveau partage, c'est le chemin vers le répertoire à partager dans l'appareil local. Il sera créé s'il n'existe pas. Vous pouvez entrer un chemin absolu (p.ex \"/home/moi/Sync/Exemple\") ou relatif à celui du programme (p.ex \"..\\Partages\\Exemple\" - utile pour installation portable). Le caractère tilde (~, ou ~+Espace sous Windows) peut être utilisé comme raccourci vers",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Dans l'écran de \"Personnalisation\" des valeurs par défaut (menu Actions/Configuration), ce champ indique le chemin dans lequel les partages acceptés automatiquement seront créés, ainsi que chemin de base suggéré lors de l'enregistrement des nouveaux partages. Le caractère tilde (~) est un raccourci pour votre répertoire personnel {{tilde}} .\nEn création/acceptation manuelle d'un nouveau partage, c'est le chemin vers le répertoire à partager dans l'appareil local. Il sera créé s'il n'existe pas. Vous pouvez entrer un chemin absolu (p.ex \"/home/moi/Sync/Exemple\") ou relatif à celui du programme (p.ex \"..\\Partages\\Exemple\" - utile pour installation portable). Le caractère tilde (~, taper ~+Espace sous Windows) peut être utilisé comme raccourci vers",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Chemin dans lequel les partages acceptés automatiquement seront créés, ainsi que chemin suggéré lors de l'enregistrement des nouveaux partages via cette interface graphique. Le caractère tilde (~) est un raccourci pour {{tilde}}.",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Chemin où les versions seront conservées (laisser vide pour le chemin par défaut de .stversions (caché) dans le partage).\nChemin relatif ou absolu (recommandé), mais dans un répertoire non synchronisé (par masque ou hors du chemin du partage).\nSur la même partition ou système de fichiers (recommandé).",
"Pause": "Pause",
+1 -1
View File
@@ -406,7 +406,7 @@
"files": "filer",
"full documentation": "all dokumentasjon",
"items": "elementer",
"seconds": "seconds",
"seconds": "sekunder",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} ønsker å dele mappa \"{{folder}}\".",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} ønsker å dele mappa \"{{folderlabel}}\" ({{folder}}).",
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
+23 -23
View File
@@ -1,7 +1,7 @@
{
"A device with that ID is already added.": "Urządzenie o tym ID już istnieje.",
"A negative number of days doesn't make sense.": "Ujemna liczba dni nie ma sensu.",
"A new major version may not be compatible with previous versions.": "Nowa ważna wersja może nie być kompatybilna z poprzednimi wersjami.",
"A new major version may not be compatible with previous versions.": "Nowa duża wersja może nie być kompatybilna z poprzednimi wersjami.",
"API Key": "Klucz API",
"About": "O Syncthing",
"Action": "Akcja",
@@ -37,7 +37,7 @@
"Automatic upgrades are always enabled for candidate releases.": "Automatyczne aktualizacje są zawsze włączone dla wydań kandydujących.",
"Automatically create or share folders that this device advertises at the default path.": "Automatycznie utwórz lub współdziel foldery wysyłane przez to urządzenie w domyślnej ścieżce.",
"Available debug logging facilities:": "Dostępne narzędzia logujące do debugowania:",
"Be careful!": "Uwaga!",
"Be careful!": "Ostrożnie!",
"Bugs": "Błędy",
"Changelog": "Historia zmian",
"Clean out after": "Opróżnij po",
@@ -72,7 +72,7 @@
"Deselect devices to stop sharing this folder with.": "Odznacz urządzenia, z którymi chcesz przestać współdzielić ten folder.",
"Deselect folders to stop sharing with this device.": "Odznacz foldery, które chcesz przestać współdzielić z tym urządzeniem.",
"Device": "Urządzenie",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Urządzenie \"{{name}}\" {{device}} ({{address}}) chce się połączyć. Dodać nowe urządzenie?",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Urządzenie \"{{name}}\" {{device}} pod ({{address}}) chce się połączyć. Dodać nowe urządzenie?",
"Device ID": "ID urządzenia",
"Device Identification": "Identyfikator urządzenia",
"Device Name": "Nazwa urządzenia",
@@ -109,7 +109,7 @@
"Enable NAT traversal": "Włącz trawersowanie NAT",
"Enable Relaying": "Włącz przekazywanie",
"Enabled": "Włączone",
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Wprowadź nieujemną wartość liczbową (np. \"2.35\") oraz wybierz jednostkę. Wartość procentowa odnosi się do rozmiaru całego dysku.",
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Wprowadź nieujemną liczbę (np. \"2.35\") oraz wybierz jednostkę. Procenty odnos się do rozmiaru całego dysku.",
"Enter a non-privileged port number (1024 - 65535).": "Wprowadź nieuprzywilejowany numer portu (1024-65535).",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Wprowadź adresy oddzielone przecinkiem (\"tcp://ip:port\", \"tcp://host:port\") lub \"dynamic\" w celu automatycznego odnajdywania adresu.",
"Enter ignore patterns, one per line.": "Wprowadź wzorce ignorowania, po jednym w każdej linii.",
@@ -123,8 +123,8 @@
"File Versioning": "Wersjonowanie plików",
"Files are moved to .stversions directory when replaced or deleted by Syncthing.": "Pliki zmienione lub usunięte przez Syncthing są przenoszone do katalogu .stversions.",
"Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.": "Pliki zmienione lub usunięte przez Syncthing są datowane i przenoszone do katalogu .stversions.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Pliki są zabezpieczone przed zmianami dokonanymi na innych urządzeniach, ale zmiany dokonane na tym urządzeniu będą wysyłane do reszty urządzeń.",
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Pliki są synchronizowane z resztą urządzeń, ale jakiekolwiek zmiany dokonane lokalnie nie będą wysyłanie do innych urządzeń.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Pliki są zabezpieczone przed zmianami dokonanymi na innych urządzeniach, ale zmiany dokonane na tym urządzeniu będą wysyłane do pozostałych urządzeń.",
"Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.": "Pliki są synchronizowane z pozostałych urządzeń, ale jakiekolwiek zmiany dokonane lokalnie nie będą wysyłanie do innych urządzeń.",
"Filesystem Watcher Errors": "Błędy obserwatora plików",
"Filter by date": "Filtruj według daty",
"Filter by name": "Filtruj według nazwy",
@@ -133,7 +133,7 @@
"Folder Label": "Etykieta folderu",
"Folder Path": "Ścieżka folderu",
"Folder Type": "Rodzaj folderu",
"Folder type \"{%receiveEncrypted%}\" cannot be changed after adding the folder. You need to remove the folder, delete or decrypt the data on disk, and add the folder again.": "Typ folderu \"{{receiveEncrypted}}\" nie może zostać zmieniony po dodaniu folderu. Musisz najpierw usunąć folder, skasować bądź też odszyfrować dane na dysku, a następnie dodać folder ponownie.",
"Folder type \"{%receiveEncrypted%}\" cannot be changed after adding the folder. You need to remove the folder, delete or decrypt the data on disk, and add the folder again.": "Rodzaj folderu \"{{receiveEncrypted}}\" nie może być zmieniony po dodaniu folderu. Musisz najpierw usunąć folder, skasować bądź też odszyfrować dane na dysku, a następnie dodać folder ponownie.",
"Folders": "Foldery",
"For the following folders an error occurred while starting to watch for changes. It will be retried every minute, so the errors might go away soon. If they persist, try to fix the underlying issue and ask for help if you can't.": "Wystąpił błąd podczas rozpoczynania obserwowania zmian w następujących folderach. Akcja będzie ponawiana co minutę, więc błędy mogą niebawem zniknąć. Jeżeli nie uda pozbyć się błędów, spróbuj naprawić ukryty problem lub poproś o pomoc, jeżeli nie będziesz w stanie tego zrobić.",
"Full Rescan Interval (s)": "Przedział czasowy pełnego skanowania (s)",
@@ -146,12 +146,12 @@
"General": "Ogólne",
"Generate": "Generuj",
"Global Discovery": "Odnajdywanie globalne",
"Global Discovery Servers": "Serwery globalnego odnajdywania",
"Global Discovery Servers": "Serwery odnajdywania globalnego",
"Global State": "Stan globalny",
"Help": "Pomoc",
"Home page": "Strona domowa",
"However, your current settings indicate you might not want it enabled. We have disabled automatic crash reporting for you.": "Niemniej jednak, obecne ustawienia wskazują, że możesz nie chcieć włączać tej funkcji. Automatyczne zgłaszanie awarii zostało wyłączone na tym urządzeniu.",
"If untrusted, enter encryption password": "Jeśli folder jest niezaufany, wprowadź szyfrujące hasło",
"If untrusted, enter encryption password": "Jeżeli folder jest niezaufany, wprowadź szyfrujące hasło",
"If you want to prevent other users on this computer from accessing Syncthing and through it your files, consider setting up authentication.": "Jeżeli chcesz zakazać innym użytkownikom tego komputera dostępu do Syncthing, a przez niego do swoich plików, zastanów się nad włączeniem uwierzytelniania.",
"Ignore": "Ignoruj",
"Ignore Patterns": "Wzorce ignorowania",
@@ -170,7 +170,7 @@
"Last Scan": "Ostatnie skanowanie",
"Last seen": "Ostatnio widziany",
"Latest Change": "Ostatnia zmiana",
"Learn more": "Zobacz więcej",
"Learn more": "Dowiedz się więcej",
"Limit": "Ograniczenie",
"Listeners": "Nasłuchujący",
"Loading data...": "Ładowanie danych...",
@@ -183,7 +183,7 @@
"Log": "Log",
"Log tailing paused. Scroll to the bottom to continue.": "Zatrzymano wypisywanie logów. Przewiń w dół, aby je wznowić.",
"Logs": "Logi",
"Major Upgrade": "Ważna aktualizacja",
"Major Upgrade": "Duża aktualizacja",
"Mass actions": "Działania masowe",
"Maximum Age": "Maksymalny wiek",
"Metadata Only": "Tylko metadane",
@@ -213,7 +213,7 @@
"Override Changes": "Nadpisz zmiany",
"Path": "Ścieżka",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Ścieżka do folderu na komputerze lokalnym. Zostanie utworzona, jeżeli jeszcze nie istnieje. Znak tyldy (~) może zostać użyty jako skrót do",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Ścieżka, w której zostaną utworzone nowe automatycznie akceptowane foldery, a także domyślna sugerowana ścieżka podczas dodawania nowych folderów za pośrednictwem interfejsu użytkownika. Znak tyldy (~) rozwija się do {{tilde}}.",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Ścieżka, w której zostaną utworzone nowe autoakceptowane foldery, a także domyślna sugerowana ścieżka podczas dodawania nowych folderów za pośrednictwem interfejsu użytkownika. Znak tyldy (~) rozwija się do {{tilde}}.",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Ścieżka przechowywania wersji (pozostaw pustą dla domyślnego katalogu .stversions we współdzielonym folderze).",
"Pause": "Zatrzymaj",
"Pause All": "Zatrzymaj wszystkie",
@@ -224,7 +224,7 @@
"Periodic scanning at given interval and enabled watching for changes": "Okresowe skanowanie w podanym przedziale czasowym i włączone obserwowanie zmian",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Okresowe skanowanie w podanym przedziale czasowym i nieudane ustawienie obserwowania zmian, ponawiam co minutę:",
"Permissions": "Uprawnienia",
"Please consult the release notes before performing a major upgrade.": "Zapoznaj się z historią zmian przed przeprowadzeniem ważnej aktualizacji.",
"Please consult the release notes before performing a major upgrade.": "Zapoznaj się z informacjami o wersji przed przeprowadzeniem dużej aktualizacji.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Ustaw użytkownika i hasło do uwierzytelniania GUI w oknie Ustawień.",
"Please wait": "Proszę czekać",
"Prefix indicating that the file can be deleted if preventing directory removal": "Prefiks wskazujący, że plik może zostać usunięty, gdy blokuje on usunięcie katalogu",
@@ -239,7 +239,7 @@
"Received data is already encrypted": "Odebrane dane są już zaszyfrowane",
"Recent Changes": "Ostatnie zmiany",
"Reduced by ignore patterns": "Ograniczono przez wzorce ignorowania",
"Release Notes": "Historia zmian",
"Release Notes": "Informacje o wersji",
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Wydania kandydujące zawierają najnowsze funkcje oraz poprawki błędów. Są one podobne do tradycyjnych codwutygodniowych wydań Syncthing.",
"Remote Devices": "Urządzenia zdalne",
"Remote GUI": "Zdalne GUI",
@@ -312,7 +312,7 @@
"Syncthing is upgrading.": "Syncthing jest aktualizowany.",
"Syncthing now supports automatically reporting crashes to the developers. This feature is enabled by default.": "Syncthing zawiera teraz automatyczne zgłaszanie awarii do autorów. Ta funkcja jest domyślnie włączona.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing wydaje się być wyłączony lub wystąpił problem z połączeniem internetowym. Próbuję ponownie…",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing ma trudności z przetworzeniem tego zapytania. Odśwież stronę lub uruchom Syncthing ponownie, jeśli problem nie ustąpi.",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing wydaje się mieć trudności z przetworzeniem tego zapytania. Odśwież stronę lub uruchom Syncthing ponownie, jeżeli problem nie ustąpi.",
"Take me back": "Powrót",
"The GUI address is overridden by startup options. Changes here will not take effect while the override is in place.": "Adres GUI jest nadpisywany przez opcje uruchamiania. Zmiany dokonane tutaj nie będą obowiązywać, dopóki nadpisywanie jest w użyciu.",
"The Syncthing Authors": "The Syncthing Authors",
@@ -322,7 +322,7 @@
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Ustawienia zostały zapisane, ale nie są jeszcze aktywne. Syncthing musi zostać uruchomiony ponownie, aby aktywować nowe ustawienia.",
"The device ID cannot be blank.": "ID urządzenia nie może być puste.",
"The device ID to enter here can be found in the \"Actions > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "ID urządzenia do wpisania tutaj można znaleźć w oknie \"Akcje > Pokaż ID\" na innym urządzeniu. Spacje i myślniki są opcjonalne (ignorowane).",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Zaszyfrowane statystyki użycia są wysyłane codziennie. Używane są one do śledzenia popularności systemów, rozmiarów folderów oraz wersji programu. Jeżeli wysyłane statystyki ulegną zmianie, to zostaniesz poproszony o ponowne udzielenie zgody w tym oknie.",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Zaszyfrowane statystyki użycia są wysyłane codziennie. Używane są one do śledzenia popularności systemów, rozmiarów folderów oraz wersji programu. Jeżeli wysyłane statystyki ulegną zmianie, zostaniesz poproszony o ponowne udzielenie zgody w tym oknie.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "Wprowadzone ID urządzenia wygląda na niepoprawne. Musi ono zawierać 52 lub 56 znaków składających się z liter i cyfr. Spacje i myślniki są opcjonalne.",
"The folder ID cannot be blank.": "ID folderu nie może być puste.",
"The folder ID must be unique.": "ID folderu musi być unikatowe.",
@@ -333,26 +333,26 @@
"The following unexpected items were found.": "Znaleziono następujące elementy nieoczekiwane.",
"The interval must be a positive number of seconds.": "Przedział czasowy musi być dodatnią liczbą sekund.",
"The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.": "Przedział czasowy, w sekundach, w którym nastąpi czyszczenie katalogu wersjonowania. Ustaw na zero, aby wyłączyć czyszczenie okresowe.",
"The maximum age must be a number and cannot be blank.": "Maksymalny wiek musi być wartością liczbową oraz nie może być pusty.",
"The maximum age must be a number and cannot be blank.": "Maksymalny wiek musi być liczbą oraz nie może być pusty.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Maksymalny czas zachowania wersji (w dniach, ustaw na 0, aby zachować na zawsze).",
"The number of days must be a number and cannot be blank.": "Liczba dni musi być wartością liczbową oraz nie może być pusta.",
"The number of days to keep files in the trash can. Zero means forever.": "Liczba dni, przez które pliki trzymane będą w koszu. Zero oznacza nieskończoność.",
"The number of old versions to keep, per file.": "Liczba starszych wersji do zachowania, dla pojedynczego pliku.",
"The number of versions must be a number and cannot be blank.": "Liczba wersji musi być wartością liczbową oraz nie może być pusta.",
"The path cannot be blank.": "Ścieżka nie może być pusta.",
"The rate limit must be a non-negative number (0: no limit)": "Ograniczenie prędkości musi być nieujemną wartością liczbową (0: brak ograniczeń)",
"The rate limit must be a non-negative number (0: no limit)": "Ograniczenie prędkości musi być nieujemną liczbą (0: brak ograniczeń)",
"The rescan interval must be a non-negative number of seconds.": "Przedział czasowy ponownego skanowania musi być nieujemną liczbą sekund.",
"There are no devices to share this folder with.": "Brak urządzeń, z którymi możesz współdzielić ten folder.",
"There are no folders to share with this device.": "Brak folderów, które możesz współdzielić z tym urządzeniem.",
"They are retried automatically and will be synced when the error is resolved.": "Ponowne próby zachodzą automatycznie. Synchronizacja nastąpi po usunięciu usterki.",
"They are retried automatically and will be synced when the error is resolved.": "Ponowne próby zachodzą automatycznie. Synchronizacja nastąpi po usunięciu błędu.",
"This Device": "To urządzenie",
"This can easily give hackers access to read and change any files on your computer.": "Może to umożliwić hakerom dostęp do odczytu i zmian dowolnych plików na tym komputerze.",
"This is a major version upgrade.": "To jest ważna aktualizacja.",
"This is a major version upgrade.": "To jest duża aktualizacja.",
"This setting controls the free space required on the home (i.e., index database) disk.": "To ustawienie kontroluje ilość wolnej przestrzeni na dysku domowym (np. do indeksowania bazy danych).",
"Time": "Czas",
"Time the item was last modified": "Czas ostatniej modyfikacji elementu",
"Trash Can File Versioning": "Wersjonowanie plików w koszu",
"Type": "Typ",
"Type": "Rodzaj",
"UNIX Permissions": "UNIX-owe uprawnienia",
"Unavailable": "Niedostępne",
"Unavailable/Disabled by administrator or maintainer": "Niedostępne/wyłączone przez administratora lub serwisanta",
@@ -386,7 +386,7 @@
"Warning, this path is a parent directory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Uwaga, ta ścieżka to nadkatalog istniejącego folderu \"{{otherFolderLabel}}\" ({{otherFolder}}).",
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Uwaga, ta ścieżka to podkatalog istniejącego folderu \"{{otherFolder}}\".",
"Warning, this path is a subdirectory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Uwaga, ten folder to podkatalog istniejącego folderu \"{{otherFolderLabel}}\" ({{otherFolder}}).",
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Uwaga: Jeżeli korzystasz z zewnętrznego obserwatora, takiego jak {{syncthingInotify}}, upewnij się, że jest on wyłączony.",
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Uwaga: Jeżeli korzystasz z zewnętrznego obserwatora, takiego jak {{syncthingInotify}}, upewnij się, że ta opcja jest wyłączona.",
"Watch for Changes": "Obserwuj zmiany",
"Watching for Changes": "Obserwowanie zmian",
"Watching for changes discovers most changes without periodic scanning.": "Obserwowanie wykrywa większość zmian bez potrzeby okresowego skanowania.",
@@ -395,7 +395,7 @@
"Yes": "Tak",
"You can also select one of these nearby devices:": "Możesz również wybrać jedno z pobliskich urządzeń:",
"You can change your choice at any time in the Settings dialog.": "Możesz zmienić swój wybór w dowolnej chwili w oknie Ustawień.",
"You can read more about the two release channels at the link below.": "Możesz przeczytać więcej na temat obu kanałów wydawniczych pod poniższym adresem.",
"You can read more about the two release channels at the link below.": "Możesz przeczytać więcej na temat obu kanałów wydawniczych pod poniższym odnośnikiem.",
"You have no ignored devices.": "Brak ignorowanych urządzeń.",
"You have no ignored folders.": "Brak ignorowanych folderów.",
"You have unsaved changes. Do you really want to discard them?": "Masz niezapisane zmiany. Czy na pewno chcesz je odrzucić?",
@@ -600,7 +600,13 @@ angular.module('syncthing.core')
}
$scope.completion[device][folder] = data;
recalcCompletion(device);
}).error($scope.emitHTTPError);
}).error(function(data, status, headers, config) {
if (status === 404) {
console.log("refreshCompletion:", data);
} else {
$scope.emitHTTPError(data, status, headers, config);
}
});
}
function refreshConnectionStats() {
@@ -2047,6 +2053,9 @@ angular.module('syncthing.core')
folderCfg.devices = newDevices;
delete $scope.currentSharing;
if (!folderCfg.versioning) {
folderCfg.versioning = {params: {}};
}
folderCfg.versioning.type = folderCfg._guiVersioning.selector;
if ($scope.internalVersioningEnabled()) {
folderCfg.versioning.cleanupIntervalS = folderCfg._guiVersioning.cleanupIntervalS;
@@ -98,7 +98,7 @@
<option value="external" translate>External File Versioning</option>
</select>
</div>
<div class="form-group" ng-if="currentFolder._guiVersioning.selector=='trashcan' || currentFolder._guiVersioning.selectorector=='simple'" ng-class="{'has-error': folderEditor._guiVersioning.trashcanClean.$invalid && folderEditor._guiVersioning.trashcanClean.$dirty}">
<div class="form-group" ng-if="currentFolder._guiVersioning.selector=='trashcan' || currentFolder._guiVersioning.selector=='simple'" ng-class="{'has-error': folderEditor.trashcanClean.$invalid && folderEditor.trashcanClean.$dirty}">
<p translate class="help-block">Files are moved to .stversions directory when replaced or deleted by Syncthing.</p>
<label translate for="trashcanClean">Clean out after</label>
<div class="input-group">
@@ -106,30 +106,30 @@
<div class="input-group-addon" translate>days</div>
</div>
<p class="help-block">
<span translate ng-if="folderEditor._guiVersioning.trashcanClean.$valid || folderEditor._guiVersioning.trashcanClean.$pristine">The number of days to keep files in the trash can. Zero means forever.</span>
<span translate ng-if="folderEditor._guiVersioning.trashcanClean.$error.required && folderEditor._guiVersioning.trashcanClean.$dirty">The number of days must be a number and cannot be blank.</span>
<span translate ng-if="folderEditor._guiVersioning.trashcanClean.$error.min && folderEditor._guiVersioning.trashcanClean.$dirty">A negative number of days doesn't make sense.</span>
<span translate ng-if="folderEditor.trashcanClean.$valid || folderEditor.trashcanClean.$pristine">The number of days to keep files in the trash can. Zero means forever.</span>
<span translate ng-if="folderEditor.trashcanClean.$error.required && folderEditor.trashcanClean.$dirty">The number of days must be a number and cannot be blank.</span>
<span translate ng-if="folderEditor.trashcanClean.$error.min && folderEditor.trashcanClean.$dirty">A negative number of days doesn't make sense.</span>
</p>
</div>
<div class="form-group" ng-if="currentFolder._guiVersioning.selector=='simple'" ng-class="{'has-error': folderEditor._guiVersioning.simpleKeep.$invalid && folderEditor._guiVersioning.simpleKeep.$dirty}">
<div class="form-group" ng-if="currentFolder._guiVersioning.selector=='simple'" ng-class="{'has-error': folderEditor.simpleKeep.$invalid && folderEditor.simpleKeep.$dirty}">
<p translate class="help-block">Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.</p>
<label translate for="simpleKeep">Keep Versions</label>
<input name="simpleKeep" id="simpleKeep" class="form-control" type="number" ng-model="currentFolder._guiVersioning.simpleKeep" required="" aria-required="true" min="1" />
<p class="help-block">
<span translate ng-if="folderEditor._guiVersioning.simpleKeep.$valid || folderEditor._guiVersioning.simpleKeep.$pristine">The number of old versions to keep, per file.</span>
<span translate ng-if="folderEditor._guiVersioning.simpleKeep.$error.required && folderEditor._guiVersioning.simpleKeep.$dirty">The number of versions must be a number and cannot be blank.</span>
<span translate ng-if="folderEditor._guiVersioning.simpleKeep.$error.min && folderEditor._guiVersioning.simpleKeep.$dirty">You must keep at least one version.</span>
<span translate ng-if="folderEditor.simpleKeep.$valid || folderEditor.simpleKeep.$pristine">The number of old versions to keep, per file.</span>
<span translate ng-if="folderEditor.simpleKeep.$error.required && folderEditor.simpleKeep.$dirty">The number of versions must be a number and cannot be blank.</span>
<span translate ng-if="folderEditor.simpleKeep.$error.min && folderEditor.simpleKeep.$dirty">You must keep at least one version.</span>
</p>
</div>
<div class="form-group" ng-if="currentFolder._guiVersioning.selector=='staggered'" ng-class="{'has-error': folderEditor._guiVersioning.staggeredMaxAge.$invalid && folderEditor._guiVersioning.staggeredMaxAge.$dirty}">
<div class="form-group" ng-if="currentFolder._guiVersioning.selector=='staggered'" ng-class="{'has-error': folderEditor.staggeredMaxAge.$invalid && folderEditor.staggeredMaxAge.$dirty}">
<p class="help-block"><span translate>Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.</span> <span translate>Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.</span></p>
<p translate class="help-block">The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.</p>
<label translate for="staggeredMaxAge">Maximum Age</label>
<input name="staggeredMaxAge" id="staggeredMaxAge" class="form-control" type="number" ng-model="currentFolder._guiVersioning.staggeredMaxAge" required="" aria-required="true" min="0" />
<p class="help-block">
<span translate ng-if="folderEditor._guiVersioning.staggeredMaxAge.$valid || folderEditor._guiVersioning.staggeredMaxAge.$pristine">The maximum time to keep a version (in days, set to 0 to keep versions forever).</span>
<span translate ng-if="folderEditor._guiVersioning.staggeredMaxAge.$error.required && folderEditor._guiVersioning.staggeredMaxAge.$dirty">The maximum age must be a number and cannot be blank.</span>
<span translate ng-if="folderEditor._guiVersioning.staggeredMaxAge.$error.min && folderEditor._guiVersioning.staggeredMaxAge.$dirty">A negative number of days doesn't make sense.</span>
<span translate ng-if="folderEditor.staggeredMaxAge.$valid || folderEditor.staggeredMaxAge.$pristine">The maximum time to keep a version (in days, set to 0 to keep versions forever).</span>
<span translate ng-if="folderEditor.staggeredMaxAge.$error.required && folderEditor.staggeredMaxAge.$dirty">The maximum age must be a number and cannot be blank.</span>
<span translate ng-if="folderEditor.staggeredMaxAge.$error.min && folderEditor.staggeredMaxAge.$dirty">A negative number of days doesn't make sense.</span>
</p>
</div>
<div class="form-group" ng-if="internalVersioningEnabled()">
@@ -146,16 +146,16 @@
<span translate ng-if="folderEditor.externalCommand.$error.required && folderEditor.externalCommand.$dirty">The path cannot be blank.</span>
</p>
</div>
<div class="form-group" ng-if="internalVersioningEnabled()" ng-class="{'has-error': folderEditor._guiVersioning.cleanupIntervalS.$invalid && folderEditor._guiVersioning.cleanupIntervalS.$dirty}">
<label translate for="versioningCleanupIntervalS">Cleanup Interval</label>
<div class="form-group" ng-if="internalVersioningEnabled()" ng-class="{'has-error': folderEditor.cleanupIntervalS.$invalid && folderEditor.cleanupIntervalS.$dirty}">
<label translate for="cleanupIntervalS">Cleanup Interval</label>
<div class="input-group">
<input name="versioningCleanupIntervalS" id="versioningCleanupIntervalS" class="form-control text-right" type="number" ng-model="currentFolder._guiVersioning.cleanupIntervalS" required="" min="0" max="31536000" aria-required="true" />
<input name="cleanupIntervalS" id="cleanupIntervalS" class="form-control text-right" type="number" ng-model="currentFolder._guiVersioning.cleanupIntervalS" required="" min="0" max="31536000" aria-required="true" />
<div class="input-group-addon" translate>seconds</div>
</div>
<p class="help-block">
<span translate ng-if="folderEditor._guiVersioning.cleanupIntervalS.$valid || folderEditor._guiVersioning.cleanupIntervalS.$pristine"class="help-block">The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.</span>
<span translate ng-if="folderEditor._guiVersioning.cleanupIntervalS.$error.required && folderEditor._guiVersioning.cleanupIntervalS.$dirty">The cleanup interval cannot be blank.</span>
<span translate ng-if="folderEditor._guiVersioning.cleanupIntervalS.$error.min && folderEditor._guiVersioning.cleanupIntervalS.$dirty">The interval must be a positive number of seconds.</span>
<span translate ng-if="folderEditor.cleanupIntervalS.$valid || folderEditor.cleanupIntervalS.$pristine" class="help-block">The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.</span>
<span translate ng-if="folderEditor.cleanupIntervalS.$error.required && folderEditor.cleanupIntervalS.$dirty">The cleanup interval cannot be blank.</span>
<span translate ng-if="folderEditor.cleanupIntervalS.$error.min && folderEditor.cleanupIntervalS.$dirty">The interval must be a positive number of seconds.</span>
</p>
</div>
</div>
@@ -600,7 +600,13 @@ angular.module('syncthing.core')
}
$scope.completion[device][folder] = data;
recalcCompletion(device);
}).error($scope.emitHTTPError);
}).error(function(data, status, headers, config) {
if (status === 404) {
console.log("refreshCompletion:", data);
} else {
$scope.emitHTTPError(data, status, headers, config);
}
})
}
function refreshConnectionStats() {
@@ -2087,6 +2093,9 @@ angular.module('syncthing.core')
folderCfg.devices = newDevices;
delete $scope.currentSharing;
if (!folderCfg.versioning) {
folderCfg.versioning = {params: {}};
}
folderCfg.versioning.type = folderCfg._guiVersioning.selector;
if ($scope.internalVersioningEnabled()) {
folderCfg.versioning.cleanupIntervalS = folderCfg._guiVersioning.cleanupIntervalS;
@@ -86,7 +86,7 @@
<option value="external" translate>External File Versioning</option>
</select>
</div>
<div class="form-group" ng-if="currentFolder._guiVersioning.selector=='trashcan' || currentFolder._guiVersioning.selectorector=='simple'" ng-class="{'has-error': folderEditor._guiVersioning.trashcanClean.$invalid && folderEditor._guiVersioning.trashcanClean.$dirty}">
<div class="form-group" ng-if="currentFolder._guiVersioning.selector=='trashcan' || currentFolder._guiVersioning.selector=='simple'" ng-class="{'has-error': folderEditor.trashcanClean.$invalid && folderEditor.trashcanClean.$dirty}">
<p translate class="help-block">Files are moved to .stversions directory when replaced or deleted by Syncthing.</p>
<label translate for="trashcanClean">Clean out after</label>
<div class="input-group">
@@ -94,30 +94,30 @@
<div class="input-group-addon" translate>days</div>
</div>
<p class="help-block">
<span translate ng-if="folderEditor._guiVersioning.trashcanClean.$valid || folderEditor._guiVersioning.trashcanClean.$pristine">The number of days to keep files in the trash can. Zero means forever.</span>
<span translate ng-if="folderEditor._guiVersioning.trashcanClean.$error.required && folderEditor._guiVersioning.trashcanClean.$dirty">The number of days must be a number and cannot be blank.</span>
<span translate ng-if="folderEditor._guiVersioning.trashcanClean.$error.min && folderEditor._guiVersioning.trashcanClean.$dirty">A negative number of days doesn't make sense.</span>
<span translate ng-if="folderEditor.trashcanClean.$valid || folderEditor.trashcanClean.$pristine">The number of days to keep files in the trash can. Zero means forever.</span>
<span translate ng-if="folderEditor.trashcanClean.$error.required && folderEditor.trashcanClean.$dirty">The number of days must be a number and cannot be blank.</span>
<span translate ng-if="folderEditor.trashcanClean.$error.min && folderEditor.trashcanClean.$dirty">A negative number of days doesn't make sense.</span>
</p>
</div>
<div class="form-group" ng-if="currentFolder._guiVersioning.selector=='simple'" ng-class="{'has-error': folderEditor._guiVersioning.simpleKeep.$invalid && folderEditor._guiVersioning.simpleKeep.$dirty}">
<div class="form-group" ng-if="currentFolder._guiVersioning.selector=='simple'" ng-class="{'has-error': folderEditor.simpleKeep.$invalid && folderEditor.simpleKeep.$dirty}">
<p translate class="help-block">Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.</p>
<label translate for="simpleKeep">Keep Versions</label>
<input name="simpleKeep" id="simpleKeep" class="form-control" type="number" ng-model="currentFolder._guiVersioning.simpleKeep" required="" aria-required="true" min="1" />
<p class="help-block">
<span translate ng-if="folderEditor._guiVersioning.simpleKeep.$valid || folderEditor._guiVersioning.simpleKeep.$pristine">The number of old versions to keep, per file.</span>
<span translate ng-if="folderEditor._guiVersioning.simpleKeep.$error.required && folderEditor._guiVersioning.simpleKeep.$dirty">The number of versions must be a number and cannot be blank.</span>
<span translate ng-if="folderEditor._guiVersioning.simpleKeep.$error.min && folderEditor._guiVersioning.simpleKeep.$dirty">You must keep at least one version.</span>
<span translate ng-if="folderEditor.simpleKeep.$valid || folderEditor.simpleKeep.$pristine">The number of old versions to keep, per file.</span>
<span translate ng-if="folderEditor.simpleKeep.$error.required && folderEditor.simpleKeep.$dirty">The number of versions must be a number and cannot be blank.</span>
<span translate ng-if="folderEditor.simpleKeep.$error.min && folderEditor.simpleKeep.$dirty">You must keep at least one version.</span>
</p>
</div>
<div class="form-group" ng-if="currentFolder._guiVersioning.selector=='staggered'" ng-class="{'has-error': folderEditor._guiVersioning.staggeredMaxAge.$invalid && folderEditor._guiVersioning.staggeredMaxAge.$dirty}">
<div class="form-group" ng-if="currentFolder._guiVersioning.selector=='staggered'" ng-class="{'has-error': folderEditor.staggeredMaxAge.$invalid && folderEditor.staggeredMaxAge.$dirty}">
<p class="help-block"><span translate>Files are moved to date stamped versions in a .stversions directory when replaced or deleted by Syncthing.</span> <span translate>Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.</span></p>
<p translate class="help-block">The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.</p>
<label translate for="staggeredMaxAge">Maximum Age</label>
<input name="staggeredMaxAge" id="staggeredMaxAge" class="form-control" type="number" ng-model="currentFolder._guiVersioning.staggeredMaxAge" required="" aria-required="true" min="0" />
<p class="help-block">
<span translate ng-if="folderEditor._guiVersioning.staggeredMaxAge.$valid || folderEditor._guiVersioning.staggeredMaxAge.$pristine">The maximum time to keep a version (in days, set to 0 to keep versions forever).</span>
<span translate ng-if="folderEditor._guiVersioning.staggeredMaxAge.$error.required && folderEditor._guiVersioning.staggeredMaxAge.$dirty">The maximum age must be a number and cannot be blank.</span>
<span translate ng-if="folderEditor._guiVersioning.staggeredMaxAge.$error.min && folderEditor._guiVersioning.staggeredMaxAge.$dirty">A negative number of days doesn't make sense.</span>
<span translate ng-if="folderEditor.staggeredMaxAge.$valid || folderEditor.staggeredMaxAge.$pristine">The maximum time to keep a version (in days, set to 0 to keep versions forever).</span>
<span translate ng-if="folderEditor.staggeredMaxAge.$error.required && folderEditor.staggeredMaxAge.$dirty">The maximum age must be a number and cannot be blank.</span>
<span translate ng-if="folderEditor.staggeredMaxAge.$error.min && folderEditor.staggeredMaxAge.$dirty">A negative number of days doesn't make sense.</span>
</p>
</div>
<div class="form-group" ng-if="internalVersioningEnabled()">
@@ -134,16 +134,16 @@
<span translate ng-if="folderEditor.externalCommand.$error.required && folderEditor.externalCommand.$dirty">The path cannot be blank.</span>
</p>
</div>
<div class="form-group" ng-if="internalVersioningEnabled()" ng-class="{'has-error': folderEditor._guiVersioning.cleanupIntervalS.$invalid && folderEditor._guiVersioning.cleanupIntervalS.$dirty}">
<label translate for="versioningCleanupIntervalS">Cleanup Interval</label>
<div class="form-group" ng-if="internalVersioningEnabled()" ng-class="{'has-error': folderEditor.cleanupIntervalS.$invalid && folderEditor.cleanupIntervalS.$dirty}">
<label translate for="cleanupIntervalS">Cleanup Interval</label>
<div class="input-group">
<input name="versioningCleanupIntervalS" id="versioningCleanupIntervalS" class="form-control text-right" type="number" ng-model="currentFolder._guiVersioning.cleanupIntervalS" required="" min="0" max="31536000" aria-required="true" />
<input name="cleanupIntervalS" id="cleanupIntervalS" class="form-control text-right" type="number" ng-model="currentFolder._guiVersioning.cleanupIntervalS" required="" min="0" max="31536000" aria-required="true" />
<div class="input-group-addon" translate>seconds</div>
</div>
<p class="help-block">
<span translate ng-if="folderEditor._guiVersioning.cleanupIntervalS.$valid || folderEditor._guiVersioning.cleanupIntervalS.$pristine"class="help-block">The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.</span>
<span translate ng-if="folderEditor._guiVersioning.cleanupIntervalS.$error.required && folderEditor._guiVersioning.cleanupIntervalS.$dirty">The cleanup interval cannot be blank.</span>
<span translate ng-if="folderEditor._guiVersioning.cleanupIntervalS.$error.min && folderEditor._guiVersioning.cleanupIntervalS.$dirty">The interval must be a positive number of seconds.</span>
<span translate ng-if="folderEditor.cleanupIntervalS.$valid || folderEditor.cleanupIntervalS.$pristine"class="help-block">The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.</span>
<span translate ng-if="folderEditor.cleanupIntervalS.$error.required && folderEditor.cleanupIntervalS.$dirty">The cleanup interval cannot be blank.</span>
<span translate ng-if="folderEditor.cleanupIntervalS.$error.min && folderEditor.cleanupIntervalS.$dirty">The interval must be a positive number of seconds.</span>
</p>
</div>
</div>
@@ -205,7 +205,7 @@
<div class="col-md-6 form-group">
<label translate>Folder Type</label>
&nbsp;<a href="https://docs.syncthing.net/users/foldertypes.html" target="_blank"><span class="fas fa-question-circle"></span>&nbsp;<span translate>Help</span></a>
<select class="form-control" ng-change="setDefaultsForFolderType()" ng-model="currentFolder.type" ng-disabled="editingExisting && currentFolder.type === 'receiveencrypted'">
<select class="form-control" ng-change="setDefaultsForFolderType()" ng-model="currentFolder.type" ng-disabled="editingExisting && currentFolder.type == 'receiveencrypted'">
<option value="sendreceive" translate>Send &amp; Receive</option>
<option value="sendonly" translate>Send Only</option>
<option value="receiveonly" translate>Receive Only</option>
@@ -214,7 +214,8 @@
<p ng-if="currentFolder.type == 'sendonly'" translate class="help-block">Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.</p>
<p ng-if="currentFolder.type == 'receiveonly'" translate class="help-block">Files are synchronized from the cluster, but any changes made locally will not be sent to other devices.</p>
<p ng-if="currentFolder.type == 'receiveencrypted'" translate class="help-block" translate-value-receive-encrypted="{{'Receive Encrypted' | translate}}">Stores and syncs only encrypted data. Folders on all connected devices need to be set up with the same password or be of type "{%receiveEncrypted%}" too.</p>
<p ng-if="editingExisting" translate class="help-block" translate-value-receive-encrypted="{{'Receive Encrypted' | translate}}">Folder type "{%receiveEncrypted%}" cannot be changed after adding the folder. You need to remove the folder, delete or decrypt the data on disk, and add the folder again.</p>
<p ng-if="editingExisting && currentFolder.type == 'receiveencrypted'" translate class="help-block" translate-value-receive-encrypted="{{'Receive Encrypted' | translate}}">Folder type "{%receiveEncrypted%}" cannot be changed after adding the folder. You need to remove the folder, delete or decrypt the data on disk, and add the folder again.</p>
<p ng-if="editingExisting && currentFolder.type != 'receiveencrypted'" translate class="help-block" translate-value-receive-encrypted="{{'Receive Encrypted' | translate}}">Folder type "{%receiveEncrypted%}" can only be set when adding a new folder.</p>
</div>
<div class="col-md-6 form-group">
<label translate>File Pull Order</label>
+93 -10
View File
@@ -23,18 +23,21 @@ import (
"os"
"path/filepath"
"reflect"
"regexp"
"runtime"
"runtime/pprof"
"sort"
"strconv"
"strings"
"time"
"unicode"
"github.com/julienschmidt/httprouter"
metrics "github.com/rcrowley/go-metrics"
"github.com/thejerf/suture/v4"
"github.com/vitrun/qart/qr"
"golang.org/x/text/runes"
"golang.org/x/text/transform"
"golang.org/x/text/unicode/norm"
"github.com/syncthing/syncthing/lib/build"
"github.com/syncthing/syncthing/lib/config"
@@ -56,9 +59,6 @@ import (
"github.com/syncthing/syncthing/lib/ur"
)
// matches a bcrypt hash and not too much else
var bcryptExpr = regexp.MustCompile(`^\$2[aby]\$\d+\$.{50,}`)
const (
DefaultEventMask = events.AllEvents &^ events.LocalChangeDetected &^ events.RemoteChangeDetected
DiskEventMask = events.LocalChangeDetected | events.RemoteChangeDetected
@@ -153,6 +153,10 @@ func (s *service) getListener(guiCfg config.GUIConfiguration) (net.Listener, err
if err != nil {
name = s.tlsDefaultCommonName
}
name, err = sanitizedHostname(name)
if err != nil {
name = s.tlsDefaultCommonName
}
cert, err = tlsutil.NewCertificate(httpsCertFile, httpsKeyFile, name, httpsCertLifetimeDays)
}
@@ -298,7 +302,7 @@ func (s *service) Serve(ctx context.Context) error {
configBuilder.registerConfig("/rest/config")
configBuilder.registerConfigInsync("/rest/config/insync") // deprecated
configBuilder.registerConfigInsync("/rest/config/restart-required")
configBuilder.registerConfigRequiresRestart("/rest/config/restart-required")
configBuilder.registerFolders("/rest/config/folders")
configBuilder.registerDevices("/rest/config/devices")
configBuilder.registerFolder("/rest/config/folders/:id")
@@ -746,7 +750,15 @@ func (s *service) getDBCompletion(w http.ResponseWriter, r *http.Request) {
}
}
sendJSON(w, s.model.Completion(device, folder).Map())
if comp, err := s.model.Completion(device, folder); err != nil {
status := http.StatusInternalServerError
if isFolderNotFound(err) {
status = http.StatusNotFound
}
http.Error(w, err.Error(), status)
} else {
sendJSON(w, comp.Map())
}
}
func (s *service) getDBStatus(w http.ResponseWriter, r *http.Request) {
@@ -878,8 +890,25 @@ func (s *service) getDBFile(w http.ResponseWriter, r *http.Request) {
qs := r.URL.Query()
folder := qs.Get("folder")
file := qs.Get("file")
gf, gfOk := s.model.CurrentGlobalFile(folder, file)
lf, lfOk := s.model.CurrentFolderFile(folder, file)
errStatus := http.StatusInternalServerError
gf, gfOk, err := s.model.CurrentGlobalFile(folder, file)
if err != nil {
if isFolderNotFound(err) {
errStatus = http.StatusNotFound
}
http.Error(w, err.Error(), errStatus)
return
}
lf, lfOk, err := s.model.CurrentFolderFile(folder, file)
if err != nil {
if isFolderNotFound(err) {
errStatus = http.StatusNotFound
}
http.Error(w, err.Error(), errStatus)
return
}
if !(gfOk || lfOk) {
// This file for sure does not exist.
@@ -887,7 +916,11 @@ func (s *service) getDBFile(w http.ResponseWriter, r *http.Request) {
return
}
av := s.model.Availability(folder, gf, protocol.BlockInfo{})
av, err := s.model.Availability(folder, gf, protocol.BlockInfo{})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
sendJSON(w, map[string]interface{}{
"global": jsonFileInfo(gf),
"local": jsonFileInfo(lf),
@@ -1498,7 +1531,12 @@ func (s *service) getPeerCompletion(w http.ResponseWriter, r *http.Request) {
for _, device := range folder.DeviceIDs() {
deviceStr := device.String()
if _, ok := s.model.Connection(device); ok {
tot[deviceStr] += s.model.Completion(device, folder.ID).CompletionPct
comp, err := s.model.Completion(device, folder.ID)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
tot[deviceStr] += comp.CompletionPct
} else {
tot[deviceStr] = 0
}
@@ -1860,3 +1898,48 @@ func errorString(err error) *string {
}
return nil
}
// sanitizedHostname returns the given name in a suitable form for use as
// the common name in a certificate, or an error.
func sanitizedHostname(name string) (string, error) {
// Remove diacritics and non-alphanumerics. This works by first
// transforming into normalization form D (things with diacriticals are
// split into the base character and the mark) and then removing
// undesired characters.
t := transform.Chain(
// Split runes with diacritics into base character and mark.
norm.NFD,
// Leave only [A-Za-z0-9-.].
runes.Remove(runes.Predicate(func(r rune) bool {
return r > unicode.MaxASCII ||
!unicode.IsLetter(r) && !unicode.IsNumber(r) &&
r != '.' && r != '-'
})))
name, _, err := transform.String(t, name)
if err != nil {
return "", err
}
// Name should not start or end with a dash or dot.
name = strings.Trim(name, "-.")
// Name should not be empty.
if name == "" {
return "", errors.New("no suitable name")
}
return strings.ToLower(name), nil
}
func isFolderNotFound(err error) bool {
for _, target := range []error{
model.ErrFolderMissing,
model.ErrFolderPaused,
model.ErrFolderNotRunning,
} {
if errors.Is(err, target) {
return true
}
}
return false
}
+24 -5
View File
@@ -1262,11 +1262,9 @@ func TestConfigChanges(t *testing.T) {
defer os.Remove(tmpFile.Name())
w := config.Wrap(tmpFile.Name(), cfg, protocol.LocalDeviceID, events.NoopLogger)
tmpFile.Close()
if cfgService, ok := w.(suture.Service); ok {
cfgCtx, cfgCancel := context.WithCancel(context.Background())
go cfgService.Serve(cfgCtx)
defer cfgCancel()
}
cfgCtx, cfgCancel := context.WithCancel(context.Background())
go w.Serve(cfgCtx)
defer cfgCancel()
baseURL, cancel, err := startHTTP(w)
if err != nil {
t.Fatal("Unexpected error from getting base URL:", err)
@@ -1370,6 +1368,27 @@ func TestConfigChanges(t *testing.T) {
}
}
func TestSanitizedHostname(t *testing.T) {
cases := []struct {
in, out string
}{
{"foo.BAR-baz", "foo.bar-baz"},
{"~.~-Min 1:a Räksmörgås-dator 😀😎 ~.~-", "min1araksmorgas-dator"},
{"Vicenç-PC", "vicenc-pc"},
{"~.~-~.~-", ""},
{"", ""},
}
for _, tc := range cases {
res, err := sanitizedHostname(tc.in)
if tc.out == "" && err == nil {
t.Errorf("%q should cause error", tc.in)
} else if res != tc.out {
t.Errorf("%q => %q, expected %q", tc.in, res, tc.out)
}
}
}
func equalStrings(a, b []string) bool {
if len(a) != len(b) {
return false
+32
View File
@@ -0,0 +1,32 @@
// Copyright (C) 2021 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,
// You can obtain one at https://mozilla.org/MPL/2.0/.
//+build noassets
package auto
import (
"bytes"
"compress/gzip"
"github.com/syncthing/syncthing/lib/assets"
)
func Assets() map[string]assets.Asset {
// Return a minimal index.html and nothing else, to allow the trivial
// test to pass.
buf := new(bytes.Buffer)
gw := gzip.NewWriter(buf)
_, _ = gw.Write([]byte("<html></html>"))
_ = gw.Flush()
return map[string]assets.Asset{
"default/index.html": {
Gzipped: true,
Content: buf.String(),
},
}
}
+10
View File
@@ -51,6 +51,12 @@ func (c *configMuxBuilder) registerConfigInsync(path string) {
})
}
func (c *configMuxBuilder) registerConfigRequiresRestart(path string) {
c.HandlerFunc(http.MethodGet, path, func(w http.ResponseWriter, _ *http.Request) {
sendJSON(w, map[string]bool{"requiresRestart": c.cfg.RequiresRestart()})
})
}
func (c *configMuxBuilder) registerFolders(path string) {
c.HandlerFunc(http.MethodGet, path, func(w http.ResponseWriter, _ *http.Request) {
sendJSON(w, c.cfg.FolderList())
@@ -181,6 +187,10 @@ func (c *configMuxBuilder) registerDevice(path string) {
c.Handle(http.MethodDelete, path, func(w http.ResponseWriter, _ *http.Request, p httprouter.Params) {
id, err := protocol.DeviceIDFromString(p.ByName("id"))
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
waiter, err := c.cfg.RemoveDevice(id)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
+1 -8
View File
@@ -23,7 +23,6 @@ import (
"testing"
"github.com/d4l3k/messagediff"
"github.com/thejerf/suture/v4"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/fs"
@@ -1301,16 +1300,10 @@ func startWrapper(wrapper Wrapper) *testWrapper {
Wrapper: wrapper,
done: make(chan struct{}),
}
s, ok := wrapper.(suture.Service)
if !ok {
tw.cancel = func() {}
close(tw.done)
return tw
}
ctx, cancel := context.WithCancel(context.Background())
tw.cancel = cancel
go func() {
s.Serve(ctx)
wrapper.Serve(ctx)
close(tw.done)
}()
return tw
+2 -2
View File
@@ -47,11 +47,11 @@ func (f FolderConfiguration) Filesystem() fs.Filesystem {
// cfg.Folders["default"].Filesystem() should be valid.
var opts []fs.Option
if f.FilesystemType == fs.FilesystemTypeBasic && f.JunctionsAsDirs {
opts = append(opts, fs.WithJunctionsAsDirs())
opts = append(opts, new(fs.OptionJunctionsAsDirs))
}
filesystem := fs.NewFilesystem(f.FilesystemType, f.Path, opts...)
if !f.CaseSensitiveFS {
filesystem = fs.NewCaseFilesystem(filesystem, opts...)
filesystem = fs.NewCaseFilesystem(filesystem)
}
return filesystem
}
+75
View File
@@ -2,6 +2,7 @@
package mocks
import (
"context"
"sync"
"github.com/syncthing/syncthing/lib/config"
@@ -258,6 +259,17 @@ type Wrapper struct {
saveReturnsOnCall map[int]struct {
result1 error
}
ServeStub func(context.Context) error
serveMutex sync.RWMutex
serveArgsForCall []struct {
arg1 context.Context
}
serveReturns struct {
result1 error
}
serveReturnsOnCall map[int]struct {
result1 error
}
SubscribeStub func(config.Committer) config.Configuration
subscribeMutex sync.RWMutex
subscribeArgsForCall []struct {
@@ -1577,6 +1589,67 @@ func (fake *Wrapper) SaveReturnsOnCall(i int, result1 error) {
}{result1}
}
func (fake *Wrapper) Serve(arg1 context.Context) error {
fake.serveMutex.Lock()
ret, specificReturn := fake.serveReturnsOnCall[len(fake.serveArgsForCall)]
fake.serveArgsForCall = append(fake.serveArgsForCall, struct {
arg1 context.Context
}{arg1})
stub := fake.ServeStub
fakeReturns := fake.serveReturns
fake.recordInvocation("Serve", []interface{}{arg1})
fake.serveMutex.Unlock()
if stub != nil {
return stub(arg1)
}
if specificReturn {
return ret.result1
}
return fakeReturns.result1
}
func (fake *Wrapper) ServeCallCount() int {
fake.serveMutex.RLock()
defer fake.serveMutex.RUnlock()
return len(fake.serveArgsForCall)
}
func (fake *Wrapper) ServeCalls(stub func(context.Context) error) {
fake.serveMutex.Lock()
defer fake.serveMutex.Unlock()
fake.ServeStub = stub
}
func (fake *Wrapper) ServeArgsForCall(i int) context.Context {
fake.serveMutex.RLock()
defer fake.serveMutex.RUnlock()
argsForCall := fake.serveArgsForCall[i]
return argsForCall.arg1
}
func (fake *Wrapper) ServeReturns(result1 error) {
fake.serveMutex.Lock()
defer fake.serveMutex.Unlock()
fake.ServeStub = nil
fake.serveReturns = struct {
result1 error
}{result1}
}
func (fake *Wrapper) ServeReturnsOnCall(i int, result1 error) {
fake.serveMutex.Lock()
defer fake.serveMutex.Unlock()
fake.ServeStub = nil
if fake.serveReturnsOnCall == nil {
fake.serveReturnsOnCall = make(map[int]struct {
result1 error
})
}
fake.serveReturnsOnCall[i] = struct {
result1 error
}{result1}
}
func (fake *Wrapper) Subscribe(arg1 config.Committer) config.Configuration {
fake.subscribeMutex.Lock()
ret, specificReturn := fake.subscribeReturnsOnCall[len(fake.subscribeArgsForCall)]
@@ -1719,6 +1792,8 @@ func (fake *Wrapper) Invocations() map[string][][]interface{} {
defer fake.requiresRestartMutex.RUnlock()
fake.saveMutex.RLock()
defer fake.saveMutex.RUnlock()
fake.serveMutex.RLock()
defer fake.serveMutex.RUnlock()
fake.subscribeMutex.RLock()
defer fake.subscribeMutex.RUnlock()
fake.unsubscribeMutex.RLock()
+3
View File
@@ -20,6 +20,7 @@ import (
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/sync"
"github.com/thejerf/suture/v4"
)
const (
@@ -111,6 +112,8 @@ type Wrapper interface {
Subscribe(c Committer) Configuration
Unsubscribe(c Committer)
suture.Service
}
type wrapper struct {
+2 -7
View File
@@ -17,7 +17,6 @@ import (
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/thejerf/suture/v4"
"golang.org/x/time/rate"
)
@@ -45,12 +44,8 @@ func initConfig() (config.Wrapper, context.CancelFunc) {
dev3Conf = newDeviceConfiguration(wrapper, device3, "device3")
dev4Conf = newDeviceConfiguration(wrapper, device4, "device4")
var cancel context.CancelFunc = func() {}
if wrapperService, ok := wrapper.(suture.Service); ok {
var ctx context.Context
ctx, cancel = context.WithCancel(context.Background())
go wrapperService.Serve(ctx)
}
ctx, cancel := context.WithCancel(context.Background())
go wrapper.Serve(ctx)
dev2Conf.MaxRecvKbps = rand.Int() % 100000
dev2Conf.MaxSendKbps = rand.Int() % 100000
+1 -4
View File
@@ -90,10 +90,7 @@ func (d *quicDialer) Dial(ctx context.Context, _ protocol.DeviceID, uri *url.URL
return newInternalConn(&quicTlsConn{session, stream, createdConn}, connTypeQUICClient, quicPriority), nil
}
type quicDialerFactory struct {
cfg config.Wrapper
tlsCfg *tls.Config
}
type quicDialerFactory struct{}
func (quicDialerFactory) New(opts config.OptionsConfiguration, tlsCfg *tls.Config) genericDialer {
return &quicDialer{commonDialer{
+2 -2
View File
@@ -79,7 +79,7 @@ func (t *quicListener) OnExternalAddressChanged(address *stun.Host, via string)
}
func (t *quicListener) serve(ctx context.Context) error {
network := strings.Replace(t.uri.Scheme, "quic", "udp", -1)
network := strings.ReplaceAll(t.uri.Scheme, "quic", "udp")
packetConn, err := net.ListenPacket(network, t.uri.Host)
if err != nil {
@@ -174,7 +174,7 @@ func (t *quicListener) WANAddresses() []*url.URL {
func (t *quicListener) LANAddresses() []*url.URL {
addrs := []*url.URL{t.uri}
network := strings.Replace(t.uri.Scheme, "quic", "udp", -1)
network := strings.ReplaceAll(t.uri.Scheme, "quic", "udp")
addrs = append(addrs, getURLsForAllAdaptersIfUnspecified(network, t.uri)...)
return addrs
}
+4 -10
View File
@@ -244,7 +244,7 @@ func (s *service) handle(ctx context.Context) error {
// though, especially in the presence of NAT hairpinning, multiple
// clients between the same NAT gateway, and global discovery.
if remoteID == s.myID {
l.Infof("Connected to myself (%s) at %s - should not happen", remoteID, c)
l.Debugf("Connected to myself (%s) at %s", remoteID, c)
c.Close()
continue
}
@@ -335,13 +335,7 @@ func (s *service) handle(ctx context.Context) error {
isLAN := s.isLAN(c.RemoteAddr())
rd, wr := s.limiter.getLimiters(remoteID, c, isLAN)
var protoConn protocol.Connection
passwords := s.cfg.FolderPasswords(remoteID)
if len(passwords) > 0 {
protoConn = protocol.NewEncryptedConnection(passwords, remoteID, rd, wr, c, s.model, c, deviceCfg.Compression)
} else {
protoConn = protocol.NewConnection(remoteID, rd, wr, c, s.model, c, deviceCfg.Compression)
}
protoConn := protocol.NewConnection(remoteID, rd, wr, c, s.model, c, deviceCfg.Compression, s.cfg.FolderPasswords(remoteID))
l.Infof("Established secure connection to %s at %s", remoteID, c)
@@ -478,7 +472,7 @@ func (s *service) dialDevices(ctx context.Context, now time.Time, cfg config.Con
// doesn't have much effect, but it may result in getting up and running
// quicker if only a subset of configured devices are actually reachable
// (by prioritizing those that were reachable recently).
dialQueue.Sort(queue)
queue.Sort()
// Perform dials according to the queue, stopping when we've reached the
// allowed additional number of connections (if limited).
@@ -1023,7 +1017,7 @@ func (s *service) validateIdentity(c internalConn, expectedID protocol.DeviceID)
// though, especially in the presence of NAT hairpinning, multiple
// clients between the same NAT gateway, and global discovery.
if remoteID == s.myID {
l.Infof("Connected to myself (%s) at %s - should not happen", remoteID, c)
l.Debugf("Connected to myself (%s) at %s", remoteID, c)
c.Close()
return errors.New("connected to self")
}
+1 -1
View File
@@ -89,7 +89,7 @@ func newInternalConn(tc tlsConn, connType connType, priority int) internalConn {
tlsConn: tc,
connType: connType,
priority: priority,
establishedAt: time.Now(),
establishedAt: time.Now().Truncate(time.Second),
}
}
+9 -9
View File
@@ -15,7 +15,7 @@ import (
"github.com/syncthing/syncthing/lib/protocol"
)
var files, filesUpdated, oneFile, firstHalf, secondHalf, changed100, unchanged100 []protocol.FileInfo
var files, oneFile, firstHalf, secondHalf, changed100, unchanged100 []protocol.FileInfo
func lazyInitBenchFiles() {
if files != nil {
@@ -185,7 +185,7 @@ func BenchmarkNeedHalf(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
count := 0
snap := benchS.Snapshot()
snap := snapshot(b, benchS)
snap.WithNeed(protocol.LocalDeviceID, func(fi protocol.FileIntf) bool {
count++
return true
@@ -209,7 +209,7 @@ func BenchmarkNeedHalfRemote(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
count := 0
snap := fset.Snapshot()
snap := snapshot(b, fset)
snap.WithNeed(remoteDevice0, func(fi protocol.FileIntf) bool {
count++
return true
@@ -230,7 +230,7 @@ func BenchmarkHave(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
count := 0
snap := benchS.Snapshot()
snap := snapshot(b, benchS)
snap.WithHave(protocol.LocalDeviceID, func(fi protocol.FileIntf) bool {
count++
return true
@@ -251,7 +251,7 @@ func BenchmarkGlobal(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
count := 0
snap := benchS.Snapshot()
snap := snapshot(b, benchS)
snap.WithGlobal(func(fi protocol.FileIntf) bool {
count++
return true
@@ -272,7 +272,7 @@ func BenchmarkNeedHalfTruncated(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
count := 0
snap := benchS.Snapshot()
snap := snapshot(b, benchS)
snap.WithNeedTruncated(protocol.LocalDeviceID, func(fi protocol.FileIntf) bool {
count++
return true
@@ -293,7 +293,7 @@ func BenchmarkHaveTruncated(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
count := 0
snap := benchS.Snapshot()
snap := snapshot(b, benchS)
snap.WithHaveTruncated(protocol.LocalDeviceID, func(fi protocol.FileIntf) bool {
count++
return true
@@ -314,7 +314,7 @@ func BenchmarkGlobalTruncated(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
count := 0
snap := benchS.Snapshot()
snap := snapshot(b, benchS)
snap.WithGlobalTruncated(func(fi protocol.FileIntf) bool {
count++
return true
@@ -336,7 +336,7 @@ func BenchmarkNeedCount(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
snap := benchS.Snapshot()
snap := snapshot(b, benchS)
_ = snap.NeedSize(protocol.LocalDeviceID)
snap.Release()
}
+7 -4
View File
@@ -77,7 +77,7 @@ func TestIgnoredFiles(t *testing.T) {
// Local files should have the "ignored" bit in addition to just being
// generally invalid if we want to look at the simulation of that bit.
snap := fs.Snapshot()
snap := snapshot(t, fs)
defer snap.Release()
fi, ok := snap.Get(protocol.LocalDeviceID, "foo")
if !ok {
@@ -262,6 +262,9 @@ func TestUpdate0to3(t *testing.T) {
t.Fatal(err)
}
key, err = trans.keyer.GenerateDeviceFileKey(key, folder, vl.Versions[0].Device, name)
if err != nil {
t.Fatal(err)
}
fi, ok, err := trans.getFileTrunc(key, false)
if err != nil {
t.Fatal(err)
@@ -866,7 +869,7 @@ func TestCheckLocalNeed(t *testing.T) {
fs.Update(remoteDevice0, files)
checkNeed := func() {
snap := fs.Snapshot()
snap := snapshot(t, fs)
defer snap.Release()
c := snap.NeedSize(protocol.LocalDeviceID)
if c.Files != 2 {
@@ -974,7 +977,7 @@ func TestNeedAfterDropGlobal(t *testing.T) {
fs.Update(remoteDevice1, files[1:])
// remoteDevice1 needs one file: test
snap := fs.Snapshot()
snap := snapshot(t, fs)
c := snap.NeedSize(remoteDevice1)
if c.Files != 1 {
t.Errorf("Expected 1 needed files initially, got %v", c.Files)
@@ -986,7 +989,7 @@ func TestNeedAfterDropGlobal(t *testing.T) {
fs.Drop(remoteDevice0)
// remoteDevice1 still needs test.
snap = fs.Snapshot()
snap = snapshot(t, fs)
c = snap.NeedSize(remoteDevice1)
if c.Files != 1 {
t.Errorf("Expected still 1 needed files, got %v", c.Files)
+7
View File
@@ -846,6 +846,10 @@ func (db *Lowlevel) getMetaAndCheck(folder string) (*metadataTracker, error) {
db.gcMut.RLock()
defer db.gcMut.RUnlock()
return db.getMetaAndCheckGCLocked(folder)
}
func (db *Lowlevel) getMetaAndCheckGCLocked(folder string) (*metadataTracker, error) {
fixed, err := db.checkLocalNeed([]byte(folder))
if err != nil {
return nil, fmt.Errorf("checking local need: %w", err)
@@ -928,6 +932,9 @@ func (db *Lowlevel) recalcMeta(folderStr string) (*metadataTracker, error) {
meta.addFile(protocol.GlobalDeviceID, f)
return true
})
if err != nil {
return nil, err
}
meta.emptyNeeded(protocol.LocalDeviceID)
err = t.withNeed(folder, protocol.LocalDeviceID[:], true, func(f protocol.FileIntf) bool {
+2 -3
View File
@@ -148,10 +148,9 @@ func (m *metadataTracker) countsPtr(dev protocol.DeviceID, flag uint32) *Counts
// the metadatatracker, even if there's no change to the need
// bucket itself.
nkey := metaKey{dev, needFlag}
nidx, ok := m.indexes[nkey]
if !ok {
if _, ok := m.indexes[nkey]; !ok {
// Initially a new device needs everything, except deletes
nidx = len(m.counts.Counts)
nidx := len(m.counts.Counts)
m.counts.Counts = append(m.counts.Counts, m.allNeededCounts(dev))
m.indexes[nkey] = nidx
}
+3 -3
View File
@@ -117,7 +117,7 @@ func TestRecalcMeta(t *testing.T) {
s1.Update(protocol.LocalDeviceID, files)
// Verify local/global size
snap := s1.Snapshot()
snap := snapshot(t, s1)
ls := snap.LocalSize()
gs := snap.GlobalSize()
snap.Release()
@@ -149,7 +149,7 @@ func TestRecalcMeta(t *testing.T) {
}
// Verify that our bad data "took"
snap = s1.Snapshot()
snap = snapshot(t, s1)
ls = snap.LocalSize()
gs = snap.GlobalSize()
snap.Release()
@@ -164,7 +164,7 @@ func TestRecalcMeta(t *testing.T) {
s2 := newFileSet(t, "test", fs.NewFilesystem(fs.FilesystemTypeFake, "fake"), ldb)
// Verify local/global size
snap = s2.Snapshot()
snap = snapshot(t, s2)
ls = snap.LocalSize()
gs = snap.GlobalSize()
snap.Release()
+2 -2
View File
@@ -15,7 +15,7 @@ import (
func (db *Lowlevel) AddOrUpdatePendingDevice(device protocol.DeviceID, name, address string) error {
key := db.keyer.GeneratePendingDeviceKey(nil, device[:])
od := ObservedDevice{
Time: time.Now().Round(time.Second),
Time: time.Now().Truncate(time.Second),
Name: name,
Address: address,
}
@@ -72,7 +72,7 @@ func (db *Lowlevel) AddOrUpdatePendingFolder(id, label string, device protocol.D
return err
}
of := ObservedFolder{
Time: time.Now().Round(time.Second),
Time: time.Now().Truncate(time.Second),
Label: label,
ReceiveEncrypted: receiveEncrypted,
}
+15 -4
View File
@@ -8,7 +8,6 @@ package db
import (
"bytes"
"errors"
"fmt"
"sort"
"strings"
@@ -21,7 +20,7 @@ import (
// do not put restrictions on downgrades (e.g. for repairs after a bugfix).
const (
dbVersion = 14
dbMigrationVersion = 15
dbMigrationVersion = 16
dbMinSyncthingVersion = "v1.9.0"
)
@@ -32,8 +31,6 @@ type migration struct {
migration func(prevSchema int) error
}
var errFolderMissing = errors.New("folder present in global list but missing in keyer index")
type databaseDowngradeError struct {
minSyncthingVersion string
}
@@ -104,6 +101,7 @@ func (db *schemaUpdater) updateSchema() error {
{13, 13, "v1.7.0", db.updateSchemaTo13},
{14, 14, "v1.9.0", db.updateSchemaTo14},
{14, 15, "v1.9.0", db.migration15},
{14, 16, "v1.9.0", db.checkRepairMigration},
}
for _, m := range migrations {
@@ -729,6 +727,9 @@ func (db *schemaUpdater) updateSchemaTo14(_ int) error {
defer t.close()
key, err = t.keyer.GenerateDeviceFileKey(key, folder, protocol.LocalDeviceID[:], nil)
if err != nil {
return err
}
it, err := t.NewPrefixIterator(key)
if err != nil {
return err
@@ -782,6 +783,16 @@ func (db *schemaUpdater) migration15(_ int) error {
return nil
}
func (db *schemaUpdater) checkRepairMigration(_ int) error {
for _, folder := range db.ListFolders() {
_, err := db.getMetaAndCheckGCLocked(folder)
if err != nil {
return err
}
}
return nil
}
func (db *schemaUpdater) rewriteGlobals(t readWriteTransaction) error {
it, err := t.NewPrefixIterator([]byte{KeyTypeGlobal})
if err != nil {
+4 -3
View File
@@ -151,12 +151,13 @@ type Snapshot struct {
fatalError func(error, string)
}
func (s *FileSet) Snapshot() *Snapshot {
func (s *FileSet) Snapshot() (*Snapshot, error) {
opStr := fmt.Sprintf("%s Snapshot()", s.folder)
l.Debugf(opStr)
t, err := s.db.newReadOnlyTransaction()
if err != nil {
fatalError(err, opStr, s.db)
s.db.handleFailure(err)
return nil, err
}
return &Snapshot{
folder: s.folder,
@@ -165,7 +166,7 @@ func (s *FileSet) Snapshot() *Snapshot {
fatalError: func(err error, opStr string) {
fatalError(err, opStr, s.db)
},
}
}, nil
}
func (s *Snapshot) Release() {
+121 -111
View File
@@ -45,9 +45,9 @@ func genBlocks(n int) []protocol.BlockInfo {
return b
}
func globalList(s *db.FileSet) []protocol.FileInfo {
func globalList(t testing.TB, s *db.FileSet) []protocol.FileInfo {
var fs []protocol.FileInfo
snap := s.Snapshot()
snap := snapshot(t, s)
defer snap.Release()
snap.WithGlobal(func(fi protocol.FileIntf) bool {
f := fi.(protocol.FileInfo)
@@ -56,9 +56,9 @@ func globalList(s *db.FileSet) []protocol.FileInfo {
})
return fs
}
func globalListPrefixed(s *db.FileSet, prefix string) []db.FileInfoTruncated {
func globalListPrefixed(t testing.TB, s *db.FileSet, prefix string) []db.FileInfoTruncated {
var fs []db.FileInfoTruncated
snap := s.Snapshot()
snap := snapshot(t, s)
defer snap.Release()
snap.WithPrefixedGlobalTruncated(prefix, func(fi protocol.FileIntf) bool {
f := fi.(db.FileInfoTruncated)
@@ -68,9 +68,9 @@ func globalListPrefixed(s *db.FileSet, prefix string) []db.FileInfoTruncated {
return fs
}
func haveList(s *db.FileSet, n protocol.DeviceID) []protocol.FileInfo {
func haveList(t testing.TB, s *db.FileSet, n protocol.DeviceID) []protocol.FileInfo {
var fs []protocol.FileInfo
snap := s.Snapshot()
snap := snapshot(t, s)
defer snap.Release()
snap.WithHave(n, func(fi protocol.FileIntf) bool {
f := fi.(protocol.FileInfo)
@@ -80,9 +80,9 @@ func haveList(s *db.FileSet, n protocol.DeviceID) []protocol.FileInfo {
return fs
}
func haveListPrefixed(s *db.FileSet, n protocol.DeviceID, prefix string) []db.FileInfoTruncated {
func haveListPrefixed(t testing.TB, s *db.FileSet, n protocol.DeviceID, prefix string) []db.FileInfoTruncated {
var fs []db.FileInfoTruncated
snap := s.Snapshot()
snap := snapshot(t, s)
defer snap.Release()
snap.WithPrefixedHaveTruncated(n, prefix, func(fi protocol.FileIntf) bool {
f := fi.(db.FileInfoTruncated)
@@ -92,9 +92,9 @@ func haveListPrefixed(s *db.FileSet, n protocol.DeviceID, prefix string) []db.Fi
return fs
}
func needList(s *db.FileSet, n protocol.DeviceID) []protocol.FileInfo {
func needList(t testing.TB, s *db.FileSet, n protocol.DeviceID) []protocol.FileInfo {
var fs []protocol.FileInfo
snap := s.Snapshot()
snap := snapshot(t, s)
defer snap.Release()
snap.WithNeed(n, func(fi protocol.FileIntf) bool {
f := fi.(protocol.FileInfo)
@@ -221,7 +221,7 @@ func TestGlobalSet(t *testing.T) {
check := func() {
t.Helper()
g := fileList(globalList(m))
g := fileList(globalList(t, m))
sort.Sort(g)
if fmt.Sprint(g) != fmt.Sprint(expectedGlobal) {
@@ -244,7 +244,7 @@ func TestGlobalSet(t *testing.T) {
}
globalBytes += f.FileSize()
}
gs := globalSize(m)
gs := globalSize(t, m)
if gs.Files != globalFiles {
t.Errorf("Incorrect GlobalSize files; %d != %d", gs.Files, globalFiles)
}
@@ -258,7 +258,7 @@ func TestGlobalSet(t *testing.T) {
t.Errorf("Incorrect GlobalSize bytes; %d != %d", gs.Bytes, globalBytes)
}
h := fileList(haveList(m, protocol.LocalDeviceID))
h := fileList(haveList(t, m, protocol.LocalDeviceID))
sort.Sort(h)
if fmt.Sprint(h) != fmt.Sprint(localTot) {
@@ -281,7 +281,7 @@ func TestGlobalSet(t *testing.T) {
}
haveBytes += f.FileSize()
}
ls := localSize(m)
ls := localSize(t, m)
if ls.Files != haveFiles {
t.Errorf("Incorrect LocalSize files; %d != %d", ls.Files, haveFiles)
}
@@ -295,14 +295,14 @@ func TestGlobalSet(t *testing.T) {
t.Errorf("Incorrect LocalSize bytes; %d != %d", ls.Bytes, haveBytes)
}
h = fileList(haveList(m, remoteDevice0))
h = fileList(haveList(t, m, remoteDevice0))
sort.Sort(h)
if fmt.Sprint(h) != fmt.Sprint(remoteTot) {
t.Errorf("Have incorrect (remote);\n A: %v !=\n E: %v", h, remoteTot)
}
n := fileList(needList(m, protocol.LocalDeviceID))
n := fileList(needList(t, m, protocol.LocalDeviceID))
sort.Sort(n)
if fmt.Sprint(n) != fmt.Sprint(expectedLocalNeed) {
@@ -311,7 +311,7 @@ func TestGlobalSet(t *testing.T) {
checkNeed(t, m, protocol.LocalDeviceID, expectedLocalNeed)
n = fileList(needList(m, remoteDevice0))
n = fileList(needList(t, m, remoteDevice0))
sort.Sort(n)
if fmt.Sprint(n) != fmt.Sprint(expectedRemoteNeed) {
@@ -320,7 +320,7 @@ func TestGlobalSet(t *testing.T) {
checkNeed(t, m, remoteDevice0, expectedRemoteNeed)
snap := m.Snapshot()
snap := snapshot(t, m)
defer snap.Release()
f, ok := snap.Get(protocol.LocalDeviceID, "b")
if !ok {
@@ -365,7 +365,7 @@ func TestGlobalSet(t *testing.T) {
check()
snap := m.Snapshot()
snap := snapshot(t, m)
av := []protocol.DeviceID{protocol.LocalDeviceID, remoteDevice0}
a := snap.Availability("a")
@@ -431,14 +431,14 @@ func TestGlobalSet(t *testing.T) {
check()
h := fileList(haveList(m, remoteDevice1))
h := fileList(haveList(t, m, remoteDevice1))
sort.Sort(h)
if fmt.Sprint(h) != fmt.Sprint(secRemote) {
t.Errorf("Have incorrect (secRemote);\n A: %v !=\n E: %v", h, secRemote)
}
n := fileList(needList(m, remoteDevice1))
n := fileList(needList(t, m, remoteDevice1))
sort.Sort(n)
if fmt.Sprint(n) != fmt.Sprint(expectedSecRemoteNeed) {
@@ -469,16 +469,17 @@ func TestNeedWithInvalid(t *testing.T) {
}
expectedNeed := fileList{
protocol.FileInfo{Name: "b", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1001}}}, Blocks: genBlocks(2)},
protocol.FileInfo{Name: "c", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1002}}}, Blocks: genBlocks(7)},
protocol.FileInfo{Name: "d", Version: protocol.Vector{Counters: []protocol.Counter{{ID: myID, Value: 1003}}}, Blocks: genBlocks(7)},
remote0Have[0],
remote1Have[0],
remote0Have[2],
remote1Have[2],
}
replace(s, protocol.LocalDeviceID, localHave)
replace(s, remoteDevice0, remote0Have)
replace(s, remoteDevice1, remote1Have)
need := fileList(needList(s, protocol.LocalDeviceID))
need := fileList(needList(t, s, protocol.LocalDeviceID))
sort.Sort(need)
if fmt.Sprint(need) != fmt.Sprint(expectedNeed) {
@@ -506,7 +507,7 @@ func TestUpdateToInvalid(t *testing.T) {
replace(s, protocol.LocalDeviceID, localHave)
have := fileList(haveList(s, protocol.LocalDeviceID))
have := fileList(haveList(t, s, protocol.LocalDeviceID))
sort.Sort(have)
if fmt.Sprint(have) != fmt.Sprint(localHave) {
@@ -523,7 +524,7 @@ func TestUpdateToInvalid(t *testing.T) {
s.Update(protocol.LocalDeviceID, append(fileList{}, localHave[1], localHave[4]))
have = fileList(haveList(s, protocol.LocalDeviceID))
have = fileList(haveList(t, s, protocol.LocalDeviceID))
sort.Sort(have)
if fmt.Sprint(have) != fmt.Sprint(localHave) {
@@ -567,7 +568,7 @@ func TestInvalidAvailability(t *testing.T) {
replace(s, remoteDevice0, remote0Have)
replace(s, remoteDevice1, remote1Have)
snap := s.Snapshot()
snap := snapshot(t, s)
defer snap.Release()
if av := snap.Availability("both"); len(av) != 2 {
@@ -608,7 +609,7 @@ func TestGlobalReset(t *testing.T) {
}
replace(m, protocol.LocalDeviceID, local)
g := globalList(m)
g := globalList(t, m)
sort.Sort(fileList(g))
if diff, equal := messagediff.PrettyDiff(local, g); !equal {
@@ -618,7 +619,7 @@ func TestGlobalReset(t *testing.T) {
replace(m, remoteDevice0, remote)
replace(m, remoteDevice0, nil)
g = globalList(m)
g = globalList(t, m)
sort.Sort(fileList(g))
if diff, equal := messagediff.PrettyDiff(local, g); !equal {
@@ -655,7 +656,7 @@ func TestNeed(t *testing.T) {
replace(m, protocol.LocalDeviceID, local)
replace(m, remoteDevice0, remote)
need := needList(m, protocol.LocalDeviceID)
need := needList(t, m, protocol.LocalDeviceID)
sort.Sort(fileList(need))
sort.Sort(fileList(shouldNeed))
@@ -725,10 +726,10 @@ func TestListDropFolder(t *testing.T) {
if diff, equal := messagediff.PrettyDiff(expectedFolderList, actualFolderList); !equal {
t.Fatalf("FolderList mismatch. Diff:\n%s", diff)
}
if l := len(globalList(s0)); l != 3 {
if l := len(globalList(t, s0)); l != 3 {
t.Errorf("Incorrect global length %d != 3 for s0", l)
}
if l := len(globalList(s1)); l != 3 {
if l := len(globalList(t, s1)); l != 3 {
t.Errorf("Incorrect global length %d != 3 for s1", l)
}
@@ -741,10 +742,10 @@ func TestListDropFolder(t *testing.T) {
if diff, equal := messagediff.PrettyDiff(expectedFolderList, actualFolderList); !equal {
t.Fatalf("FolderList mismatch. Diff:\n%s", diff)
}
if l := len(globalList(s0)); l != 3 {
if l := len(globalList(t, s0)); l != 3 {
t.Errorf("Incorrect global length %d != 3 for s0", l)
}
if l := len(globalList(s1)); l != 0 {
if l := len(globalList(t, s1)); l != 0 {
t.Errorf("Incorrect global length %d != 0 for s1", l)
}
}
@@ -780,13 +781,13 @@ func TestGlobalNeedWithInvalid(t *testing.T) {
protocol.FileInfo{Name: "d", Version: protocol.Vector{Counters: []protocol.Counter{{ID: remoteDevice0.Short(), Value: 1002}}}},
}
need := fileList(needList(s, protocol.LocalDeviceID))
need := fileList(needList(t, s, protocol.LocalDeviceID))
if fmt.Sprint(need) != fmt.Sprint(total) {
t.Errorf("Need incorrect;\n A: %v !=\n E: %v", need, total)
}
checkNeed(t, s, protocol.LocalDeviceID, total)
global := fileList(globalList(s))
global := fileList(globalList(t, s))
if fmt.Sprint(global) != fmt.Sprint(total) {
t.Errorf("Global incorrect;\n A: %v !=\n E: %v", global, total)
}
@@ -810,7 +811,7 @@ func TestLongPath(t *testing.T) {
replace(s, protocol.LocalDeviceID, local)
gf := globalList(s)
gf := globalList(t, s)
if l := len(gf); l != 1 {
t.Fatalf("Incorrect len %d != 1 for global list", l)
}
@@ -911,17 +912,17 @@ func TestDropFiles(t *testing.T) {
// Check that they're there
h := haveList(m, protocol.LocalDeviceID)
h := haveList(t, m, protocol.LocalDeviceID)
if len(h) != len(local0) {
t.Errorf("Incorrect number of files after update, %d != %d", len(h), len(local0))
}
h = haveList(m, remoteDevice0)
h = haveList(t, m, remoteDevice0)
if len(h) != len(remote0) {
t.Errorf("Incorrect number of files after update, %d != %d", len(h), len(local0))
}
g := globalList(m)
g := globalList(t, m)
if len(g) != len(local0) {
// local0 covers all files
t.Errorf("Incorrect global files after update, %d != %d", len(g), len(local0))
@@ -931,17 +932,17 @@ func TestDropFiles(t *testing.T) {
m.Drop(protocol.LocalDeviceID)
h = haveList(m, protocol.LocalDeviceID)
h = haveList(t, m, protocol.LocalDeviceID)
if len(h) != 0 {
t.Errorf("Incorrect number of files after drop, %d != %d", len(h), 0)
}
h = haveList(m, remoteDevice0)
h = haveList(t, m, remoteDevice0)
if len(h) != len(remote0) {
t.Errorf("Incorrect number of files after update, %d != %d", len(h), len(local0))
}
g = globalList(m)
g = globalList(t, m)
if len(g) != len(remote0) {
// the ones in remote0 remain
t.Errorf("Incorrect global files after update, %d != %d", len(g), len(remote0))
@@ -961,20 +962,20 @@ func TestIssue4701(t *testing.T) {
s.Update(protocol.LocalDeviceID, localHave)
if c := localSize(s); c.Files != 1 {
if c := localSize(t, s); c.Files != 1 {
t.Errorf("Expected 1 local file, got %v", c.Files)
}
if c := globalSize(s); c.Files != 1 {
if c := globalSize(t, s); c.Files != 1 {
t.Errorf("Expected 1 global file, got %v", c.Files)
}
localHave[1].LocalFlags = 0
s.Update(protocol.LocalDeviceID, localHave)
if c := localSize(s); c.Files != 2 {
if c := localSize(t, s); c.Files != 2 {
t.Errorf("Expected 2 local files, got %v", c.Files)
}
if c := globalSize(s); c.Files != 2 {
if c := globalSize(t, s); c.Files != 2 {
t.Errorf("Expected 2 global files, got %v", c.Files)
}
@@ -982,10 +983,10 @@ func TestIssue4701(t *testing.T) {
localHave[1].LocalFlags = protocol.FlagLocalIgnored
s.Update(protocol.LocalDeviceID, localHave)
if c := localSize(s); c.Files != 0 {
if c := localSize(t, s); c.Files != 0 {
t.Errorf("Expected 0 local files, got %v", c.Files)
}
if c := globalSize(s); c.Files != 0 {
if c := globalSize(t, s); c.Files != 0 {
t.Errorf("Expected 0 global files, got %v", c.Files)
}
}
@@ -1009,7 +1010,7 @@ func TestWithHaveSequence(t *testing.T) {
replace(s, protocol.LocalDeviceID, localHave)
i := 2
snap := s.Snapshot()
snap := snapshot(t, s)
defer snap.Release()
snap.WithHaveSequence(int64(i), func(fi protocol.FileIntf) bool {
if f := fi.(protocol.FileInfo); !f.IsEquivalent(localHave[i-1], 0) {
@@ -1061,7 +1062,7 @@ loop:
break loop
default:
}
snap := s.Snapshot()
snap := snapshot(t, s)
snap.WithHaveSequence(prevSeq+1, func(fi protocol.FileIntf) bool {
if fi.SequenceNo() < prevSeq+1 {
t.Fatal("Skipped ", prevSeq+1, fi.SequenceNo())
@@ -1089,11 +1090,11 @@ func TestIssue4925(t *testing.T) {
replace(s, protocol.LocalDeviceID, localHave)
for _, prefix := range []string{"dir", "dir/"} {
pl := haveListPrefixed(s, protocol.LocalDeviceID, prefix)
pl := haveListPrefixed(t, s, protocol.LocalDeviceID, prefix)
if l := len(pl); l != 2 {
t.Errorf("Expected 2, got %v local items below %v", l, prefix)
}
pl = globalListPrefixed(s, prefix)
pl = globalListPrefixed(t, s, prefix)
if l := len(pl); l != 2 {
t.Errorf("Expected 2, got %v global items below %v", l, prefix)
}
@@ -1114,24 +1115,24 @@ func TestMoveGlobalBack(t *testing.T) {
s.Update(protocol.LocalDeviceID, localHave)
s.Update(remoteDevice0, remote0Have)
if need := needList(s, protocol.LocalDeviceID); len(need) != 1 {
if need := needList(t, s, protocol.LocalDeviceID); len(need) != 1 {
t.Error("Expected 1 local need, got", need)
} else if !need[0].IsEquivalent(remote0Have[0], 0) {
t.Errorf("Local need incorrect;\n A: %v !=\n E: %v", need[0], remote0Have[0])
}
checkNeed(t, s, protocol.LocalDeviceID, remote0Have[:1])
if need := needList(s, remoteDevice0); len(need) != 0 {
if need := needList(t, s, remoteDevice0); len(need) != 0 {
t.Error("Expected no need for remote 0, got", need)
}
checkNeed(t, s, remoteDevice0, nil)
ls := localSize(s)
ls := localSize(t, s)
if haveBytes := localHave[0].Size; ls.Bytes != haveBytes {
t.Errorf("Incorrect LocalSize bytes; %d != %d", ls.Bytes, haveBytes)
}
gs := globalSize(s)
gs := globalSize(t, s)
if globalBytes := remote0Have[0].Size; gs.Bytes != globalBytes {
t.Errorf("Incorrect GlobalSize bytes; %d != %d", gs.Bytes, globalBytes)
}
@@ -1142,24 +1143,24 @@ func TestMoveGlobalBack(t *testing.T) {
remote0Have[0].Version = remote0Have[0].Version.Update(remoteDevice0.Short()).DropOthers(remoteDevice0.Short())
s.Update(remoteDevice0, remote0Have)
if need := needList(s, remoteDevice0); len(need) != 1 {
if need := needList(t, s, remoteDevice0); len(need) != 1 {
t.Error("Expected 1 need for remote 0, got", need)
} else if !need[0].IsEquivalent(localHave[0], 0) {
t.Errorf("Need for remote 0 incorrect;\n A: %v !=\n E: %v", need[0], localHave[0])
}
checkNeed(t, s, remoteDevice0, localHave[:1])
if need := needList(s, protocol.LocalDeviceID); len(need) != 0 {
if need := needList(t, s, protocol.LocalDeviceID); len(need) != 0 {
t.Error("Expected no local need, got", need)
}
checkNeed(t, s, protocol.LocalDeviceID, nil)
ls = localSize(s)
ls = localSize(t, s)
if haveBytes := localHave[0].Size; ls.Bytes != haveBytes {
t.Errorf("Incorrect LocalSize bytes; %d != %d", ls.Bytes, haveBytes)
}
gs = globalSize(s)
gs = globalSize(t, s)
if globalBytes := localHave[0].Size; gs.Bytes != globalBytes {
t.Errorf("Incorrect GlobalSize bytes; %d != %d", gs.Bytes, globalBytes)
}
@@ -1181,7 +1182,7 @@ func TestIssue5007(t *testing.T) {
s.Update(remoteDevice0, fs)
if need := needList(s, protocol.LocalDeviceID); len(need) != 1 {
if need := needList(t, s, protocol.LocalDeviceID); len(need) != 1 {
t.Fatal("Expected 1 local need, got", need)
} else if !need[0].IsEquivalent(fs[0], 0) {
t.Fatalf("Local need incorrect;\n A: %v !=\n E: %v", need[0], fs[0])
@@ -1191,7 +1192,7 @@ func TestIssue5007(t *testing.T) {
fs[0].LocalFlags = protocol.FlagLocalIgnored
s.Update(protocol.LocalDeviceID, fs)
if need := needList(s, protocol.LocalDeviceID); len(need) != 0 {
if need := needList(t, s, protocol.LocalDeviceID); len(need) != 0 {
t.Fatal("Expected no local need, got", need)
}
checkNeed(t, s, protocol.LocalDeviceID, nil)
@@ -1211,7 +1212,7 @@ func TestNeedDeleted(t *testing.T) {
s.Update(remoteDevice0, fs)
if need := needList(s, protocol.LocalDeviceID); len(need) != 0 {
if need := needList(t, s, protocol.LocalDeviceID); len(need) != 0 {
t.Fatal("Expected no local need, got", need)
}
checkNeed(t, s, protocol.LocalDeviceID, nil)
@@ -1220,7 +1221,7 @@ func TestNeedDeleted(t *testing.T) {
fs[0].Version = fs[0].Version.Update(remoteDevice0.Short())
s.Update(remoteDevice0, fs)
if need := needList(s, protocol.LocalDeviceID); len(need) != 1 {
if need := needList(t, s, protocol.LocalDeviceID); len(need) != 1 {
t.Fatal("Expected 1 local need, got", need)
} else if !need[0].IsEquivalent(fs[0], 0) {
t.Fatalf("Local need incorrect;\n A: %v !=\n E: %v", need[0], fs[0])
@@ -1231,7 +1232,7 @@ func TestNeedDeleted(t *testing.T) {
fs[0].Version = fs[0].Version.Update(remoteDevice0.Short())
s.Update(remoteDevice0, fs)
if need := needList(s, protocol.LocalDeviceID); len(need) != 0 {
if need := needList(t, s, protocol.LocalDeviceID); len(need) != 0 {
t.Fatal("Expected no local need, got", need)
}
checkNeed(t, s, protocol.LocalDeviceID, nil)
@@ -1261,22 +1262,22 @@ func TestReceiveOnlyAccounting(t *testing.T) {
replace(s, protocol.LocalDeviceID, files)
replace(s, remote, files)
if n := localSize(s).Files; n != 3 {
if n := localSize(t, s).Files; n != 3 {
t.Fatal("expected 3 local files initially, not", n)
}
if n := localSize(s).Bytes; n != 30 {
if n := localSize(t, s).Bytes; n != 30 {
t.Fatal("expected 30 local bytes initially, not", n)
}
if n := globalSize(s).Files; n != 3 {
if n := globalSize(t, s).Files; n != 3 {
t.Fatal("expected 3 global files initially, not", n)
}
if n := globalSize(s).Bytes; n != 30 {
if n := globalSize(t, s).Bytes; n != 30 {
t.Fatal("expected 30 global bytes initially, not", n)
}
if n := receiveOnlyChangedSize(s).Files; n != 0 {
if n := receiveOnlyChangedSize(t, s).Files; n != 0 {
t.Fatal("expected 0 receive only changed files initially, not", n)
}
if n := receiveOnlyChangedSize(s).Bytes; n != 0 {
if n := receiveOnlyChangedSize(t, s).Bytes; n != 0 {
t.Fatal("expected 0 receive only changed bytes initially, not", n)
}
@@ -1291,22 +1292,22 @@ func TestReceiveOnlyAccounting(t *testing.T) {
// Check that we see the files
if n := localSize(s).Files; n != 3 {
if n := localSize(t, s).Files; n != 3 {
t.Fatal("expected 3 local files after local change, not", n)
}
if n := localSize(s).Bytes; n != 120 {
if n := localSize(t, s).Bytes; n != 120 {
t.Fatal("expected 120 local bytes after local change, not", n)
}
if n := globalSize(s).Files; n != 3 {
if n := globalSize(t, s).Files; n != 3 {
t.Fatal("expected 3 global files after local change, not", n)
}
if n := globalSize(s).Bytes; n != 30 {
if n := globalSize(t, s).Bytes; n != 30 {
t.Fatal("expected 30 global files after local change, not", n)
}
if n := receiveOnlyChangedSize(s).Files; n != 1 {
if n := receiveOnlyChangedSize(t, s).Files; n != 1 {
t.Fatal("expected 1 receive only changed file after local change, not", n)
}
if n := receiveOnlyChangedSize(s).Bytes; n != 100 {
if n := receiveOnlyChangedSize(t, s).Bytes; n != 100 {
t.Fatal("expected 100 receive only changed btyes after local change, not", n)
}
@@ -1322,22 +1323,22 @@ func TestReceiveOnlyAccounting(t *testing.T) {
// Check that we see the files, same data as initially
if n := localSize(s).Files; n != 3 {
if n := localSize(t, s).Files; n != 3 {
t.Fatal("expected 3 local files after revert, not", n)
}
if n := localSize(s).Bytes; n != 30 {
if n := localSize(t, s).Bytes; n != 30 {
t.Fatal("expected 30 local bytes after revert, not", n)
}
if n := globalSize(s).Files; n != 3 {
if n := globalSize(t, s).Files; n != 3 {
t.Fatal("expected 3 global files after revert, not", n)
}
if n := globalSize(s).Bytes; n != 30 {
if n := globalSize(t, s).Bytes; n != 30 {
t.Fatal("expected 30 global bytes after revert, not", n)
}
if n := receiveOnlyChangedSize(s).Files; n != 0 {
if n := receiveOnlyChangedSize(t, s).Files; n != 0 {
t.Fatal("expected 0 receive only changed files after revert, not", n)
}
if n := receiveOnlyChangedSize(s).Bytes; n != 0 {
if n := receiveOnlyChangedSize(t, s).Bytes; n != 0 {
t.Fatal("expected 0 receive only changed bytes after revert, not", n)
}
}
@@ -1366,7 +1367,7 @@ func TestNeedAfterUnignore(t *testing.T) {
local.ModifiedS = 0
s.Update(protocol.LocalDeviceID, fileList{local})
if need := needList(s, protocol.LocalDeviceID); len(need) != 1 {
if need := needList(t, s, protocol.LocalDeviceID); len(need) != 1 {
t.Fatal("Expected one local need, got", need)
} else if !need[0].IsEquivalent(remote, 0) {
t.Fatalf("Got %v, expected %v", need[0], remote)
@@ -1387,7 +1388,7 @@ func TestRemoteInvalidNotAccounted(t *testing.T) {
}
s.Update(remoteDevice0, files)
global := globalSize(s)
global := globalSize(t, s)
if global.Files != 1 {
t.Error("Expected one file in global size, not", global.Files)
}
@@ -1411,7 +1412,7 @@ func TestNeedWithNewerInvalid(t *testing.T) {
s.Update(remoteDevice0, fileList{file})
s.Update(remoteDevice1, fileList{file})
need := needList(s, protocol.LocalDeviceID)
need := needList(t, s, protocol.LocalDeviceID)
if len(need) != 1 {
t.Fatal("Locally missing file should be needed")
}
@@ -1427,7 +1428,7 @@ func TestNeedWithNewerInvalid(t *testing.T) {
s.Update(remoteDevice1, fileList{inv})
// We still have an old file, we need the newest valid file
need = needList(s, protocol.LocalDeviceID)
need = needList(t, s, protocol.LocalDeviceID)
if len(need) != 1 {
t.Fatal("Locally missing file should be needed regardless of invalid files")
}
@@ -1452,13 +1453,13 @@ func TestNeedAfterDeviceRemove(t *testing.T) {
s.Update(remoteDevice0, fs)
if need := needList(s, protocol.LocalDeviceID); len(need) != 1 {
if need := needList(t, s, protocol.LocalDeviceID); len(need) != 1 {
t.Fatal("Expected one local need, got", need)
}
s.Drop(remoteDevice0)
if need := needList(s, protocol.LocalDeviceID); len(need) != 0 {
if need := needList(t, s, protocol.LocalDeviceID); len(need) != 0 {
t.Fatal("Expected no local need, got", need)
}
checkNeed(t, s, protocol.LocalDeviceID, nil)
@@ -1481,7 +1482,7 @@ func TestCaseSensitive(t *testing.T) {
replace(s, protocol.LocalDeviceID, local)
gf := globalList(s)
gf := globalList(t, s)
if l := len(gf); l != len(local) {
t.Fatalf("Incorrect len %d != %d for global list", l, len(local))
}
@@ -1551,7 +1552,7 @@ func TestSequenceIndex(t *testing.T) {
// a subset of those files if we manage to run before a complete
// update has happened since our last iteration.
latest = latest[:0]
snap := s.Snapshot()
snap := snapshot(t, s)
snap.WithHaveSequence(seq+1, func(f protocol.FileIntf) bool {
seen[f.FileName()] = f
latest = append(latest, f)
@@ -1617,7 +1618,7 @@ func TestIgnoreAfterReceiveOnly(t *testing.T) {
s.Update(protocol.LocalDeviceID, fs)
snap := s.Snapshot()
snap := snapshot(t, s)
defer snap.Release()
if f, ok := snap.Get(protocol.LocalDeviceID, file); !ok {
t.Error("File missing in db")
@@ -1654,7 +1655,7 @@ func TestUpdateWithOneFileTwice(t *testing.T) {
s.Update(protocol.LocalDeviceID, fs)
snap := s.Snapshot()
snap := snapshot(t, s)
defer snap.Release()
count := 0
snap.WithHaveSequence(0, func(f protocol.FileIntf) bool {
@@ -1678,7 +1679,7 @@ func TestNeedRemoteOnly(t *testing.T) {
}
s.Update(remoteDevice0, remote0Have)
need := needSize(s, remoteDevice0)
need := needSize(t, s, remoteDevice0)
if !need.Equal(db.Counts{}) {
t.Error("Expected nothing needed, got", need)
}
@@ -1697,14 +1698,14 @@ func TestNeedRemoteAfterReset(t *testing.T) {
s.Update(protocol.LocalDeviceID, files)
s.Update(remoteDevice0, files)
need := needSize(s, remoteDevice0)
need := needSize(t, s, remoteDevice0)
if !need.Equal(db.Counts{}) {
t.Error("Expected nothing needed, got", need)
}
s.Drop(remoteDevice0)
need = needSize(s, remoteDevice0)
need = needSize(t, s, remoteDevice0)
if exp := (db.Counts{Files: 1}); !need.Equal(exp) {
t.Errorf("Expected %v, got %v", exp, need)
}
@@ -1723,10 +1724,10 @@ func TestIgnoreLocalChanged(t *testing.T) {
}
s.Update(protocol.LocalDeviceID, files)
if c := globalSize(s).Files; c != 0 {
if c := globalSize(t, s).Files; c != 0 {
t.Error("Expected no global file, got", c)
}
if c := localSize(s).Files; c != 1 {
if c := localSize(t, s).Files; c != 1 {
t.Error("Expected one local file, got", c)
}
@@ -1734,10 +1735,10 @@ func TestIgnoreLocalChanged(t *testing.T) {
files[0].LocalFlags = protocol.FlagLocalIgnored
s.Update(protocol.LocalDeviceID, files)
if c := globalSize(s).Files; c != 0 {
if c := globalSize(t, s).Files; c != 0 {
t.Error("Expected no global file, got", c)
}
if c := localSize(s).Files; c != 0 {
if c := localSize(t, s).Files; c != 0 {
t.Error("Expected no local file, got", c)
}
}
@@ -1789,26 +1790,26 @@ func replace(fs *db.FileSet, device protocol.DeviceID, files []protocol.FileInfo
fs.Update(device, files)
}
func localSize(fs *db.FileSet) db.Counts {
snap := fs.Snapshot()
func localSize(t testing.TB, fs *db.FileSet) db.Counts {
snap := snapshot(t, fs)
defer snap.Release()
return snap.LocalSize()
}
func globalSize(fs *db.FileSet) db.Counts {
snap := fs.Snapshot()
func globalSize(t testing.TB, fs *db.FileSet) db.Counts {
snap := snapshot(t, fs)
defer snap.Release()
return snap.GlobalSize()
}
func needSize(fs *db.FileSet, id protocol.DeviceID) db.Counts {
snap := fs.Snapshot()
func needSize(t testing.TB, fs *db.FileSet, id protocol.DeviceID) db.Counts {
snap := snapshot(t, fs)
defer snap.Release()
return snap.NeedSize(id)
}
func receiveOnlyChangedSize(fs *db.FileSet) db.Counts {
snap := fs.Snapshot()
func receiveOnlyChangedSize(t testing.TB, fs *db.FileSet) db.Counts {
snap := snapshot(t, fs)
defer snap.Release()
return snap.ReceiveOnlyChangedSize()
}
@@ -1833,7 +1834,7 @@ func filesToCounts(files []protocol.FileInfo) db.Counts {
func checkNeed(t testing.TB, s *db.FileSet, dev protocol.DeviceID, expected []protocol.FileInfo) {
t.Helper()
counts := needSize(s, dev)
counts := needSize(t, s, dev)
if exp := filesToCounts(expected); !exp.Equal(counts) {
t.Errorf("Count incorrect (%v): expected %v, got %v", dev, exp, counts)
}
@@ -1860,3 +1861,12 @@ func newFileSet(t testing.TB, folder string, fs fs.Filesystem, ll *db.Lowlevel)
}
return fset
}
func snapshot(t testing.TB, fset *db.FileSet) *db.Snapshot {
t.Helper()
snap, err := fset.Snapshot()
if err != nil {
t.Fatal(err)
}
return snap
}
-27
View File
@@ -396,25 +396,6 @@ func (vl *VersionList) findDevice(device []byte) (bool, int, int, bool) {
return false, -1, -1, false
}
func (vl *VersionList) popVersion(version protocol.Vector) (FileVersion, bool) {
i := vl.versionIndex(version)
if i == -1 {
return FileVersion{}, false
}
fv := vl.RawVersions[i]
vl.popVersionAt(i)
return fv, true
}
func (vl *VersionList) versionIndex(version protocol.Vector) int {
for i, v := range vl.RawVersions {
if version.Equal(v.Version) {
return i
}
}
return -1
}
func (vl *VersionList) popVersionAt(i int) {
vl.RawVersions = append(vl.RawVersions[:i], vl.RawVersions[i+1:]...)
}
@@ -496,14 +477,6 @@ func popDeviceAt(devices [][]byte, i int) [][]byte {
return append(devices[:i], devices[i+1:]...)
}
func popDevice(devices [][]byte, device []byte) ([][]byte, bool) {
i := deviceIndex(devices, device)
if i == -1 {
return devices, false
}
return popDeviceAt(devices, i), true
}
func newFileVersion(device []byte, version protocol.Vector, invalid, deleted bool) FileVersion {
fv := FileVersion{
Version: version,
+2 -4
View File
@@ -762,10 +762,8 @@ func (t readWriteTransaction) updateLocalNeed(keyBuf, folder, name []byte, add b
}
func Need(global FileVersion, haveLocal bool, localVersion protocol.Vector) bool {
// We never need an invalid file or a file without a valid version (just
// another way of expressing "invalid", really, until we fix that
// part...).
if global.IsInvalid() || global.Version.IsEmpty() {
// We never need a file without a valid version.
if global.Version.IsEmpty() {
return false
}
// We don't need a deleted file if we don't have it.
+9
View File
@@ -92,6 +92,15 @@ func newFileSet(t testing.TB, folder string, fs fs.Filesystem, db *Lowlevel) *Fi
return fset
}
func snapshot(t testing.TB, fset *FileSet) *Snapshot {
t.Helper()
snap, err := fset.Snapshot()
if err != nil {
t.Fatal(err)
}
return snap
}
// The following commented tests were used to generate jsons files to stdout for
// future tests and are kept here for reference (reuse).
+1 -2
View File
@@ -237,7 +237,6 @@ type logger struct {
events chan Event
funcs chan func(context.Context)
toUnsubscribe chan *subscription
stop chan struct{}
}
type Event struct {
@@ -317,7 +316,7 @@ loop:
func (l *logger) Log(t EventType, data interface{}) {
l.events <- Event{
Time: time.Now(),
Time: time.Now(), // intentionally high precision
Type: t,
Data: data,
// SubscriptionID and GlobalID are set in sendEvent
+18 -11
View File
@@ -26,24 +26,26 @@ var (
errNotRelative = errors.New("not a relative path")
)
func WithJunctionsAsDirs() Option {
return Option{
apply: func(fs Filesystem) {
if basic, ok := fs.(*BasicFilesystem); !ok {
l.Warnln("WithJunctionsAsDirs must only be used with FilesystemTypeBasic")
} else {
basic.junctionsAsDirs = true
}
},
id: "junctionsAsDirs",
type OptionJunctionsAsDirs struct{}
func (o *OptionJunctionsAsDirs) apply(fs Filesystem) {
if basic, ok := fs.(*BasicFilesystem); !ok {
l.Warnln("WithJunctionsAsDirs must only be used with FilesystemTypeBasic")
} else {
basic.junctionsAsDirs = true
}
}
func (o *OptionJunctionsAsDirs) String() string {
return "junctionsAsDirs"
}
// The BasicFilesystem implements all aspects by delegating to package os.
// All paths are relative to the root and cannot (should not) escape the root directory.
type BasicFilesystem struct {
root string
junctionsAsDirs bool
options []Option
}
func newBasicFilesystem(root string, opts ...Option) *BasicFilesystem {
@@ -82,7 +84,8 @@ func newBasicFilesystem(root string, opts ...Option) *BasicFilesystem {
}
fs := &BasicFilesystem{
root: root,
root: root,
options: opts,
}
for _, opt := range opts {
opt.apply(fs)
@@ -311,6 +314,10 @@ func (f *BasicFilesystem) URI() string {
return strings.TrimPrefix(f.root, `\\?\`)
}
func (f *BasicFilesystem) Options() []Option {
return f.options
}
func (f *BasicFilesystem) SameFile(fi1, fi2 FileInfo) bool {
// Like os.SameFile, we always return false unless fi1 and fi2 were created
// by this package's Stat/Lstat method.
+15 -10
View File
@@ -55,22 +55,22 @@ type caseFilesystemRegistry struct {
startCleaner sync.Once
}
func newFSKey(fs Filesystem, opts ...Option) fskey {
func newFSKey(fs Filesystem) fskey {
k := fskey{
fstype: fs.Type(),
uri: fs.URI(),
}
if len(opts) > 0 {
k.opts = opts[0].id
if opts := fs.Options(); len(opts) > 0 {
k.opts = opts[0].String()
for _, o := range opts[1:] {
k.opts += "&" + o.id
k.opts += "&" + o.String()
}
}
return k
}
func (r *caseFilesystemRegistry) get(fs Filesystem, opts ...Option) Filesystem {
k := newFSKey(fs, opts...)
func (r *caseFilesystemRegistry) get(fs Filesystem) Filesystem {
k := newFSKey(fs)
// Use double locking when getting a caseFs. In the common case it will
// already exist and we take the read lock fast path. If it doesn't, we
@@ -136,10 +136,8 @@ type caseFilesystem struct {
// from the real path. It is safe to use with any filesystem, i.e. also a
// case-sensitive one. However it will add some overhead and thus shouldn't be
// used if the filesystem is known to already behave case-sensitively.
func NewCaseFilesystem(fs Filesystem, opts ...Option) Filesystem {
return wrapFilesystem(fs, func(fs Filesystem) Filesystem {
return globalCaseFilesystemRegistry.get(fs, opts...)
})
func NewCaseFilesystem(fs Filesystem) Filesystem {
return wrapFilesystem(fs, globalCaseFilesystemRegistry.get)
}
func (f *caseFilesystem) Chmod(name string, mode FileMode) error {
@@ -226,6 +224,13 @@ func (f *caseFilesystem) Rename(oldpath, newpath string) error {
if err := f.checkCase(oldpath); err != nil {
return err
}
if err := f.checkCase(newpath); err != nil {
// Case-only rename is ok
e := &ErrCaseConflict{}
if !errors.As(err, &e) || e.Real != oldpath {
return err
}
}
if err := f.Filesystem.Rename(oldpath, newpath); err != nil {
return err
}
+4 -1
View File
@@ -45,7 +45,10 @@ func (fs *errorFilesystem) Roots() ([]string, error) { retur
func (fs *errorFilesystem) Usage(name string) (Usage, error) { return Usage{}, fs.err }
func (fs *errorFilesystem) Type() FilesystemType { return fs.fsType }
func (fs *errorFilesystem) URI() string { return fs.uri }
func (fs *errorFilesystem) SameFile(fi1, fi2 FileInfo) bool { return false }
func (fs *errorFilesystem) Options() []Option {
return nil
}
func (fs *errorFilesystem) SameFile(fi1, fi2 FileInfo) bool { return false }
func (fs *errorFilesystem) Watch(path string, ignore Matcher, ctx context.Context, ignorePerms bool) (<-chan Event, <-chan error, error) {
return nil, nil, fs.err
}
+4
View File
@@ -640,6 +640,10 @@ func (fs *fakefs) URI() string {
return fs.uri
}
func (fs *fakefs) Options() []Option {
return nil
}
func (fs *fakefs) SameFile(fi1, fi2 FileInfo) bool {
// BUG: real systems base file sameness on path, inodes, etc
// we try our best, but FileInfo just doesn't have enough data
+11 -5
View File
@@ -47,6 +47,7 @@ type Filesystem interface {
Usage(name string) (Usage, error)
Type() FilesystemType
URI() string
Options() []Option
SameFile(fi1, fi2 FileInfo) bool
}
@@ -178,9 +179,15 @@ var IsPermission = os.IsPermission
// IsPathSeparator is the equivalent of os.IsPathSeparator
var IsPathSeparator = os.IsPathSeparator
type Option struct {
apply func(Filesystem)
id string
// Option modifies a filesystem at creation. An option might be specific
// to a filesystem-type.
//
// String is used to detect options with the same effect, i.e. must be different
// for options with different effects. Meaning if an option has parameters, a
// representation of those must be part of the returned string.
type Option interface {
String() string
apply(Filesystem)
}
func NewFilesystem(fsType FilesystemType, uri string, opts ...Option) Filesystem {
@@ -245,8 +252,7 @@ func Canonicalize(file string) (string, error) {
file = filepath.Clean(file)
// It is not acceptable to attempt to traverse upwards.
switch file {
case "..":
if file == ".." {
return "", errNotRelative
}
if strings.HasPrefix(file, ".."+pathSep) {
+3 -4
View File
@@ -7,11 +7,12 @@
package fs
import (
"crypto/md5"
"fmt"
"path/filepath"
"runtime"
"strings"
"github.com/syncthing/syncthing/lib/sha256"
)
const (
@@ -50,9 +51,7 @@ func TempNameWithPrefix(name, prefix string) string {
tdir := filepath.Dir(name)
tbase := filepath.Base(name)
if len(tbase) > maxFilenameLength {
hash := md5.New()
hash.Write([]byte(name))
tbase = fmt.Sprintf("%x", hash.Sum(nil))
tbase = fmt.Sprintf("%x", sha256.Sum256([]byte(name)))
}
tname := fmt.Sprintf("%s%s.tmp", prefix, tbase)
return filepath.Join(tdir, tname)
+22 -7
View File
@@ -63,10 +63,20 @@ type WalkFunc func(path string, info FileInfo, err error) error
type walkFilesystem struct {
Filesystem
checkInfiniteRecursion bool
}
func NewWalkFilesystem(next Filesystem) Filesystem {
return &walkFilesystem{next}
fs := &walkFilesystem{
Filesystem: next,
}
for _, opt := range next.Options() {
if _, ok := opt.(*OptionJunctionsAsDirs); ok {
fs.checkInfiniteRecursion = true
break
}
}
return fs
}
// walk recursively descends path, calling walkFn.
@@ -89,11 +99,13 @@ func (f *walkFilesystem) walk(path string, info FileInfo, walkFn WalkFunc, ances
return nil
}
if !ancestors.Contains(info) {
ancestors.Push(info)
defer ancestors.Pop()
} else {
return walkFn(path, info, ErrInfiniteRecursion)
if f.checkInfiniteRecursion {
if !ancestors.Contains(info) {
ancestors.Push(info)
defer ancestors.Pop()
} else {
return walkFn(path, info, ErrInfiniteRecursion)
}
}
names, err := f.DirNames(path)
@@ -131,6 +143,9 @@ func (f *walkFilesystem) Walk(root string, walkFn WalkFunc) error {
if err != nil {
return walkFn(root, nil, err)
}
ancestors := &ancestorDirList{fs: f.Filesystem}
var ancestors *ancestorDirList
if f.checkInfiniteRecursion {
ancestors = &ancestorDirList{fs: f.Filesystem}
}
return f.walk(root, info, walkFn, ancestors)
}
+2 -2
View File
@@ -57,7 +57,7 @@ func testWalkTraverseDirJunct(t *testing.T, fsType FilesystemType, uri string) {
t.Skip("Directory junctions are available and tested on windows only")
}
fs := NewFilesystem(fsType, uri, WithJunctionsAsDirs())
fs := NewFilesystem(fsType, uri, new(OptionJunctionsAsDirs))
if err := fs.MkdirAll("target/foo", 0); err != nil {
t.Fatal(err)
@@ -90,7 +90,7 @@ func testWalkInfiniteRecursion(t *testing.T, fsType FilesystemType, uri string)
t.Skip("Infinite recursion detection is tested on windows only")
}
fs := NewFilesystem(fsType, uri, WithJunctionsAsDirs())
fs := NewFilesystem(fsType, uri, new(OptionJunctionsAsDirs))
if err := fs.MkdirAll("target/foo", 0); err != nil {
t.Fatal(err)
+2 -2
View File
@@ -9,7 +9,6 @@ package ignore
import (
"bufio"
"bytes"
"crypto/md5"
"errors"
"fmt"
"io"
@@ -22,6 +21,7 @@ import (
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/sha256"
"github.com/syncthing/syncthing/lib/sync"
)
@@ -373,7 +373,7 @@ func (m *Matcher) SkipIgnoredDirs() bool {
}
func hashPatterns(patterns []Pattern) string {
h := md5.New()
h := sha256.New()
for _, pat := range patterns {
h.Write([]byte(pat.String()))
h.Write([]byte("\n"))
+4 -3
View File
@@ -607,8 +607,9 @@ func TestHashOfEmpty(t *testing.T) {
firstHash := p1.Hash()
// Reloading with a non-existent file should empty the patterns and
// recalculate the hash. d41d8cd98f00b204e9800998ecf8427e is the md5 of
// nothing.
// recalculate the hash.
// e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 is
// the sah256 of nothing.
p1.Load("file/does/not/exist")
secondHash := p1.Hash()
@@ -616,7 +617,7 @@ func TestHashOfEmpty(t *testing.T) {
if firstHash == secondHash {
t.Error("hash did not change")
}
if secondHash != "d41d8cd98f00b204e9800998ecf8427e" {
if secondHash != "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" {
t.Error("second hash is not hash of empty string")
}
if len(p1.patterns) != 0 {
+2 -2
View File
@@ -115,7 +115,7 @@ func expandLocations() error {
newLocations := make(map[LocationEnum]string)
for key, dir := range locationTemplates {
for varName, value := range baseDirs {
dir = strings.Replace(dir, "${"+string(varName)+"}", value, -1)
dir = strings.ReplaceAll(dir, "${"+string(varName)+"}", value)
}
var err error
dir, err = fs.ExpandTilde(dir)
@@ -197,5 +197,5 @@ func GetTimestamped(key LocationEnum) string {
// 2006 replaced by 2015...
tpl := locations[key]
now := time.Now().Format("20060102-150405")
return strings.Replace(tpl, "${timestamp}", now, -1)
return strings.ReplaceAll(tpl, "${timestamp}", now)
}
+1 -1
View File
@@ -337,7 +337,7 @@ func (r *recorder) Clear() {
func (r *recorder) append(l LogLevel, msg string) {
line := Line{
When: time.Now(),
When: time.Now(), // intentionally high precision
Message: msg,
Level: l,
}
+2 -4
View File
@@ -7,10 +7,11 @@
package model
import (
"github.com/syncthing/syncthing/lib/protocol"
"reflect"
"sort"
"testing"
"github.com/syncthing/syncthing/lib/protocol"
)
var (
@@ -46,9 +47,6 @@ func Test_chunk(t *testing.T) {
}
func Test_inOrderBlockPullReorderer_Reorder(t *testing.T) {
type args struct {
blocks []protocol.BlockInfo
}
tests := []struct {
name string
blocks []protocol.BlockInfo
+41 -27
View File
@@ -33,7 +33,7 @@ func newFakeConnection(id protocol.DeviceID, model Model) *fakeConnection {
})
f.IDReturns(id)
f.CloseCalls(func(err error) {
model.Closed(f, err)
model.Closed(id, err)
f.ClosedReturns(true)
})
return f
@@ -62,33 +62,34 @@ func (f *fakeConnection) DownloadProgress(_ context.Context, folder string, upda
})
}
func (f *fakeConnection) addFileLocked(name string, flags uint32, ftype protocol.FileInfoType, data []byte, version protocol.Vector) {
func (f *fakeConnection) addFileLocked(name string, flags uint32, ftype protocol.FileInfoType, data []byte, version protocol.Vector, localFlags uint32) {
blockSize := protocol.BlockSize(int64(len(data)))
blocks, _ := scanner.Blocks(context.TODO(), bytes.NewReader(data), blockSize, int64(len(data)), nil, true)
if ftype == protocol.FileInfoTypeFile || ftype == protocol.FileInfoTypeDirectory {
f.files = append(f.files, protocol.FileInfo{
Name: name,
Type: ftype,
Size: int64(len(data)),
ModifiedS: time.Now().Unix(),
Permissions: flags,
Version: version,
Sequence: time.Now().UnixNano(),
RawBlockSize: blockSize,
Blocks: blocks,
})
} else {
// Symlink
f.files = append(f.files, protocol.FileInfo{
Name: name,
Type: ftype,
Version: version,
Sequence: time.Now().UnixNano(),
SymlinkTarget: string(data),
NoPermissions: true,
})
file := protocol.FileInfo{
Name: name,
Type: ftype,
Version: version,
Sequence: time.Now().UnixNano(),
LocalFlags: localFlags,
}
switch ftype {
case protocol.FileInfoTypeFile, protocol.FileInfoTypeDirectory:
file.ModifiedS = time.Now().Unix()
file.Permissions = flags
if ftype == protocol.FileInfoTypeFile {
file.Size = int64(len(data))
file.RawBlockSize = blockSize
file.Blocks = blocks
}
default: // Symlink
file.Name = name
file.Type = ftype
file.Version = version
file.SymlinkTarget = string(data)
file.NoPermissions = true
}
f.files = append(f.files, file)
if f.fileData == nil {
f.fileData = make(map[string][]byte)
@@ -96,13 +97,22 @@ func (f *fakeConnection) addFileLocked(name string, flags uint32, ftype protocol
f.fileData[name] = data
}
func (f *fakeConnection) addFileWithLocalFlags(name string, ftype protocol.FileInfoType, localFlags uint32) {
f.mut.Lock()
defer f.mut.Unlock()
var version protocol.Vector
version = version.Update(f.id.Short())
f.addFileLocked(name, 0, ftype, nil, version, localFlags)
}
func (f *fakeConnection) addFile(name string, flags uint32, ftype protocol.FileInfoType, data []byte) {
f.mut.Lock()
defer f.mut.Unlock()
var version protocol.Vector
version = version.Update(f.id.Short())
f.addFileLocked(name, flags, ftype, data, version)
f.addFileLocked(name, flags, ftype, data, version, 0)
}
func (f *fakeConnection) updateFile(name string, flags uint32, ftype protocol.FileInfoType, data []byte) {
@@ -112,7 +122,7 @@ func (f *fakeConnection) updateFile(name string, flags uint32, ftype protocol.Fi
for i, fi := range f.files {
if fi.Name == name {
f.files = append(f.files[:i], f.files[i+1:]...)
f.addFileLocked(name, flags, ftype, data, fi.Version.Update(f.id.Short()))
f.addFileLocked(name, flags, ftype, data, fi.Version.Update(f.id.Short()), 0)
return
}
}
@@ -137,7 +147,11 @@ func (f *fakeConnection) deleteFile(name string) {
}
func (f *fakeConnection) sendIndexUpdate() {
f.model.IndexUpdate(f.id, f.folder, f.files)
toSend := make([]protocol.FileInfo, len(f.files))
for i := range f.files {
toSend[i] = prepareFileInfoForIndex(f.files[i])
}
f.model.IndexUpdate(f.id, f.folder, toSend)
}
func addFakeConn(m *testModel, dev protocol.DeviceID) *fakeConnection {
+84 -40
View File
@@ -27,6 +27,7 @@ import (
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/scanner"
"github.com/syncthing/syncthing/lib/stats"
"github.com/syncthing/syncthing/lib/svcutil"
"github.com/syncthing/syncthing/lib/sync"
"github.com/syncthing/syncthing/lib/util"
"github.com/syncthing/syncthing/lib/versioner"
@@ -87,7 +88,7 @@ type syncRequest struct {
}
type puller interface {
pull() bool // true when successful and should not be retried
pull() (bool, error) // true when successful and should not be retried
}
func newFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, evLogger events.Logger, ioLimiter *byteSemaphore, ver versioner.Versioner) folder {
@@ -164,16 +165,20 @@ func (f *folder) Serve(ctx context.Context) error {
initialCompleted := f.initialScanFinished
for {
var err error
select {
case <-f.ctx.Done():
close(f.done)
return nil
case <-f.pullScheduled:
f.pull()
_, err = f.pull()
case <-f.pullFailTimer.C:
if !f.pull() && f.pullPause < 60*f.pullBasePause() {
var success bool
success, err = f.pull()
if (err != nil || !success) && f.pullPause < 60*f.pullBasePause() {
// Back off from retrying to pull
f.pullPause *= 2
}
@@ -181,18 +186,19 @@ func (f *folder) Serve(ctx context.Context) error {
case <-initialCompleted:
// Initial scan has completed, we should do a pull
initialCompleted = nil // never hit this case again
f.pull()
_, err = f.pull()
case <-f.forcedRescanRequested:
f.handleForcedRescans()
err = f.handleForcedRescans()
case <-f.scanTimer.C:
l.Debugln(f, "Scanning due to timer")
f.scanTimerFired()
err = f.scanTimerFired()
case req := <-f.doInSyncChan:
l.Debugln(f, "Running something due to request")
req.err <- req.fn()
err = req.fn()
req.err <- err
case next := <-f.scanDelay:
l.Debugln(f, "Delaying scan")
@@ -200,16 +206,23 @@ func (f *folder) Serve(ctx context.Context) error {
case fsEvents := <-f.watchChan:
l.Debugln(f, "Scan due to watcher")
f.scanSubdirs(fsEvents)
err = f.scanSubdirs(fsEvents)
case <-f.restartWatchChan:
l.Debugln(f, "Restart watcher")
f.restartWatch()
err = f.restartWatch()
case <-f.versionCleanupTimer.C:
l.Debugln(f, "Doing version cleanup")
f.versionCleanupTimerFired()
}
if err != nil {
if svcutil.IsFatal(err) {
return err
}
f.setError(err)
}
}
}
@@ -283,8 +296,10 @@ func (f *folder) getHealthErrorAndLoadIgnores() error {
if err := f.getHealthErrorWithoutIgnores(); err != nil {
return err
}
if err := f.ignores.Load(".stignore"); err != nil && !fs.IsNotExist(err) {
return errors.Wrap(err, "loading ignores")
if f.Type != config.FolderTypeReceiveEncrypted {
if err := f.ignores.Load(".stignore"); err != nil && !fs.IsNotExist(err) {
return errors.Wrap(err, "loading ignores")
}
}
return nil
}
@@ -307,7 +322,7 @@ func (f *folder) getHealthErrorWithoutIgnores() error {
return nil
}
func (f *folder) pull() (success bool) {
func (f *folder) pull() (success bool, err error) {
f.pullFailTimer.Stop()
select {
case <-f.pullFailTimer.C:
@@ -318,7 +333,7 @@ func (f *folder) pull() (success bool) {
case <-f.initialScanFinished:
default:
// Once the initial scan finished, a pull will be scheduled
return true
return true, nil
}
defer func() {
@@ -330,7 +345,10 @@ func (f *folder) pull() (success bool) {
// If there is nothing to do, don't even enter sync-waiting state.
abort := true
snap := f.fset.Snapshot()
snap, err := f.dbSnapshot()
if err != nil {
return false, err
}
snap.WithNeed(protocol.LocalDeviceID, func(intf protocol.FileIntf) bool {
abort = false
return false
@@ -341,16 +359,16 @@ func (f *folder) pull() (success bool) {
f.errorsMut.Lock()
f.pullErrors = nil
f.errorsMut.Unlock()
return true
return true, nil
}
// Abort early (before acquiring a token) if there's a folder error
err := f.getHealthErrorWithoutIgnores()
f.setError(err)
err = f.getHealthErrorWithoutIgnores()
if err != nil {
l.Debugln("Skipping pull of", f.Description(), "due to folder error:", err)
return false
return false, err
}
f.setError(nil)
// Send only folder doesn't do any io, it only checks for out-of-sync
// items that differ in metadata and updates those.
@@ -358,8 +376,7 @@ func (f *folder) pull() (success bool) {
f.setState(FolderSyncWaiting)
if err := f.ioLimiter.takeWithContext(f.ctx, 1); err != nil {
f.setError(err)
return true
return true, err
}
defer f.ioLimiter.give(1)
}
@@ -374,23 +391,23 @@ func (f *folder) pull() (success bool) {
}
}()
err = f.getHealthErrorAndLoadIgnores()
f.setError(err)
if err != nil {
l.Debugln("Skipping pull of", f.Description(), "due to folder error:", err)
return false
return false, err
}
success = f.puller.pull()
success, err = f.puller.pull()
if success {
return true
if success && err == nil {
return true, nil
}
// Pulling failed, try again later.
delay := f.pullPause + time.Since(startTime)
l.Infof("Folder %v isn't making sync progress - retrying in %v.", f.Description(), util.NiceDurationString(delay))
f.pullFailTimer.Reset(delay)
return false
return false, err
}
func (f *folder) scanSubdirs(subDirs []string) error {
@@ -399,7 +416,6 @@ func (f *folder) scanSubdirs(subDirs []string) error {
oldHash := f.ignores.Hash()
err := f.getHealthErrorAndLoadIgnores()
f.setError(err)
if err != nil {
// If there is a health error we set it as the folder error. We do not
// clear the folder error if there is no health error, as there might be
@@ -407,6 +423,7 @@ func (f *folder) scanSubdirs(subDirs []string) error {
// we do not use the CheckHealth() convenience function here.
return err
}
f.setError(nil)
// Check on the way out if the ignore patterns changed as part of scanning
// this folder. If they did we should schedule a pull of the folder so that
@@ -443,7 +460,10 @@ func (f *folder) scanSubdirs(subDirs []string) error {
// Clean the list of subitems to ensure that we start at a known
// directory, and don't scan subdirectories of things we've already
// scanned.
snap := f.fset.Snapshot()
snap, err := f.dbSnapshot()
if err != nil {
return err
}
subDirs = unifySubs(subDirs, func(file string) bool {
_, ok := snap.Get(protocol.LocalDeviceID, file)
return ok
@@ -560,7 +580,10 @@ func (f *folder) scanSubdirsBatchAppendFunc(batch *fileInfoBatch) batchAppendFun
func (f *folder) scanSubdirsChangedAndNew(subDirs []string, batch *fileInfoBatch, batchAppend batchAppendFunc) (int, error) {
changes := 0
snap := f.fset.Snapshot()
snap, err := f.dbSnapshot()
if err != nil {
return changes, err
}
defer snap.Release()
// If we return early e.g. due to a folder health error, the scan needs
@@ -629,7 +652,10 @@ func (f *folder) scanSubdirsDeletedAndIgnored(subDirs []string, batch *fileInfoB
var toIgnore []db.FileInfoTruncated
ignoredParent := ""
changes := 0
snap := f.fset.Snapshot()
snap, err := f.dbSnapshot()
if err != nil {
return 0, err
}
defer snap.Release()
for _, sub := range subDirs {
@@ -821,7 +847,7 @@ func (f *folder) findRename(snap *db.Snapshot, file protocol.FileInfo, alreadyUs
return nf, found
}
func (f *folder) scanTimerFired() {
func (f *folder) scanTimerFired() error {
err := f.scanSubdirs(nil)
select {
@@ -836,6 +862,8 @@ func (f *folder) scanTimerFired() {
}
f.Reschedule()
return err
}
func (f *folder) versionCleanupTimerFired() {
@@ -884,10 +912,10 @@ func (f *folder) scheduleWatchRestart() {
// restartWatch should only ever be called synchronously. If you want to use
// this asynchronously, you should probably use scheduleWatchRestart instead.
func (f *folder) restartWatch() {
func (f *folder) restartWatch() error {
f.stopWatch()
f.startWatch()
f.scanSubdirs(nil)
return f.scanSubdirs(nil)
}
// startWatch should only ever be called synchronously. If you want to use
@@ -1166,7 +1194,7 @@ func (f *folder) emitDiskChangeEvents(fs []protocol.FileInfo, typeOfEvent events
}
}
func (f *folder) handleForcedRescans() {
func (f *folder) handleForcedRescans() error {
f.forcedRescanPathsMut.Lock()
paths := make([]string, 0, len(f.forcedRescanPaths))
for path := range f.forcedRescanPaths {
@@ -1175,7 +1203,7 @@ func (f *folder) handleForcedRescans() {
f.forcedRescanPaths = make(map[string]struct{})
f.forcedRescanPathsMut.Unlock()
if len(paths) == 0 {
return
return nil
}
batch := newFileInfoBatch(func(fs []protocol.FileInfo) error {
@@ -1183,10 +1211,16 @@ func (f *folder) handleForcedRescans() {
return nil
})
snap := f.fset.Snapshot()
snap, err := f.dbSnapshot()
if err != nil {
return err
}
defer snap.Release()
for _, path := range paths {
_ = batch.flushIfFull()
if err := batch.flushIfFull(); err != nil {
return err
}
fi, ok := snap.Get(protocol.LocalDeviceID, path)
if !ok {
@@ -1196,11 +1230,21 @@ func (f *folder) handleForcedRescans() {
batch.append(fi)
}
snap.Release()
if err = batch.flush(); err != nil {
return err
}
_ = batch.flush()
return f.scanSubdirs(paths)
}
_ = f.scanSubdirs(paths)
// dbSnapshots gets a snapshot from the fileset, and wraps any error
// in a svcutil.FatalErr.
func (f *folder) dbSnapshot() (*db.Snapshot, error) {
snap, err := f.fset.Snapshot()
if err != nil {
return nil, svcutil.AsFatalErr(err, svcutil.ExitError)
}
return snap, nil
}
// The exists function is expected to return true for all known paths
+8 -7
View File
@@ -32,10 +32,10 @@ func newReceiveEncryptedFolder(model *model, fset *db.FileSet, ignores *ignore.M
}
func (f *receiveEncryptedFolder) Revert() {
f.doInSync(func() error { f.revert(); return nil })
f.doInSync(f.revert)
}
func (f *receiveEncryptedFolder) revert() {
func (f *receiveEncryptedFolder) revert() error {
l.Infof("Reverting unexpected items in folder %v (receive-encrypted)", f.Description())
f.setState(FolderScanning)
@@ -46,7 +46,10 @@ func (f *receiveEncryptedFolder) revert() {
return nil
})
snap := f.fset.Snapshot()
snap, err := f.dbSnapshot()
if err != nil {
return err
}
defer snap.Release()
var iterErr error
var dirs []string
@@ -85,12 +88,10 @@ func (f *receiveEncryptedFolder) revert() {
f.revertHandleDirs(dirs, snap)
if iterErr == nil {
iterErr = batch.flush()
}
if iterErr != nil {
l.Infoln("Failed to delete unexpected items:", iterErr)
return iterErr
}
return batch.flush()
}
func (f *receiveEncryptedFolder) revertHandleDirs(dirs []string, snap *db.Snapshot) {
+8 -3
View File
@@ -63,10 +63,10 @@ func newReceiveOnlyFolder(model *model, fset *db.FileSet, ignores *ignore.Matche
}
func (f *receiveOnlyFolder) Revert() {
f.doInSync(func() error { f.revert(); return nil })
f.doInSync(f.revert)
}
func (f *receiveOnlyFolder) revert() {
func (f *receiveOnlyFolder) revert() error {
l.Infof("Reverting folder %v", f.Description)
f.setState(FolderScanning)
@@ -84,7 +84,10 @@ func (f *receiveOnlyFolder) revert() {
batch := make([]protocol.FileInfo, 0, maxBatchSizeFiles)
batchSizeBytes := 0
snap := f.fset.Snapshot()
snap, err := f.dbSnapshot()
if err != nil {
return err
}
defer snap.Release()
snap.WithHave(protocol.LocalDeviceID, func(intf protocol.FileIntf) bool {
fi := intf.(protocol.FileInfo)
@@ -161,6 +164,8 @@ func (f *receiveOnlyFolder) revert() {
// pull by itself. Make sure we schedule one so that we start
// downloading files.
f.SchedulePull()
return nil
}
// deleteQueue handles deletes by delegating to a handler and queuing
+1 -1
View File
@@ -384,7 +384,7 @@ func TestRecvOnlyRemoteUndoChanges(t *testing.T) {
// Do the same changes on the remote
files := make([]protocol.FileInfo, 0, 2)
snap := f.fset.Snapshot()
snap := fsetSnapshot(t, f.fset)
snap.WithHave(protocol.LocalDeviceID, func(fi protocol.FileIntf) bool {
if n := fi.FileName(); n != file && n != knownFile {
return true
+13 -6
View File
@@ -36,11 +36,14 @@ func (f *sendOnlyFolder) PullErrors() []FileError {
}
// pull checks need for files that only differ by metadata (no changes on disk)
func (f *sendOnlyFolder) pull() bool {
func (f *sendOnlyFolder) pull() (bool, error) {
batch := make([]protocol.FileInfo, 0, maxBatchSizeFiles)
batchSizeBytes := 0
snap := f.fset.Snapshot()
snap, err := f.dbSnapshot()
if err != nil {
return false, err
}
defer snap.Release()
snap.WithNeed(protocol.LocalDeviceID, func(intf protocol.FileIntf) bool {
if len(batch) == maxBatchSizeFiles || batchSizeBytes > maxBatchSizeBytes {
@@ -83,14 +86,14 @@ func (f *sendOnlyFolder) pull() bool {
f.updateLocalsFromPulling(batch)
}
return true
return true, nil
}
func (f *sendOnlyFolder) Override() {
f.doInSync(func() error { f.override(); return nil })
f.doInSync(f.override)
}
func (f *sendOnlyFolder) override() {
func (f *sendOnlyFolder) override() error {
l.Infoln("Overriding global state on folder", f.Description())
f.setState(FolderScanning)
@@ -98,7 +101,10 @@ func (f *sendOnlyFolder) override() {
batch := make([]protocol.FileInfo, 0, maxBatchSizeFiles)
batchSizeBytes := 0
snap := f.fset.Snapshot()
snap, err := f.dbSnapshot()
if err != nil {
return err
}
defer snap.Release()
snap.WithNeed(protocol.LocalDeviceID, func(fi protocol.FileIntf) bool {
need := fi.(protocol.FileInfo)
@@ -130,4 +136,5 @@ func (f *sendOnlyFolder) override() {
if len(batch) > 0 {
f.updateLocalsFromScanning(batch)
}
return nil
}
+28 -15
View File
@@ -156,7 +156,7 @@ func newSendReceiveFolder(model *model, fset *db.FileSet, ignores *ignore.Matche
// pull returns true if it manages to get all needed items from peers, i.e. get
// the device in sync with the global state.
func (f *sendReceiveFolder) pull() bool {
func (f *sendReceiveFolder) pull() (bool, error) {
l.Debugf("%v pulling", f)
scanChan := make(chan string)
@@ -173,10 +173,11 @@ func (f *sendReceiveFolder) pull() bool {
f.pullErrors = nil
f.errorsMut.Unlock()
var err error
for tries := 0; tries < maxPullerIterations; tries++ {
select {
case <-f.ctx.Done():
return false
return false, f.ctx.Err()
default:
}
@@ -184,7 +185,10 @@ func (f *sendReceiveFolder) pull() bool {
// it to FolderSyncing during the last iteration.
f.setState(FolderSyncPreparing)
changed = f.pullerIteration(scanChan)
changed, err = f.pullerIteration(scanChan)
if err != nil {
return false, err
}
l.Debugln(f, "changed", changed, "on try", tries+1)
@@ -219,19 +223,22 @@ func (f *sendReceiveFolder) pull() bool {
})
}
return changed == 0
return changed == 0, nil
}
// pullerIteration runs a single puller iteration for the given folder and
// returns the number items that should have been synced (even those that
// might have failed). One puller iteration handles all files currently
// flagged as needed in the folder.
func (f *sendReceiveFolder) pullerIteration(scanChan chan<- string) int {
func (f *sendReceiveFolder) pullerIteration(scanChan chan<- string) (int, error) {
f.errorsMut.Lock()
f.tempPullErrors = make(map[string]string)
f.errorsMut.Unlock()
snap := f.fset.Snapshot()
snap, err := f.dbSnapshot()
if err != nil {
return 0, err
}
defer snap.Release()
pullChan := make(chan pullBlockState)
@@ -265,7 +272,7 @@ func (f *sendReceiveFolder) pullerIteration(scanChan chan<- string) int {
pullWg.Add(1)
go func() {
// pullerRoutine finishes when pullChan is closed
f.pullerRoutine(pullChan, finisherChan)
f.pullerRoutine(snap, pullChan, finisherChan)
pullWg.Done()
}()
@@ -300,7 +307,7 @@ func (f *sendReceiveFolder) pullerIteration(scanChan chan<- string) int {
f.queue.Reset()
return changed
return changed, err
}
func (f *sendReceiveFolder) processNeeded(snap *db.Snapshot, dbUpdateChan chan<- dbUpdateJob, copyChan chan<- copyBlocksState, scanChan chan<- string) (int, map[string]protocol.FileInfo, []protocol.FileInfo, error) {
@@ -350,6 +357,11 @@ func (f *sendReceiveFolder) processNeeded(snap *db.Snapshot, dbUpdateChan chan<-
changed--
}
case file.IsInvalid():
// Global invalid file just exists for need accounting
l.Debugln(f, "Handling global invalid item", file)
dbUpdateChan <- dbUpdateJob{file, dbUpdateInvalidate}
case file.IsDeleted():
if file.IsDirectory() {
// Perform directory deletions at the end, as we may have
@@ -582,7 +594,7 @@ func (f *sendReceiveFolder) handleDir(file protocol.FileInfo, snap *db.Snapshot,
// that don't result in a conflict.
case err == nil && !info.IsDir():
// Check that it is what we have in the database.
curFile, hasCurFile := f.model.CurrentFolderFile(f.folderID, file.Name)
curFile, hasCurFile := snap.Get(protocol.LocalDeviceID, file.Name)
if err := f.scanIfItemChanged(file.Name, info, curFile, hasCurFile, scanChan); err != nil {
err = errors.Wrap(err, "handling dir")
f.newPullError(file.Name, err)
@@ -766,7 +778,7 @@ func (f *sendReceiveFolder) handleSymlinkCheckExisting(file protocol.FileInfo, s
return err
}
// Check that it is what we have in the database.
curFile, hasCurFile := f.model.CurrentFolderFile(f.folderID, file.Name)
curFile, hasCurFile := snap.Get(protocol.LocalDeviceID, file.Name)
if err := f.scanIfItemChanged(file.Name, info, curFile, hasCurFile, scanChan); err != nil {
return err
}
@@ -1427,7 +1439,7 @@ func (f *sendReceiveFolder) verifyBuffer(buf []byte, block protocol.BlockInfo) e
return nil
}
func (f *sendReceiveFolder) pullerRoutine(in <-chan pullBlockState, out chan<- *sharedPullerState) {
func (f *sendReceiveFolder) pullerRoutine(snap *db.Snapshot, in <-chan pullBlockState, out chan<- *sharedPullerState) {
requestLimiter := newByteSemaphore(f.PullerMaxPendingKiB * 1024)
wg := sync.NewWaitGroup()
@@ -1458,13 +1470,13 @@ func (f *sendReceiveFolder) pullerRoutine(in <-chan pullBlockState, out chan<- *
defer wg.Done()
defer requestLimiter.give(bytes)
f.pullBlock(state, out)
f.pullBlock(state, snap, out)
}()
}
wg.Wait()
}
func (f *sendReceiveFolder) pullBlock(state pullBlockState, out chan<- *sharedPullerState) {
func (f *sendReceiveFolder) pullBlock(state pullBlockState, snap *db.Snapshot, out chan<- *sharedPullerState) {
// Get an fd to the temporary file. Technically we don't need it until
// after fetching the block, but if we run into an error here there is
// no point in issuing the request to the network.
@@ -1483,12 +1495,13 @@ func (f *sendReceiveFolder) pullBlock(state pullBlockState, out chan<- *sharedPu
}
var lastError error
candidates := f.model.Availability(f.folderID, state.file, state.block)
candidates := f.model.availabilityInSnapshot(f.FolderConfiguration, snap, state.file, state.block)
loop:
for {
select {
case <-f.ctx.Done():
state.fail(errors.Wrap(f.ctx.Err(), "folder stopped"))
break
break loop
default:
}
+25 -24
View File
@@ -135,7 +135,7 @@ func TestHandleFile(t *testing.T) {
copyChan := make(chan copyBlocksState, 1)
f.handleFile(requiredFile, f.fset.Snapshot(), copyChan)
f.handleFile(requiredFile, fsetSnapshot(t, f.fset), copyChan)
// Receive the results
toCopy := <-copyChan
@@ -181,7 +181,7 @@ func TestHandleFileWithTemp(t *testing.T) {
copyChan := make(chan copyBlocksState, 1)
f.handleFile(requiredFile, f.fset.Snapshot(), copyChan)
f.handleFile(requiredFile, fsetSnapshot(t, f.fset), copyChan)
// Receive the results
toCopy := <-copyChan
@@ -245,7 +245,7 @@ func TestCopierFinder(t *testing.T) {
go f.copierRoutine(copyChan, pullChan, finisherChan)
defer close(copyChan)
f.handleFile(requiredFile, f.fset.Snapshot(), copyChan)
f.handleFile(requiredFile, fsetSnapshot(t, f.fset), copyChan)
timeout := time.After(10 * time.Second)
pulls := make([]pullBlockState, 4)
@@ -379,7 +379,7 @@ func TestWeakHash(t *testing.T) {
// Test 1 - no weak hashing, file gets fully repulled (`expectBlocks` pulls).
fo.WeakHashThresholdPct = 101
fo.handleFile(desiredFile, fo.fset.Snapshot(), copyChan)
fo.handleFile(desiredFile, fsetSnapshot(t, fo.fset), copyChan)
var pulls []pullBlockState
timeout := time.After(10 * time.Second)
@@ -408,7 +408,7 @@ func TestWeakHash(t *testing.T) {
// Test 2 - using weak hash, expectPulls blocks pulled.
fo.WeakHashThresholdPct = -1
fo.handleFile(desiredFile, fo.fset.Snapshot(), copyChan)
fo.handleFile(desiredFile, fsetSnapshot(t, fo.fset), copyChan)
pulls = pulls[:0]
for len(pulls) < expectPulls {
@@ -489,7 +489,7 @@ func TestDeregisterOnFailInCopy(t *testing.T) {
finisherBufferChan := make(chan *sharedPullerState, 1)
finisherChan := make(chan *sharedPullerState)
dbUpdateChan := make(chan dbUpdateJob, 1)
snap := f.fset.Snapshot()
snap := fsetSnapshot(t, f.fset)
copyChan, copyWg := startCopier(f, pullChan, finisherBufferChan)
go f.finisherRoutine(snap, finisherChan, dbUpdateChan, make(chan string))
@@ -589,13 +589,13 @@ func TestDeregisterOnFailInPull(t *testing.T) {
finisherBufferChan := make(chan *sharedPullerState)
finisherChan := make(chan *sharedPullerState)
dbUpdateChan := make(chan dbUpdateJob, 1)
snap := f.fset.Snapshot()
snap := fsetSnapshot(t, f.fset)
copyChan, copyWg := startCopier(f, pullChan, finisherBufferChan)
pullWg := sync.NewWaitGroup()
pullWg.Add(1)
go func() {
f.pullerRoutine(pullChan, finisherBufferChan)
f.pullerRoutine(snap, pullChan, finisherBufferChan)
pullWg.Done()
}()
go f.finisherRoutine(snap, finisherChan, dbUpdateChan, make(chan string))
@@ -696,7 +696,7 @@ func TestIssue3164(t *testing.T) {
dbUpdateChan := make(chan dbUpdateJob, 1)
f.deleteDir(file, f.fset.Snapshot(), dbUpdateChan, make(chan string))
f.deleteDir(file, fsetSnapshot(t, f.fset), dbUpdateChan, make(chan string))
if _, err := ffs.Stat("issue3164"); !fs.IsNotExist(err) {
t.Fatal(err)
@@ -828,7 +828,7 @@ func TestCopyOwner(t *testing.T) {
dbUpdateChan := make(chan dbUpdateJob, 1)
scanChan := make(chan string)
defer close(dbUpdateChan)
f.handleDir(dir, f.fset.Snapshot(), dbUpdateChan, scanChan)
f.handleDir(dir, fsetSnapshot(t, f.fset), dbUpdateChan, scanChan)
select {
case <-dbUpdateChan: // empty the channel for later
case toScan := <-scanChan:
@@ -858,7 +858,7 @@ func TestCopyOwner(t *testing.T) {
// but it's the way data is passed around. When the database update
// comes the finisher is done.
snap := f.fset.Snapshot()
snap := fsetSnapshot(t, f.fset)
finisherChan := make(chan *sharedPullerState)
copierChan, copyWg := startCopier(f, nil, finisherChan)
go f.finisherRoutine(snap, finisherChan, dbUpdateChan, nil)
@@ -926,7 +926,7 @@ func TestSRConflictReplaceFileByDir(t *testing.T) {
dbUpdateChan := make(chan dbUpdateJob, 1)
scanChan := make(chan string, 1)
f.handleDir(file, f.fset.Snapshot(), dbUpdateChan, scanChan)
f.handleDir(file, fsetSnapshot(t, f.fset), dbUpdateChan, scanChan)
if confls := existingConflicts(name, ffs); len(confls) != 1 {
t.Fatal("Expected one conflict, got", len(confls))
@@ -959,7 +959,7 @@ func TestSRConflictReplaceFileByLink(t *testing.T) {
dbUpdateChan := make(chan dbUpdateJob, 1)
scanChan := make(chan string, 1)
f.handleSymlink(file, f.fset.Snapshot(), dbUpdateChan, scanChan)
f.handleSymlink(file, fsetSnapshot(t, f.fset), dbUpdateChan, scanChan)
if confls := existingConflicts(name, ffs); len(confls) != 1 {
t.Fatal("Expected one conflict, got", len(confls))
@@ -1001,7 +1001,7 @@ func TestDeleteBehindSymlink(t *testing.T) {
fi.Version = fi.Version.Update(device1.Short())
scanChan := make(chan string, 1)
dbUpdateChan := make(chan dbUpdateJob, 1)
f.deleteFile(fi, f.fset.Snapshot(), dbUpdateChan, scanChan)
f.deleteFile(fi, fsetSnapshot(t, f.fset), dbUpdateChan, scanChan)
select {
case f := <-scanChan:
t.Fatalf("Received %v on scanChan", f)
@@ -1031,7 +1031,7 @@ func TestPullCtxCancel(t *testing.T) {
var cancel context.CancelFunc
f.ctx, cancel = context.WithCancel(context.Background())
go f.pullerRoutine(pullChan, finisherChan)
go f.pullerRoutine(fsetSnapshot(t, f.fset), pullChan, finisherChan)
defer close(pullChan)
emptyState := func() pullBlockState {
@@ -1077,7 +1077,7 @@ func TestPullDeleteUnscannedDir(t *testing.T) {
scanChan := make(chan string, 1)
dbUpdateChan := make(chan dbUpdateJob, 1)
f.deleteDir(fi, f.fset.Snapshot(), dbUpdateChan, scanChan)
f.deleteDir(fi, fsetSnapshot(t, f.fset), dbUpdateChan, scanChan)
if _, err := ffs.Stat(dir); fs.IsNotExist(err) {
t.Error("directory has been deleted")
@@ -1226,7 +1226,7 @@ func TestPullTempFileCaseConflict(t *testing.T) {
fd.Close()
}
f.handleFile(file, f.fset.Snapshot(), copyChan)
f.handleFile(file, fsetSnapshot(t, f.fset), copyChan)
cs := <-copyChan
if _, err := cs.tempFile(); err != nil {
@@ -1252,7 +1252,7 @@ func TestPullCaseOnlyRename(t *testing.T) {
must(t, f.scanSubdirs(nil))
cur, ok := m.CurrentFolderFile(f.ID, name)
cur, ok := m.testCurrentFolderFile(f.ID, name)
if !ok {
t.Fatal("file missing")
}
@@ -1266,7 +1266,7 @@ func TestPullCaseOnlyRename(t *testing.T) {
dbUpdateChan := make(chan dbUpdateJob, 2)
scanChan := make(chan string, 2)
snap := f.fset.Snapshot()
snap := fsetSnapshot(t, f.fset)
defer snap.Release()
if err := f.renameFile(cur, deleted, confl, snap, dbUpdateChan, scanChan); err != nil {
t.Error(err)
@@ -1293,7 +1293,7 @@ func TestPullSymlinkOverExistingWindows(t *testing.T) {
must(t, f.scanSubdirs(nil))
file, ok := m.CurrentFolderFile(f.ID, name)
file, ok := m.testCurrentFolderFile(f.ID, name)
if !ok {
t.Fatal("file missing")
}
@@ -1301,11 +1301,12 @@ func TestPullSymlinkOverExistingWindows(t *testing.T) {
scanChan := make(chan string)
changed := f.pullerIteration(scanChan)
changed, err := f.pullerIteration(scanChan)
must(t, err)
if changed != 1 {
t.Error("Expected one change in pull, got", changed)
}
if file, ok := m.CurrentFolderFile(f.ID, name); !ok {
if file, ok := m.testCurrentFolderFile(f.ID, name); !ok {
t.Error("symlink entry missing")
} else if !file.IsUnsupported() {
t.Error("symlink entry isn't marked as unsupported")
@@ -1341,7 +1342,7 @@ func TestPullDeleteCaseConflict(t *testing.T) {
t.Error("Missing db update for file")
}
snap := f.fset.Snapshot()
snap := fsetSnapshot(t, f.fset)
defer snap.Release()
f.deleteDir(fi, snap, dbUpdateChan, scanChan)
select {
@@ -1371,7 +1372,7 @@ func TestPullDeleteIgnoreChildDir(t *testing.T) {
scanChan := make(chan string, 2)
err := f.deleteDirOnDisk(parent, f.fset.Snapshot(), scanChan)
err := f.deleteDirOnDisk(parent, fsetSnapshot(t, f.fset), scanChan)
if err == nil {
t.Error("no error")
}
+10 -5
View File
@@ -96,7 +96,7 @@ func (c *folderSummaryService) Summary(folder string) (map[string]interface{}, e
// For API backwards compatibility (SyncTrayzor needs it) an empty folder
// summary is returned for not running folders, an error might actually be
// more appropriate
if err != nil && err != ErrFolderPaused && err != errFolderNotRunning {
if err != nil && err != ErrFolderPaused && err != ErrFolderNotRunning {
return nil, err
}
@@ -348,9 +348,14 @@ func (c *folderSummaryService) sendSummary(ctx context.Context, folder string) {
// Get completion percentage of this folder for the
// remote device.
comp := c.model.Completion(devCfg.DeviceID, folder).Map()
comp["folder"] = folder
comp["device"] = devCfg.DeviceID.String()
c.evLogger.Log(events.FolderCompletion, comp)
comp, err := c.model.Completion(devCfg.DeviceID, folder)
if err != nil {
l.Debugf("Error getting completion for folder %v, device %v: %v", folder, devCfg.DeviceID, err)
continue
}
ev := comp.Map()
ev["folder"] = folder
ev["device"] = devCfg.DeviceID.String()
c.evLogger.Log(events.FolderCompletion, ev)
}
}
+2 -2
View File
@@ -100,7 +100,7 @@ func (s *stateTracker) setState(newState folderState) {
}
s.current = newState
s.changed = time.Now()
s.changed = time.Now().Truncate(time.Second)
s.evLogger.Log(events.StateChanged, eventData)
}
@@ -139,7 +139,7 @@ func (s *stateTracker) setError(err error) {
}
s.err = err
s.changed = time.Now()
s.changed = time.Now().Truncate(time.Second)
s.evLogger.Log(events.StateChanged, eventData)
}
+21 -17
View File
@@ -25,7 +25,6 @@ type indexSender struct {
conn protocol.Connection
folder string
folderIsReceiveEncrypted bool
dev string
fset *db.FileSet
prevSequence int64
evLogger events.Logger
@@ -131,7 +130,10 @@ func (s *indexSender) sendIndexTo(ctx context.Context) error {
var err error
var f protocol.FileInfo
snap := s.fset.Snapshot()
snap, err := s.fset.Snapshot()
if err != nil {
return svcutil.AsFatalErr(err, svcutil.ExitError)
}
defer snap.Release()
previousWasDelete := false
snap.WithHaveSequence(s.prevSequence+1, func(fi protocol.FileIntf) bool {
@@ -175,18 +177,7 @@ func (s *indexSender) sendIndexTo(ctx context.Context) error {
return true
}
// Mark the file as invalid if any of the local bad stuff flags are set.
f.RawInvalid = f.IsInvalid()
// If the file is marked LocalReceive (i.e., changed locally on a
// receive only folder) we do not want it to ever become the
// globally best version, invalid or not.
if f.IsReceiveOnlyChanged() {
f.Version = protocol.Vector{}
}
// never sent externally
f.LocalFlags = 0
f.VersionHash = nil
f = prepareFileInfoForIndex(f)
previousWasDelete = f.IsDeleted()
@@ -208,6 +199,21 @@ func (s *indexSender) sendIndexTo(ctx context.Context) error {
return err
}
func prepareFileInfoForIndex(f protocol.FileInfo) protocol.FileInfo {
// Mark the file as invalid if any of the local bad stuff flags are set.
f.RawInvalid = f.IsInvalid()
// If the file is marked LocalReceive (i.e., changed locally on a
// receive only folder) we do not want it to ever become the
// globally best version, invalid or not.
if f.IsReceiveOnlyChanged() {
f.Version = protocol.Vector{}
}
// never sent externally
f.LocalFlags = 0
f.VersionHash = nil
return f
}
func (s *indexSender) String() string {
return fmt.Sprintf("indexSender@%p for %s to %s at %s", s, s.folder, s.conn.ID(), s.conn)
}
@@ -309,9 +315,7 @@ func (r *indexSenderRegistry) addLocked(folder config.FolderConfiguration, fset
r.sup.RemoveAndWait(is.token, 0)
delete(r.indexSenders, folder.ID)
}
if _, ok := r.startInfos[folder.ID]; ok {
delete(r.startInfos, folder.ID)
}
delete(r.startInfos, folder.ID)
is := &indexSender{
conn: r.conn,
+62 -42
View File
@@ -22,7 +22,7 @@ type Model struct {
arg1 protocol.Connection
arg2 protocol.Hello
}
AvailabilityStub func(string, protocol.FileInfo, protocol.BlockInfo) []model.Availability
AvailabilityStub func(string, protocol.FileInfo, protocol.BlockInfo) ([]model.Availability, error)
availabilityMutex sync.RWMutex
availabilityArgsForCall []struct {
arg1 string
@@ -31,9 +31,11 @@ type Model struct {
}
availabilityReturns struct {
result1 []model.Availability
result2 error
}
availabilityReturnsOnCall map[int]struct {
result1 []model.Availability
result2 error
}
BringToFrontStub func(string, string)
bringToFrontMutex sync.RWMutex
@@ -41,10 +43,10 @@ type Model struct {
arg1 string
arg2 string
}
ClosedStub func(protocol.Connection, error)
ClosedStub func(protocol.DeviceID, error)
closedMutex sync.RWMutex
closedArgsForCall []struct {
arg1 protocol.Connection
arg1 protocol.DeviceID
arg2 error
}
ClusterConfigStub func(protocol.DeviceID, protocol.ClusterConfig) error
@@ -59,7 +61,7 @@ type Model struct {
clusterConfigReturnsOnCall map[int]struct {
result1 error
}
CompletionStub func(protocol.DeviceID, string) model.FolderCompletion
CompletionStub func(protocol.DeviceID, string) (model.FolderCompletion, error)
completionMutex sync.RWMutex
completionArgsForCall []struct {
arg1 protocol.DeviceID
@@ -67,9 +69,11 @@ type Model struct {
}
completionReturns struct {
result1 model.FolderCompletion
result2 error
}
completionReturnsOnCall map[int]struct {
result1 model.FolderCompletion
result2 error
}
ConnectionStub func(protocol.DeviceID) (protocol.Connection, bool)
connectionMutex sync.RWMutex
@@ -94,7 +98,7 @@ type Model struct {
connectionStatsReturnsOnCall map[int]struct {
result1 map[string]interface{}
}
CurrentFolderFileStub func(string, string) (protocol.FileInfo, bool)
CurrentFolderFileStub func(string, string) (protocol.FileInfo, bool, error)
currentFolderFileMutex sync.RWMutex
currentFolderFileArgsForCall []struct {
arg1 string
@@ -103,12 +107,14 @@ type Model struct {
currentFolderFileReturns struct {
result1 protocol.FileInfo
result2 bool
result3 error
}
currentFolderFileReturnsOnCall map[int]struct {
result1 protocol.FileInfo
result2 bool
result3 error
}
CurrentGlobalFileStub func(string, string) (protocol.FileInfo, bool)
CurrentGlobalFileStub func(string, string) (protocol.FileInfo, bool, error)
currentGlobalFileMutex sync.RWMutex
currentGlobalFileArgsForCall []struct {
arg1 string
@@ -117,10 +123,12 @@ type Model struct {
currentGlobalFileReturns struct {
result1 protocol.FileInfo
result2 bool
result3 error
}
currentGlobalFileReturnsOnCall map[int]struct {
result1 protocol.FileInfo
result2 bool
result3 error
}
CurrentIgnoresStub func(string) ([]string, []string, error)
currentIgnoresMutex sync.RWMutex
@@ -577,7 +585,7 @@ func (fake *Model) AddConnectionArgsForCall(i int) (protocol.Connection, protoco
return argsForCall.arg1, argsForCall.arg2
}
func (fake *Model) Availability(arg1 string, arg2 protocol.FileInfo, arg3 protocol.BlockInfo) []model.Availability {
func (fake *Model) Availability(arg1 string, arg2 protocol.FileInfo, arg3 protocol.BlockInfo) ([]model.Availability, error) {
fake.availabilityMutex.Lock()
ret, specificReturn := fake.availabilityReturnsOnCall[len(fake.availabilityArgsForCall)]
fake.availabilityArgsForCall = append(fake.availabilityArgsForCall, struct {
@@ -593,9 +601,9 @@ func (fake *Model) Availability(arg1 string, arg2 protocol.FileInfo, arg3 protoc
return stub(arg1, arg2, arg3)
}
if specificReturn {
return ret.result1
return ret.result1, ret.result2
}
return fakeReturns.result1
return fakeReturns.result1, fakeReturns.result2
}
func (fake *Model) AvailabilityCallCount() int {
@@ -604,7 +612,7 @@ func (fake *Model) AvailabilityCallCount() int {
return len(fake.availabilityArgsForCall)
}
func (fake *Model) AvailabilityCalls(stub func(string, protocol.FileInfo, protocol.BlockInfo) []model.Availability) {
func (fake *Model) AvailabilityCalls(stub func(string, protocol.FileInfo, protocol.BlockInfo) ([]model.Availability, error)) {
fake.availabilityMutex.Lock()
defer fake.availabilityMutex.Unlock()
fake.AvailabilityStub = stub
@@ -617,27 +625,30 @@ func (fake *Model) AvailabilityArgsForCall(i int) (string, protocol.FileInfo, pr
return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3
}
func (fake *Model) AvailabilityReturns(result1 []model.Availability) {
func (fake *Model) AvailabilityReturns(result1 []model.Availability, result2 error) {
fake.availabilityMutex.Lock()
defer fake.availabilityMutex.Unlock()
fake.AvailabilityStub = nil
fake.availabilityReturns = struct {
result1 []model.Availability
}{result1}
result2 error
}{result1, result2}
}
func (fake *Model) AvailabilityReturnsOnCall(i int, result1 []model.Availability) {
func (fake *Model) AvailabilityReturnsOnCall(i int, result1 []model.Availability, result2 error) {
fake.availabilityMutex.Lock()
defer fake.availabilityMutex.Unlock()
fake.AvailabilityStub = nil
if fake.availabilityReturnsOnCall == nil {
fake.availabilityReturnsOnCall = make(map[int]struct {
result1 []model.Availability
result2 error
})
}
fake.availabilityReturnsOnCall[i] = struct {
result1 []model.Availability
}{result1}
result2 error
}{result1, result2}
}
func (fake *Model) BringToFront(arg1 string, arg2 string) {
@@ -673,10 +684,10 @@ func (fake *Model) BringToFrontArgsForCall(i int) (string, string) {
return argsForCall.arg1, argsForCall.arg2
}
func (fake *Model) Closed(arg1 protocol.Connection, arg2 error) {
func (fake *Model) Closed(arg1 protocol.DeviceID, arg2 error) {
fake.closedMutex.Lock()
fake.closedArgsForCall = append(fake.closedArgsForCall, struct {
arg1 protocol.Connection
arg1 protocol.DeviceID
arg2 error
}{arg1, arg2})
stub := fake.ClosedStub
@@ -693,13 +704,13 @@ func (fake *Model) ClosedCallCount() int {
return len(fake.closedArgsForCall)
}
func (fake *Model) ClosedCalls(stub func(protocol.Connection, error)) {
func (fake *Model) ClosedCalls(stub func(protocol.DeviceID, error)) {
fake.closedMutex.Lock()
defer fake.closedMutex.Unlock()
fake.ClosedStub = stub
}
func (fake *Model) ClosedArgsForCall(i int) (protocol.Connection, error) {
func (fake *Model) ClosedArgsForCall(i int) (protocol.DeviceID, error) {
fake.closedMutex.RLock()
defer fake.closedMutex.RUnlock()
argsForCall := fake.closedArgsForCall[i]
@@ -768,7 +779,7 @@ func (fake *Model) ClusterConfigReturnsOnCall(i int, result1 error) {
}{result1}
}
func (fake *Model) Completion(arg1 protocol.DeviceID, arg2 string) model.FolderCompletion {
func (fake *Model) Completion(arg1 protocol.DeviceID, arg2 string) (model.FolderCompletion, error) {
fake.completionMutex.Lock()
ret, specificReturn := fake.completionReturnsOnCall[len(fake.completionArgsForCall)]
fake.completionArgsForCall = append(fake.completionArgsForCall, struct {
@@ -783,9 +794,9 @@ func (fake *Model) Completion(arg1 protocol.DeviceID, arg2 string) model.FolderC
return stub(arg1, arg2)
}
if specificReturn {
return ret.result1
return ret.result1, ret.result2
}
return fakeReturns.result1
return fakeReturns.result1, fakeReturns.result2
}
func (fake *Model) CompletionCallCount() int {
@@ -794,7 +805,7 @@ func (fake *Model) CompletionCallCount() int {
return len(fake.completionArgsForCall)
}
func (fake *Model) CompletionCalls(stub func(protocol.DeviceID, string) model.FolderCompletion) {
func (fake *Model) CompletionCalls(stub func(protocol.DeviceID, string) (model.FolderCompletion, error)) {
fake.completionMutex.Lock()
defer fake.completionMutex.Unlock()
fake.CompletionStub = stub
@@ -807,27 +818,30 @@ func (fake *Model) CompletionArgsForCall(i int) (protocol.DeviceID, string) {
return argsForCall.arg1, argsForCall.arg2
}
func (fake *Model) CompletionReturns(result1 model.FolderCompletion) {
func (fake *Model) CompletionReturns(result1 model.FolderCompletion, result2 error) {
fake.completionMutex.Lock()
defer fake.completionMutex.Unlock()
fake.CompletionStub = nil
fake.completionReturns = struct {
result1 model.FolderCompletion
}{result1}
result2 error
}{result1, result2}
}
func (fake *Model) CompletionReturnsOnCall(i int, result1 model.FolderCompletion) {
func (fake *Model) CompletionReturnsOnCall(i int, result1 model.FolderCompletion, result2 error) {
fake.completionMutex.Lock()
defer fake.completionMutex.Unlock()
fake.CompletionStub = nil
if fake.completionReturnsOnCall == nil {
fake.completionReturnsOnCall = make(map[int]struct {
result1 model.FolderCompletion
result2 error
})
}
fake.completionReturnsOnCall[i] = struct {
result1 model.FolderCompletion
}{result1}
result2 error
}{result1, result2}
}
func (fake *Model) Connection(arg1 protocol.DeviceID) (protocol.Connection, bool) {
@@ -947,7 +961,7 @@ func (fake *Model) ConnectionStatsReturnsOnCall(i int, result1 map[string]interf
}{result1}
}
func (fake *Model) CurrentFolderFile(arg1 string, arg2 string) (protocol.FileInfo, bool) {
func (fake *Model) CurrentFolderFile(arg1 string, arg2 string) (protocol.FileInfo, bool, error) {
fake.currentFolderFileMutex.Lock()
ret, specificReturn := fake.currentFolderFileReturnsOnCall[len(fake.currentFolderFileArgsForCall)]
fake.currentFolderFileArgsForCall = append(fake.currentFolderFileArgsForCall, struct {
@@ -962,9 +976,9 @@ func (fake *Model) CurrentFolderFile(arg1 string, arg2 string) (protocol.FileInf
return stub(arg1, arg2)
}
if specificReturn {
return ret.result1, ret.result2
return ret.result1, ret.result2, ret.result3
}
return fakeReturns.result1, fakeReturns.result2
return fakeReturns.result1, fakeReturns.result2, fakeReturns.result3
}
func (fake *Model) CurrentFolderFileCallCount() int {
@@ -973,7 +987,7 @@ func (fake *Model) CurrentFolderFileCallCount() int {
return len(fake.currentFolderFileArgsForCall)
}
func (fake *Model) CurrentFolderFileCalls(stub func(string, string) (protocol.FileInfo, bool)) {
func (fake *Model) CurrentFolderFileCalls(stub func(string, string) (protocol.FileInfo, bool, error)) {
fake.currentFolderFileMutex.Lock()
defer fake.currentFolderFileMutex.Unlock()
fake.CurrentFolderFileStub = stub
@@ -986,17 +1000,18 @@ func (fake *Model) CurrentFolderFileArgsForCall(i int) (string, string) {
return argsForCall.arg1, argsForCall.arg2
}
func (fake *Model) CurrentFolderFileReturns(result1 protocol.FileInfo, result2 bool) {
func (fake *Model) CurrentFolderFileReturns(result1 protocol.FileInfo, result2 bool, result3 error) {
fake.currentFolderFileMutex.Lock()
defer fake.currentFolderFileMutex.Unlock()
fake.CurrentFolderFileStub = nil
fake.currentFolderFileReturns = struct {
result1 protocol.FileInfo
result2 bool
}{result1, result2}
result3 error
}{result1, result2, result3}
}
func (fake *Model) CurrentFolderFileReturnsOnCall(i int, result1 protocol.FileInfo, result2 bool) {
func (fake *Model) CurrentFolderFileReturnsOnCall(i int, result1 protocol.FileInfo, result2 bool, result3 error) {
fake.currentFolderFileMutex.Lock()
defer fake.currentFolderFileMutex.Unlock()
fake.CurrentFolderFileStub = nil
@@ -1004,15 +1019,17 @@ func (fake *Model) CurrentFolderFileReturnsOnCall(i int, result1 protocol.FileIn
fake.currentFolderFileReturnsOnCall = make(map[int]struct {
result1 protocol.FileInfo
result2 bool
result3 error
})
}
fake.currentFolderFileReturnsOnCall[i] = struct {
result1 protocol.FileInfo
result2 bool
}{result1, result2}
result3 error
}{result1, result2, result3}
}
func (fake *Model) CurrentGlobalFile(arg1 string, arg2 string) (protocol.FileInfo, bool) {
func (fake *Model) CurrentGlobalFile(arg1 string, arg2 string) (protocol.FileInfo, bool, error) {
fake.currentGlobalFileMutex.Lock()
ret, specificReturn := fake.currentGlobalFileReturnsOnCall[len(fake.currentGlobalFileArgsForCall)]
fake.currentGlobalFileArgsForCall = append(fake.currentGlobalFileArgsForCall, struct {
@@ -1027,9 +1044,9 @@ func (fake *Model) CurrentGlobalFile(arg1 string, arg2 string) (protocol.FileInf
return stub(arg1, arg2)
}
if specificReturn {
return ret.result1, ret.result2
return ret.result1, ret.result2, ret.result3
}
return fakeReturns.result1, fakeReturns.result2
return fakeReturns.result1, fakeReturns.result2, fakeReturns.result3
}
func (fake *Model) CurrentGlobalFileCallCount() int {
@@ -1038,7 +1055,7 @@ func (fake *Model) CurrentGlobalFileCallCount() int {
return len(fake.currentGlobalFileArgsForCall)
}
func (fake *Model) CurrentGlobalFileCalls(stub func(string, string) (protocol.FileInfo, bool)) {
func (fake *Model) CurrentGlobalFileCalls(stub func(string, string) (protocol.FileInfo, bool, error)) {
fake.currentGlobalFileMutex.Lock()
defer fake.currentGlobalFileMutex.Unlock()
fake.CurrentGlobalFileStub = stub
@@ -1051,17 +1068,18 @@ func (fake *Model) CurrentGlobalFileArgsForCall(i int) (string, string) {
return argsForCall.arg1, argsForCall.arg2
}
func (fake *Model) CurrentGlobalFileReturns(result1 protocol.FileInfo, result2 bool) {
func (fake *Model) CurrentGlobalFileReturns(result1 protocol.FileInfo, result2 bool, result3 error) {
fake.currentGlobalFileMutex.Lock()
defer fake.currentGlobalFileMutex.Unlock()
fake.CurrentGlobalFileStub = nil
fake.currentGlobalFileReturns = struct {
result1 protocol.FileInfo
result2 bool
}{result1, result2}
result3 error
}{result1, result2, result3}
}
func (fake *Model) CurrentGlobalFileReturnsOnCall(i int, result1 protocol.FileInfo, result2 bool) {
func (fake *Model) CurrentGlobalFileReturnsOnCall(i int, result1 protocol.FileInfo, result2 bool, result3 error) {
fake.currentGlobalFileMutex.Lock()
defer fake.currentGlobalFileMutex.Unlock()
fake.CurrentGlobalFileStub = nil
@@ -1069,12 +1087,14 @@ func (fake *Model) CurrentGlobalFileReturnsOnCall(i int, result1 protocol.FileIn
fake.currentGlobalFileReturnsOnCall = make(map[int]struct {
result1 protocol.FileInfo
result2 bool
result3 error
})
}
fake.currentGlobalFileReturnsOnCall[i] = struct {
result1 protocol.FileInfo
result2 bool
}{result1, result2}
result3 error
}{result1, result2, result3}
}
func (fake *Model) CurrentIgnores(arg1 string) ([]string, []string, error) {
+142 -85
View File
@@ -98,11 +98,11 @@ type Model interface {
LocalChangedFolderFiles(folder string, page, perpage int) ([]db.FileInfoTruncated, error)
FolderProgressBytesCompleted(folder string) int64
CurrentFolderFile(folder string, file string) (protocol.FileInfo, bool)
CurrentGlobalFile(folder string, file string) (protocol.FileInfo, bool)
Availability(folder string, file protocol.FileInfo, block protocol.BlockInfo) []Availability
CurrentFolderFile(folder string, file string) (protocol.FileInfo, bool, error)
CurrentGlobalFile(folder string, file string) (protocol.FileInfo, bool, error)
Availability(folder string, file protocol.FileInfo, block protocol.BlockInfo) ([]Availability, error)
Completion(device protocol.DeviceID, folder string) FolderCompletion
Completion(device protocol.DeviceID, folder string) (FolderCompletion, error)
ConnectionStats() map[string]interface{}
DeviceStatistics() (map[protocol.DeviceID]stats.DeviceStatistics, error)
FolderStatistics() (map[string]stats.FolderStatistics, error)
@@ -179,12 +179,11 @@ var (
errDeviceIgnored = errors.New("device is ignored")
errDeviceRemoved = errors.New("device has been removed")
ErrFolderPaused = errors.New("folder is paused")
errFolderNotRunning = errors.New("folder is not running")
errFolderMissing = errors.New("no such folder")
ErrFolderNotRunning = errors.New("folder is not running")
ErrFolderMissing = errors.New("no such folder")
errNetworkNotAllowed = errors.New("network not allowed")
errNoVersioner = errors.New("folder has no versioner")
// errors about why a connection is closed
errIgnoredFolderRemoved = errors.New("folder no longer ignored")
errReplacingConnection = errors.New("replacing connection")
errStopped = errors.New("Syncthing is being stopped")
errEncryptionInvConfigLocal = errors.New("can't encrypt data for a device when the folder type is receiveEncrypted")
@@ -193,10 +192,12 @@ var (
errEncryptionNotEncryptedRemote = errors.New("folder is configured to be encrypted but not announced thus")
errEncryptionNotEncryptedUntrusted = errors.New("device is untrusted, but configured to receive not encrypted data")
errEncryptionPassword = errors.New("different encryption passwords used")
errEncryptionReceivedToken = errors.New("resetting connection to send info on new encrypted folder (new cluster config)")
errEncryptionNeedToken = errors.New("require password token for receive-encrypted token")
errMissingRemoteInClusterConfig = errors.New("remote device missing in cluster config")
errMissingLocalInClusterConfig = errors.New("local device missing in cluster config")
errConnLimitReached = errors.New("connection limit reached")
// messages for failure reports
failureUnexpectedGenerateCCError = "unexpected error occurred in generateClusterConfig"
)
// NewModel creates and starts a new model. The model starts in read-only mode,
@@ -294,7 +295,7 @@ func (m *model) initFolders(cfg config.Configuration) error {
ignoredDevices := observedDeviceSet(m.cfg.IgnoredDevices())
m.cleanPending(cfg.DeviceMap(), cfg.FolderMap(), ignoredDevices, nil)
m.resendClusterConfig(clusterConfigDevices.AsSlice())
m.sendClusterConfig(clusterConfigDevices.AsSlice())
return nil
}
@@ -331,8 +332,10 @@ func (m *model) StartDeadlockDetector(timeout time.Duration) {
// Need to hold lock on m.fmut when calling this.
func (m *model) addAndStartFolderLocked(cfg config.FolderConfiguration, fset *db.FileSet, cacheIgnoredFiles bool) {
ignores := ignore.New(cfg.Filesystem(), ignore.WithCache(cacheIgnoredFiles))
if err := ignores.Load(".stignore"); err != nil && !fs.IsNotExist(err) {
l.Warnln("Loading ignores:", err)
if cfg.Type != config.FolderTypeReceiveEncrypted {
if err := ignores.Load(".stignore"); err != nil && !fs.IsNotExist(err) {
l.Warnln("Loading ignores:", err)
}
}
m.addAndStartFolderLockedWithIgnores(cfg, fset, ignores)
@@ -691,7 +694,7 @@ func (m *model) UsageReportingStats(report *contract.Report, version int, previe
if strings.Contains(line, "**") {
report.IgnoreStats.DoubleStars++
// Remove not to trip up star checks.
line = strings.Replace(line, "**", "", -1)
line = strings.ReplaceAll(line, "**", "")
}
if strings.Contains(line, "*") {
@@ -769,7 +772,7 @@ func (m *model) ConnectionStats() map[string]interface{} {
in, out := protocol.TotalInOut()
res["total"] = ConnectionInfo{
Statistics: protocol.Statistics{
At: time.Now(),
At: time.Now().Truncate(time.Second),
InBytesTotal: in,
OutBytesTotal: out,
},
@@ -875,7 +878,7 @@ func (comp FolderCompletion) Map() map[string]interface{} {
// (including the local device) or explicitly protocol.LocalDeviceID. An
// empty folder string means the aggregate of all folders shared with the
// given device.
func (m *model) Completion(device protocol.DeviceID, folder string) FolderCompletion {
func (m *model) Completion(device protocol.DeviceID, folder string) (FolderCompletion, error) {
// The user specifically asked for our own device ID. Internally that is
// known as protocol.LocalDeviceID so translate.
if device == m.id {
@@ -891,21 +894,29 @@ func (m *model) Completion(device protocol.DeviceID, folder string) FolderComple
var comp FolderCompletion
for _, fcfg := range m.cfg.FolderList() {
if device == protocol.LocalDeviceID || fcfg.SharedWith(device) {
comp.add(m.folderCompletion(device, fcfg.ID))
folderComp, err := m.folderCompletion(device, fcfg.ID)
if err != nil {
return FolderCompletion{}, err
}
comp.add(folderComp)
}
}
return comp
return comp, nil
}
func (m *model) folderCompletion(device protocol.DeviceID, folder string) FolderCompletion {
func (m *model) folderCompletion(device protocol.DeviceID, folder string) (FolderCompletion, error) {
m.fmut.RLock()
rf, ok := m.folderFiles[folder]
err := m.checkFolderRunningLocked(folder)
rf := m.folderFiles[folder]
m.fmut.RUnlock()
if !ok {
return FolderCompletion{} // Folder doesn't exist, so we hardly have any of it
if err != nil {
return FolderCompletion{}, err
}
snap := rf.Snapshot()
snap, err := rf.Snapshot()
if err != nil {
return FolderCompletion{}, err
}
defer snap.Release()
m.pmut.RLock()
@@ -922,7 +933,7 @@ func (m *model) folderCompletion(device protocol.DeviceID, folder string) Folder
comp := newFolderCompletion(snap.GlobalSize(), need, snap.Sequence(device))
l.Debugf("%v Completion(%s, %q): %v", m, device, folder, comp.Map())
return comp
return comp, nil
}
// DBSnapshot returns a snapshot of the database content relevant to the given folder.
@@ -934,7 +945,7 @@ func (m *model) DBSnapshot(folder string) (*db.Snapshot, error) {
if err != nil {
return nil, err
}
return rf.Snapshot(), nil
return rf.Snapshot()
}
func (m *model) FolderProgressBytesCompleted(folder string) int64 {
@@ -951,10 +962,13 @@ func (m *model) NeedFolderFiles(folder string, page, perpage int) ([]db.FileInfo
m.fmut.RUnlock()
if !rfOk {
return nil, nil, nil, errFolderMissing
return nil, nil, nil, ErrFolderMissing
}
snap := rf.Snapshot()
snap, err := rf.Snapshot()
if err != nil {
return nil, nil, nil, err
}
defer snap.Release()
var progress, queued, rest []db.FileInfoTruncated
var seen map[string]struct{}
@@ -1018,10 +1032,13 @@ func (m *model) RemoteNeedFolderFiles(folder string, device protocol.DeviceID, p
m.fmut.RUnlock()
if !ok {
return nil, errFolderMissing
return nil, ErrFolderMissing
}
snap := rf.Snapshot()
snap, err := rf.Snapshot()
if err != nil {
return nil, err
}
defer snap.Release()
files := make([]db.FileInfoTruncated, 0, perpage)
@@ -1043,10 +1060,13 @@ func (m *model) LocalChangedFolderFiles(folder string, page, perpage int) ([]db.
m.fmut.RUnlock()
if !ok {
return nil, errFolderMissing
return nil, ErrFolderMissing
}
snap := rf.Snapshot()
snap, err := rf.Snapshot()
if err != nil {
return nil, err
}
defer snap.Release()
if snap.ReceiveOnlyChangedSize().TotalItems() == 0 {
@@ -1120,7 +1140,7 @@ func (m *model) handleIndex(deviceID protocol.DeviceID, folder string, fs []prot
if cfg, ok := m.cfg.Folder(folder); !ok || !cfg.SharedWith(deviceID) {
l.Infof("%v for unexpected folder ID %q sent from device %q; ensure that the folder exists and that this device is selected under \"Share With\" in the folder configuration.", op, folder, deviceID)
return errors.Wrap(errFolderMissing, folder)
return errors.Wrap(ErrFolderMissing, folder)
} else if cfg.Paused {
l.Debugf("%v for paused folder (ID %q) sent from device %q.", op, folder, deviceID)
return errors.Wrap(ErrFolderPaused, folder)
@@ -1133,7 +1153,7 @@ func (m *model) handleIndex(deviceID protocol.DeviceID, folder string, fs []prot
if !existing {
l.Infof("%v for nonexistent folder %q", op, folder)
return errors.Wrap(errFolderMissing, folder)
return errors.Wrap(ErrFolderMissing, folder)
}
if running {
@@ -1347,12 +1367,12 @@ func (m *model) ccHandleFolders(folders []protocol.Folder, deviceCfg config.Devi
}
m.folderEncryptionFailures[folder.ID][deviceID] = err
msg := fmt.Sprintf("Failure checking encryption consistency with device %v for folder %v: %v", deviceID, cfg.Description(), err)
if sameError || err == errEncryptionReceivedToken {
if sameError {
l.Debugln(msg)
} else {
l.Warnln(msg)
}
m.evLogger.Log(events.Failure, err.Error())
return tempIndexFolders, paused, err
}
if devErrs, ok := m.folderEncryptionFailures[folder.ID]; ok {
@@ -1492,7 +1512,7 @@ func (m *model) ccCheckEncryption(fcfg config.FolderConfiguration, folderDevice
m.fmut.Unlock()
// We can only announce ourselfs once we have the token,
// thus we need to resend CCs now that we have it.
m.resendClusterConfig(fcfg.DeviceIDs())
m.sendClusterConfig(fcfg.DeviceIDs())
return nil
}
}
@@ -1502,7 +1522,7 @@ func (m *model) ccCheckEncryption(fcfg config.FolderConfiguration, folderDevice
return nil
}
func (m *model) resendClusterConfig(ids []protocol.DeviceID) {
func (m *model) sendClusterConfig(ids []protocol.DeviceID) {
if len(ids) == 0 {
return
}
@@ -1516,7 +1536,16 @@ func (m *model) resendClusterConfig(ids []protocol.DeviceID) {
m.pmut.RUnlock()
// Generating cluster-configs acquires fmut -> must happen outside of pmut.
for _, conn := range ccConns {
cm := m.generateClusterConfig(conn.ID())
cm, passwords, err := m.generateClusterConfig(conn.ID())
if err != nil {
if err != errEncryptionNeedToken {
m.evLogger.Log(events.Failure, failureUnexpectedGenerateCCError)
continue
}
go conn.Close(err)
continue
}
conn.SetFolderPasswords(passwords)
go conn.ClusterConfig(cm)
}
}
@@ -1682,15 +1711,6 @@ func (m *model) handleAutoAccepts(deviceID protocol.DeviceID, folder protocol.Fo
}
}
func (m *model) newFolderConfiguration(id, label string, fsType fs.FilesystemType, path string) config.FolderConfiguration {
fcfg := m.cfg.DefaultFolder()
fcfg.ID = id
fcfg.Label = label
fcfg.FilesystemType = fsType
fcfg.Path = path
return fcfg
}
func (m *model) introduceDevice(device protocol.Device, introducerCfg config.DeviceConfiguration) config.DeviceConfiguration {
addresses := []string{"dynamic"}
for _, addr := range device.Addresses {
@@ -1719,9 +1739,7 @@ func (m *model) introduceDevice(device protocol.Device, introducerCfg config.Dev
}
// Closed is called when a connection has been closed
func (m *model) Closed(conn protocol.Connection, err error) {
device := conn.ID()
func (m *model) Closed(device protocol.DeviceID, err error) {
m.pmut.Lock()
conn, ok := m.conn[device]
if !ok {
@@ -1930,7 +1948,11 @@ func newLimitedRequestResponse(size int, limiters ...*byteSemaphore) *requestRes
}
func (m *model) recheckFile(deviceID protocol.DeviceID, folder, name string, offset int64, hash []byte, weakHash uint32) {
cf, ok := m.CurrentFolderFile(folder, name)
cf, ok, err := m.CurrentFolderFile(folder, name)
if err != nil {
l.Debugf("%v recheckFile: %s: %q / %q: current file error: %v", m, deviceID, folder, name, err)
return
}
if !ok {
l.Debugf("%v recheckFile: %s: %q / %q: no current file", m, deviceID, folder, name)
return
@@ -1976,28 +1998,36 @@ func (m *model) recheckFile(deviceID protocol.DeviceID, folder, name string, off
l.Debugf("%v recheckFile: %s: %q / %q", m, deviceID, folder, name)
}
func (m *model) CurrentFolderFile(folder string, file string) (protocol.FileInfo, bool) {
func (m *model) CurrentFolderFile(folder string, file string) (protocol.FileInfo, bool, error) {
m.fmut.RLock()
fs, ok := m.folderFiles[folder]
m.fmut.RUnlock()
if !ok {
return protocol.FileInfo{}, false
return protocol.FileInfo{}, false, ErrFolderMissing
}
snap := fs.Snapshot()
defer snap.Release()
return snap.Get(protocol.LocalDeviceID, file)
snap, err := fs.Snapshot()
if err != nil {
return protocol.FileInfo{}, false, err
}
f, ok := snap.Get(protocol.LocalDeviceID, file)
snap.Release()
return f, ok, nil
}
func (m *model) CurrentGlobalFile(folder string, file string) (protocol.FileInfo, bool) {
func (m *model) CurrentGlobalFile(folder string, file string) (protocol.FileInfo, bool, error) {
m.fmut.RLock()
fs, ok := m.folderFiles[folder]
m.fmut.RUnlock()
if !ok {
return protocol.FileInfo{}, false
return protocol.FileInfo{}, false, ErrFolderMissing
}
snap := fs.Snapshot()
defer snap.Release()
return snap.GetGlobal(file)
snap, err := fs.Snapshot()
if err != nil {
return protocol.FileInfo{}, false, err
}
f, ok := snap.GetGlobal(file)
snap.Release()
return f, ok, nil
}
// Connection returns the current connection for device, and a boolean whether a connection was found.
@@ -2226,7 +2256,13 @@ func (m *model) AddConnection(conn protocol.Connection, hello protocol.Hello) {
m.pmut.Unlock()
// Acquires fmut, so has to be done outside of pmut.
cm := m.generateClusterConfig(deviceID)
cm, passwords, err := m.generateClusterConfig(deviceID)
// We ignore errEncryptionNeedToken on a new connection, as the missing
// token should be delivered in the cluster-config about to be received.
if err != nil && err != errEncryptionNeedToken {
m.evLogger.Log(events.Failure, failureUnexpectedGenerateCCError)
}
conn.SetFolderPasswords(passwords)
conn.ClusterConfig(cm)
if (device.Name == "" || m.cfg.Options().OverwriteRemoteDevNames) && hello.DeviceName != "" {
@@ -2386,15 +2422,17 @@ func (m *model) numHashers(folder string) int {
return 1
}
// generateClusterConfig returns a ClusterConfigMessage that is correct for
// the given peer device
func (m *model) generateClusterConfig(device protocol.DeviceID) protocol.ClusterConfig {
// generateClusterConfig returns a ClusterConfigMessage that is correct and the
// set of folder passwords for the given peer device
func (m *model) generateClusterConfig(device protocol.DeviceID) (protocol.ClusterConfig, map[string]string, error) {
var message protocol.ClusterConfig
m.fmut.RLock()
defer m.fmut.RUnlock()
for _, folderCfg := range m.cfg.FolderList() {
folders := m.cfg.FolderList()
passwords := make(map[string]string, len(folders))
for _, folderCfg := range folders {
if !folderCfg.SharedWith(device) {
continue
}
@@ -2403,10 +2441,10 @@ func (m *model) generateClusterConfig(device protocol.DeviceID) protocol.Cluster
var hasEncryptionToken bool
if folderCfg.Type == config.FolderTypeReceiveEncrypted {
if encryptionToken, hasEncryptionToken = m.folderEncryptionPasswordTokens[folderCfg.ID]; !hasEncryptionToken {
// We haven't gotten a token for us yet and without
// one the other side can't validate us - pretend
// we don't have the folder yet.
continue
// We haven't gotten a token yet and without one the other side
// can't validate us - reset the connection to trigger a new
// cluster-config and get the token.
return message, nil, errEncryptionNeedToken
}
}
@@ -2427,8 +2465,8 @@ func (m *model) generateClusterConfig(device protocol.DeviceID) protocol.Cluster
// another cluster config once the folder is started.
protocolFolder.Paused = folderCfg.Paused || fs == nil
for _, device := range folderCfg.Devices {
deviceCfg, _ := m.cfg.Device(device.DeviceID)
for _, folderDevice := range folderCfg.Devices {
deviceCfg, _ := m.cfg.Device(folderDevice.DeviceID)
protocolDevice := protocol.Device{
ID: deviceCfg.DeviceID,
@@ -2441,8 +2479,11 @@ func (m *model) generateClusterConfig(device protocol.DeviceID) protocol.Cluster
if deviceCfg.DeviceID == m.id && hasEncryptionToken {
protocolDevice.EncryptionPasswordToken = encryptionToken
} else if device.EncryptionPassword != "" {
protocolDevice.EncryptionPasswordToken = protocol.PasswordToken(folderCfg.ID, device.EncryptionPassword)
} else if folderDevice.EncryptionPassword != "" {
protocolDevice.EncryptionPasswordToken = protocol.PasswordToken(folderCfg.ID, folderDevice.EncryptionPassword)
if folderDevice.DeviceID == device {
passwords[folderCfg.ID] = folderDevice.EncryptionPassword
}
}
if fs != nil {
@@ -2461,7 +2502,7 @@ func (m *model) generateClusterConfig(device protocol.DeviceID) protocol.Cluster
message.Folders = append(message.Folders, protocolFolder)
}
return message
return message, passwords, nil
}
func (m *model) State(folder string) (string, time.Time, error) {
@@ -2552,7 +2593,7 @@ func (m *model) GlobalDirectoryTree(folder, prefix string, levels int, dirsOnly
files, ok := m.folderFiles[folder]
m.fmut.RUnlock()
if !ok {
return nil, errFolderMissing
return nil, ErrFolderMissing
}
root := &TreeEntry{
@@ -2565,9 +2606,11 @@ func (m *model) GlobalDirectoryTree(folder, prefix string, levels int, dirsOnly
prefix = prefix + sep
}
snap := files.Snapshot()
snap, err := files.Snapshot()
if err != nil {
return nil, err
}
defer snap.Release()
var err error
snap.WithPrefixedGlobalTruncated(prefix, func(fi protocol.FileIntf) bool {
f := fi.(db.FileInfoTruncated)
@@ -2661,7 +2704,7 @@ func (m *model) RestoreFolderVersions(folder string, versions map[string]time.Ti
return restoreErrors, nil
}
func (m *model) Availability(folder string, file protocol.FileInfo, block protocol.BlockInfo) []Availability {
func (m *model) Availability(folder string, file protocol.FileInfo, block protocol.BlockInfo) ([]Availability, error) {
// The slightly unusual locking sequence here is because we need to hold
// pmut for the duration (as the value returned from foldersFiles can
// get heavily modified on Close()), but also must acquire fmut before
@@ -2675,17 +2718,31 @@ func (m *model) Availability(folder string, file protocol.FileInfo, block protoc
m.fmut.RUnlock()
if !ok {
return nil
return nil, ErrFolderMissing
}
var availabilities []Availability
snap := fs.Snapshot()
snap, err := fs.Snapshot()
if err != nil {
return nil, err
}
defer snap.Release()
return m.availabilityInSnapshotPRlocked(cfg, snap, file, block), nil
}
func (m *model) availabilityInSnapshot(cfg config.FolderConfiguration, snap *db.Snapshot, file protocol.FileInfo, block protocol.BlockInfo) []Availability {
m.pmut.RLock()
defer m.pmut.RUnlock()
return m.availabilityInSnapshotPRlocked(cfg, snap, file, block)
}
func (m *model) availabilityInSnapshotPRlocked(cfg config.FolderConfiguration, snap *db.Snapshot, file protocol.FileInfo, block protocol.BlockInfo) []Availability {
var availabilities []Availability
for _, device := range snap.Availability(file.Name) {
if _, ok := m.remotePausedFolders[device]; !ok {
continue
}
if _, ok := m.remotePausedFolders[device][folder]; ok {
if _, ok := m.remotePausedFolders[device][cfg.ID]; ok {
continue
}
_, ok := m.conn[device]
@@ -2695,7 +2752,7 @@ func (m *model) Availability(folder string, file protocol.FileInfo, block protoc
}
for _, device := range cfg.Devices {
if m.deviceDownloads[device.DeviceID].Has(folder, file.Name, file.Version, int(block.Offset/int64(file.BlockSize()))) {
if m.deviceDownloads[device.DeviceID].Has(cfg.ID, file.Name, file.Version, int(block.Offset/int64(file.BlockSize()))) {
availabilities = append(availabilities, Availability{ID: device.DeviceID, FromTemporary: true})
}
}
@@ -2854,7 +2911,7 @@ func (m *model) CommitConfiguration(from, to config.Configuration) bool {
}
m.pmut.RUnlock()
// Generating cluster-configs acquires fmut -> must happen outside of pmut.
m.resendClusterConfig(clusterConfigDevices.AsSlice())
m.sendClusterConfig(clusterConfigDevices.AsSlice())
ignoredDevices := observedDeviceSet(to.IgnoredDevices)
m.cleanPending(toDevices, toFolders, ignoredDevices, removedFolders)
@@ -2960,12 +3017,12 @@ func (m *model) checkFolderRunningLocked(folder string) error {
}
if cfg, ok := m.cfg.Folder(folder); !ok {
return errFolderMissing
return ErrFolderMissing
} else if cfg.Paused {
return ErrFolderPaused
}
return errFolderNotRunning
return ErrFolderNotRunning
}
// PendingDevices lists unknown devices that tried to connect.
+46 -40
View File
@@ -341,7 +341,7 @@ func TestDeviceRename(t *testing.T) {
t.Errorf("Device already has a name")
}
m.Closed(conn, protocol.ErrTimeout)
m.Closed(conn.ID(), protocol.ErrTimeout)
hello.DeviceName = "tester"
m.AddConnection(conn, hello)
@@ -349,7 +349,7 @@ func TestDeviceRename(t *testing.T) {
t.Errorf("Device did not get a name")
}
m.Closed(conn, protocol.ErrTimeout)
m.Closed(conn.ID(), protocol.ErrTimeout)
hello.DeviceName = "tester2"
m.AddConnection(conn, hello)
@@ -367,7 +367,7 @@ func TestDeviceRename(t *testing.T) {
t.Errorf("Device name not saved in config")
}
m.Closed(conn, protocol.ErrTimeout)
m.Closed(conn.ID(), protocol.ErrTimeout)
waiter, err := cfg.Modify(func(cfg *config.Configuration) {
cfg.Options.OverwriteRemoteDevNames = true
@@ -428,7 +428,8 @@ func TestClusterConfig(t *testing.T) {
m.ServeBackground()
defer cleanupModel(m)
cm := m.generateClusterConfig(device2)
cm, _, err := m.generateClusterConfig(device2)
must(t, err)
if l := len(cm.Folders); l != 2 {
t.Fatalf("Incorrect number of folders %d != 2", l)
@@ -853,7 +854,8 @@ func TestIssue4897(t *testing.T) {
defer cleanupModel(m)
cancel()
cm := m.generateClusterConfig(device1)
cm, _, err := m.generateClusterConfig(device1)
must(t, err)
if l := len(cm.Folders); l != 1 {
t.Errorf("Cluster config contains %v folders, expected 1", l)
}
@@ -873,7 +875,7 @@ func TestIssue5063(t *testing.T) {
for _, c := range m.conn {
conn := c.(*fakeConnection)
conn.CloseCalls(func(_ error) {})
defer m.Closed(c, errStopped) // to unblock deferred m.Stop()
defer m.Closed(c.ID(), errStopped) // to unblock deferred m.Stop()
}
m.pmut.Unlock()
@@ -2300,7 +2302,7 @@ func TestIssue3496(t *testing.T) {
fs := m.folderFiles["default"]
m.fmut.RUnlock()
var localFiles []protocol.FileInfo
snap := fs.Snapshot()
snap := fsetSnapshot(t, fs)
snap.WithHave(protocol.LocalDeviceID, func(i protocol.FileIntf) bool {
localFiles = append(localFiles, i.(protocol.FileInfo))
return true
@@ -2329,7 +2331,7 @@ func TestIssue3496(t *testing.T) {
// Check that the completion percentage for us makes sense
comp := m.Completion(protocol.LocalDeviceID, "default")
comp := m.testCompletion(protocol.LocalDeviceID, "default")
if comp.NeedBytes > comp.GlobalBytes {
t.Errorf("Need more bytes than exist, not possible: %d > %d", comp.NeedBytes, comp.GlobalBytes)
}
@@ -2393,7 +2395,7 @@ func TestNoRequestsFromPausedDevices(t *testing.T) {
files.Update(device1, []protocol.FileInfo{file})
files.Update(device2, []protocol.FileInfo{file})
avail := m.Availability("default", file, file.Blocks[0])
avail := m.testAvailability("default", file, file.Blocks[0])
if len(avail) != 0 {
t.Errorf("should not be available, no connections")
}
@@ -2403,7 +2405,7 @@ func TestNoRequestsFromPausedDevices(t *testing.T) {
// !!! This is not what I'd expect to happen, as we don't even know if the peer has the original index !!!
avail = m.Availability("default", file, file.Blocks[0])
avail = m.testAvailability("default", file, file.Blocks[0])
if len(avail) != 2 {
t.Errorf("should have two available")
}
@@ -2423,15 +2425,15 @@ func TestNoRequestsFromPausedDevices(t *testing.T) {
m.ClusterConfig(device1, cc)
m.ClusterConfig(device2, cc)
avail = m.Availability("default", file, file.Blocks[0])
avail = m.testAvailability("default", file, file.Blocks[0])
if len(avail) != 2 {
t.Errorf("should have two available")
}
m.Closed(newFakeConnection(device1, m), errDeviceUnknown)
m.Closed(newFakeConnection(device2, m), errDeviceUnknown)
m.Closed(device1, errDeviceUnknown)
m.Closed(device2, errDeviceUnknown)
avail = m.Availability("default", file, file.Blocks[0])
avail = m.testAvailability("default", file, file.Blocks[0])
if len(avail) != 0 {
t.Errorf("should have no available")
}
@@ -2446,7 +2448,7 @@ func TestNoRequestsFromPausedDevices(t *testing.T) {
ccp.Folders[0].Paused = true
m.ClusterConfig(device1, ccp)
avail = m.Availability("default", file, file.Blocks[0])
avail = m.testAvailability("default", file, file.Blocks[0])
if len(avail) != 1 {
t.Errorf("should have one available")
}
@@ -2479,12 +2481,12 @@ func TestIssue2571(t *testing.T) {
m.ScanFolder("default")
if dir, ok := m.CurrentFolderFile("default", "toLink"); !ok {
if dir, ok := m.testCurrentFolderFile("default", "toLink"); !ok {
t.Fatalf("Dir missing in db")
} else if !dir.IsSymlink() {
t.Errorf("Dir wasn't changed to symlink")
}
if file, ok := m.CurrentFolderFile("default", filepath.Join("toLink", "a")); !ok {
if file, ok := m.testCurrentFolderFile("default", filepath.Join("toLink", "a")); !ok {
t.Fatalf("File missing in db")
} else if !file.Deleted {
t.Errorf("File below symlink has not been marked as deleted")
@@ -2517,7 +2519,7 @@ func TestIssue4573(t *testing.T) {
m.ScanFolder("default")
if file, ok := m.CurrentFolderFile("default", file); !ok {
if file, ok := m.testCurrentFolderFile("default", file); !ok {
t.Fatalf("File missing in db")
} else if file.Deleted {
t.Errorf("Inaccessible file has been marked as deleted.")
@@ -2577,7 +2579,7 @@ func TestInternalScan(t *testing.T) {
m.ScanFolder("default")
for path, cond := range testCases {
if f, ok := m.CurrentFolderFile("default", path); !ok {
if f, ok := m.testCurrentFolderFile("default", path); !ok {
t.Fatalf("%v missing in db", path)
} else if cond(f) {
t.Errorf("Incorrect db entry for %v", path)
@@ -2638,14 +2640,14 @@ func TestRemoveDirWithContent(t *testing.T) {
m := setupModel(t, defaultCfgWrapper)
defer cleanupModel(m)
dir, ok := m.CurrentFolderFile("default", "dirwith")
dir, ok := m.testCurrentFolderFile("default", "dirwith")
if !ok {
t.Fatalf("Can't get dir \"dirwith\" after initial scan")
}
dir.Deleted = true
dir.Version = dir.Version.Update(device1.Short()).Update(device1.Short())
file, ok := m.CurrentFolderFile("default", content)
file, ok := m.testCurrentFolderFile("default", content)
if !ok {
t.Fatalf("Can't get file \"%v\" after initial scan", content)
}
@@ -2657,11 +2659,11 @@ func TestRemoveDirWithContent(t *testing.T) {
// Is there something we could trigger on instead of just waiting?
timeout := time.NewTimer(5 * time.Second)
for {
dir, ok := m.CurrentFolderFile("default", "dirwith")
dir, ok := m.testCurrentFolderFile("default", "dirwith")
if !ok {
t.Fatalf("Can't get dir \"dirwith\" after index update")
}
file, ok := m.CurrentFolderFile("default", content)
file, ok := m.testCurrentFolderFile("default", content)
if !ok {
t.Fatalf("Can't get file \"%v\" after index update", content)
}
@@ -2713,11 +2715,11 @@ func TestIssue4475(t *testing.T) {
created := false
for {
if !created {
if _, ok := m.CurrentFolderFile("default", fileName); ok {
if _, ok := m.testCurrentFolderFile("default", fileName); ok {
created = true
}
} else {
dir, ok := m.CurrentFolderFile("default", "delDir")
dir, ok := m.testCurrentFolderFile("default", "delDir")
if !ok {
t.Fatalf("can't get dir from db")
}
@@ -2954,7 +2956,7 @@ func TestPausedFolders(t *testing.T) {
t.Errorf("Expected folder paused error, received: %v", err)
}
if err := m.ScanFolder("nonexistent"); err != errFolderMissing {
if err := m.ScanFolder("nonexistent"); err != ErrFolderMissing {
t.Errorf("Expected missing folder error, received: %v", err)
}
}
@@ -3035,7 +3037,7 @@ func TestIssue5002(t *testing.T) {
t.Error(err)
}
file, ok := m.CurrentFolderFile("default", "foo")
file, ok := m.testCurrentFolderFile("default", "foo")
if !ok {
t.Fatal("test file should exist")
}
@@ -3054,7 +3056,7 @@ func TestParentOfUnignored(t *testing.T) {
m.SetIgnores("default", []string{"!quux", "*"})
if parent, ok := m.CurrentFolderFile("default", "baz"); !ok {
if parent, ok := m.testCurrentFolderFile("default", "baz"); !ok {
t.Errorf(`Directory "baz" missing in db`)
} else if parent.IsIgnored() {
t.Errorf(`Directory "baz" is ignored`)
@@ -3171,7 +3173,7 @@ func TestConnCloseOnRestart(t *testing.T) {
br := &testutils.BlockingRW{}
nw := &testutils.NoopRW{}
m.AddConnection(protocol.NewConnection(device1, br, nw, testutils.NoopCloser{}, m, new(protocolmocks.ConnectionInfo), protocol.CompressionNever), protocol.Hello{})
m.AddConnection(protocol.NewConnection(device1, br, nw, testutils.NoopCloser{}, m, new(protocolmocks.ConnectionInfo), protocol.CompressionNever, nil), protocol.Hello{})
m.pmut.RLock()
if len(m.closed) != 1 {
t.Fatalf("Expected just one conn (len(m.conn) == %v)", len(m.conn))
@@ -3222,7 +3224,7 @@ func TestModTimeWindow(t *testing.T) {
// Get current version
fi, ok := m.CurrentFolderFile("default", name)
fi, ok := m.testCurrentFolderFile("default", name)
if !ok {
t.Fatal("File missing")
}
@@ -3237,7 +3239,7 @@ func TestModTimeWindow(t *testing.T) {
// No change due to within window
fi, _ = m.CurrentFolderFile("default", name)
fi, _ = m.testCurrentFolderFile("default", name)
if !fi.Version.Equal(v) {
t.Fatalf("Got version %v, expected %v", fi.Version, v)
}
@@ -3251,7 +3253,7 @@ func TestModTimeWindow(t *testing.T) {
// Version should have updated
fi, _ = m.CurrentFolderFile("default", name)
fi, _ = m.testCurrentFolderFile("default", name)
if fi.Version.Compare(v) != protocol.Greater {
t.Fatalf("Got result %v, expected %v", fi.Version.Compare(v), protocol.Greater)
}
@@ -3368,8 +3370,8 @@ func TestFolderAPIErrors(t *testing.T) {
if err := method(fcfg.ID); err != ErrFolderPaused {
t.Errorf(`Expected "%v", got "%v" (method no %v)`, ErrFolderPaused, err, i)
}
if err := method("notexisting"); err != errFolderMissing {
t.Errorf(`Expected "%v", got "%v" (method no %v)`, errFolderMissing, err, i)
if err := method("notexisting"); err != ErrFolderMissing {
t.Errorf(`Expected "%v", got "%v" (method no %v)`, ErrFolderMissing, err, i)
}
}
}
@@ -3776,7 +3778,7 @@ func TestScanDeletedROChangedOnSR(t *testing.T) {
must(t, writeFile(ffs, name, []byte(name), 0644))
m.ScanFolders()
file, ok := m.CurrentFolderFile(fcfg.ID, name)
file, ok := m.testCurrentFolderFile(fcfg.ID, name)
if !ok {
t.Fatal("file missing in db")
}
@@ -3927,7 +3929,7 @@ func TestIssue6961(t *testing.T) {
pauseFolder(t, wcfg, fcfg.ID, true)
pauseFolder(t, wcfg, fcfg.ID, false)
if comp := m.Completion(device2, fcfg.ID); comp.NeedDeletes != 0 {
if comp := m.testCompletion(device2, fcfg.ID); comp.NeedDeletes != 0 {
t.Error("Expected 0 needed deletes, got", comp.NeedDeletes)
} else {
t.Log(comp)
@@ -3946,7 +3948,7 @@ func TestCompletionEmptyGlobal(t *testing.T) {
files[0].Deleted = true
files[0].Version = files[0].Version.Update(device1.Short())
m.IndexUpdate(device1, fcfg.ID, files)
comp := m.Completion(protocol.LocalDeviceID, fcfg.ID)
comp := m.testCompletion(protocol.LocalDeviceID, fcfg.ID)
if comp.CompletionPct != 95 {
t.Error("Expected completion of 95%, got", comp.CompletionPct)
}
@@ -3978,13 +3980,13 @@ func TestNeedMetaAfterIndexReset(t *testing.T) {
files[0].Sequence = seq
m.IndexUpdate(device1, fcfg.ID, files)
if comp := m.Completion(device2, fcfg.ID); comp.NeedItems != 1 {
if comp := m.testCompletion(device2, fcfg.ID); comp.NeedItems != 1 {
t.Error("Expected one needed item for device2, got", comp.NeedItems)
}
// Pretend we had an index reset on device 1
m.Index(device1, fcfg.ID, files)
if comp := m.Completion(device2, fcfg.ID); comp.NeedItems != 1 {
if comp := m.testCompletion(device2, fcfg.ID); comp.NeedItems != 1 {
t.Error("Expected one needed item for device2, got", comp.NeedItems)
}
}
@@ -4142,7 +4144,8 @@ func TestCCFolderNotRunning(t *testing.T) {
defer cleanupModelAndRemoveDir(m, tfs.URI())
// A connection can happen before all the folders are started.
cc := m.generateClusterConfig(device1)
cc, _, err := m.generateClusterConfig(device1)
must(t, err)
if l := len(cc.Folders); l != 1 {
t.Fatalf("Expected 1 folder in CC, got %v", l)
}
@@ -4185,6 +4188,9 @@ func TestPendingFolder(t *testing.T) {
}
device3, err := protocol.DeviceIDFromString("AIBAEAQ-CAIBAEC-AQCAIBA-EAQCAIA-BAEAQCA-IBAEAQC-CAIBAEA-QCAIBA7")
if err != nil {
t.Fatal(err)
}
setDevice(t, w, config.DeviceConfiguration{DeviceID: device3})
if err := m.db.AddOrUpdatePendingFolder(pfolder, pfolder, device3, false); err != nil {
t.Fatal(err)
+42
View File
@@ -1399,3 +1399,45 @@ func TestRequestReceiveEncryptedLocalNoSend(t *testing.T) {
t.Fatal("timed out before receiving index")
}
}
func TestRequestIssue7474(t *testing.T) {
// Repro for https://github.com/syncthing/syncthing/issues/7474
// Devices A, B and C. B connected to A and C, but not A to C.
// A has valid file, B ignores it.
// In the test C is local, and B is the fake connection.
done := make(chan struct{})
defer close(done)
m, fc, fcfg, wcfgCancel := setupModelWithConnection(t)
defer wcfgCancel()
tfs := fcfg.Filesystem()
defer cleanupModelAndRemoveDir(m, tfs.URI())
indexChan := make(chan []protocol.FileInfo)
fc.setIndexFn(func(ctx context.Context, folder string, fs []protocol.FileInfo) error {
select {
case indexChan <- fs:
case <-done:
case <-ctx.Done():
}
return nil
})
name := "foo"
fc.addFileWithLocalFlags(name, protocol.FileInfoTypeFile, protocol.FlagLocalIgnored)
fc.sendIndexUpdate()
select {
case <-time.After(10 * time.Second):
t.Fatal("timed out before receiving index")
case fs := <-indexChan:
if len(fs) != 1 {
t.Fatalf("Expected one file in index, got %v", len(fs))
}
if !fs[0].IsInvalid() {
t.Error("Expected invalid file")
}
}
}
+9 -3
View File
@@ -13,6 +13,7 @@ import (
"github.com/pkg/errors"
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/osutil"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/sync"
)
@@ -346,8 +347,13 @@ func (s *sharedPullerState) finalClose() (bool, error) {
// folder from encrypted data we can extract this FileInfo from the end of
// the file and regain the original metadata.
func (s *sharedPullerState) finalizeEncrypted() error {
bs := make([]byte, encryptionTrailerSize(s.file))
n, err := s.file.MarshalTo(bs)
// Here the file is in native format, while encryption happens in
// wire format (always slashes).
wireFile := s.file
wireFile.Name = osutil.NormalizedFilename(wireFile.Name)
bs := make([]byte, encryptionTrailerSize(wireFile))
n, err := wireFile.MarshalTo(bs)
if err != nil {
return err
}
@@ -359,7 +365,7 @@ func (s *sharedPullerState) finalizeEncrypted() error {
return err
}
}
if _, err := s.writer.WriteAt(bs, s.file.Size); err != nil {
if _, err := s.writer.WriteAt(bs, wireFile.Size); err != nil {
return err
}
+32 -8
View File
@@ -13,8 +13,6 @@ import (
"testing"
"time"
"github.com/thejerf/suture/v4"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/db/backend"
@@ -84,12 +82,9 @@ func createTmpWrapper(cfg config.Configuration) (config.Wrapper, context.CancelF
}
wrapper := config.Wrap(tmpFile.Name(), cfg, myID, events.NoopLogger)
tmpFile.Close()
if cfgService, ok := wrapper.(suture.Service); ok {
ctx, cancel := context.WithCancel(context.Background())
go cfgService.Serve(ctx)
return wrapper, cancel
}
return wrapper, func() {}
ctx, cancel := context.WithCancel(context.Background())
go wrapper.Serve(ctx)
return wrapper, cancel
}
func tmpDefaultWrapper() (config.Wrapper, config.FolderConfiguration, context.CancelFunc) {
@@ -152,6 +147,7 @@ func setupModel(t testing.TB, w config.Wrapper) *testModel {
type testModel struct {
*model
t testing.TB
cancel context.CancelFunc
evCancel context.CancelFunc
stopped chan struct{}
@@ -171,6 +167,7 @@ func newModel(t testing.TB, cfg config.Wrapper, id protocol.DeviceID, clientName
model: m,
evCancel: cancel,
stopped: make(chan struct{}),
t: t,
}
}
@@ -184,6 +181,24 @@ func (m *testModel) ServeBackground() {
<-m.started
}
func (m *testModel) testAvailability(folder string, file protocol.FileInfo, block protocol.BlockInfo) []Availability {
av, err := m.model.Availability(folder, file, block)
must(m.t, err)
return av
}
func (m *testModel) testCurrentFolderFile(folder string, file string) (protocol.FileInfo, bool) {
f, ok, err := m.model.CurrentFolderFile(folder, file)
must(m.t, err)
return f, ok
}
func (m *testModel) testCompletion(device protocol.DeviceID, folder string) FolderCompletion {
comp, err := m.Completion(protocol.LocalDeviceID, "default")
must(m.t, err)
return comp
}
func cleanupModel(m *testModel) {
if m.cancel != nil {
m.cancel()
@@ -277,6 +292,15 @@ func dbSnapshot(t *testing.T, m Model, folder string) *db.Snapshot {
return snap
}
func fsetSnapshot(t *testing.T, fset *db.FileSet) *db.Snapshot {
t.Helper()
snap, err := fset.Snapshot()
if err != nil {
t.Fatal(err)
}
return snap
}
// Reach in and update the ignore matcher to one that always does
// reloads when asked to, instead of checking file mtimes. This is
// because we will be changing the files on disk often enough that the
-28
View File
@@ -1,28 +0,0 @@
// Copyright (C) 2015 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,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package osutil
import (
"net"
)
func interfaceAddresses(network string, intf *net.Interface) []string {
var out []string
addrs, err := intf.Addrs()
if err != nil {
return out
}
for _, addr := range addrs {
ipnet, ok := addr.(*net.IPNet)
if ok && (network == "tcp" || (network == "tcp4" && len(ipnet.IP) == net.IPv4len) || (network == "tcp6" && len(ipnet.IP) == net.IPv6len)) {
out = append(out, ipnet.IP.String())
}
}
return out
}
+1 -1
View File
@@ -36,7 +36,7 @@ func MaximizeOpenFileLimit() (int, error) {
// macOS doesn't like a soft limit greater then OPEN_MAX
// See also: man setrlimit
if runtime.GOOS == "darwin" && lim.Max > darwinOpenMax {
lim.Cur = darwinOpenMax
lim.Max = darwinOpenMax
}
// Try to increase the limit to the max.
+3 -3
View File
@@ -60,9 +60,9 @@ func benchmarkRequestsTLS(b *testing.B, conn0, conn1 net.Conn) {
func benchmarkRequestsConnPair(b *testing.B, conn0, conn1 net.Conn) {
// Start up Connections on them
c0 := NewConnection(LocalDeviceID, conn0, conn0, testutils.NoopCloser{}, new(fakeModel), new(mockedConnectionInfo), CompressionMetadata)
c0 := NewConnection(LocalDeviceID, conn0, conn0, testutils.NoopCloser{}, new(fakeModel), new(mockedConnectionInfo), CompressionMetadata, nil)
c0.Start()
c1 := NewConnection(LocalDeviceID, conn1, conn1, testutils.NoopCloser{}, new(fakeModel), new(mockedConnectionInfo), CompressionMetadata)
c1 := NewConnection(LocalDeviceID, conn1, conn1, testutils.NoopCloser{}, new(fakeModel), new(mockedConnectionInfo), CompressionMetadata, nil)
c1.Start()
// Satisfy the assertions in the protocol by sending an initial cluster config
@@ -188,7 +188,7 @@ func (m *fakeModel) ClusterConfig(deviceID DeviceID, config ClusterConfig) error
return nil
}
func (m *fakeModel) Closed(conn Connection, err error) {
func (m *fakeModel) Closed(DeviceID, error) {
}
func (m *fakeModel) DownloadProgress(deviceID DeviceID, folder string, updates []FileDownloadProgressUpdate) error {
+1 -1
View File
@@ -49,7 +49,7 @@ func (t *TestModel) Request(deviceID DeviceID, folder, name string, blockNo, siz
return &fakeRequestResponse{buf}, nil
}
func (t *TestModel) Closed(conn Connection, err error) {
func (t *TestModel) Closed(_ DeviceID, err error) {
t.closedErr = err
close(t.closedCh)
}
+5 -5
View File
@@ -203,15 +203,15 @@ func chunkify(s string) string {
}
func unchunkify(s string) string {
s = strings.Replace(s, "-", "", -1)
s = strings.Replace(s, " ", "", -1)
s = strings.ReplaceAll(s, "-", "")
s = strings.ReplaceAll(s, " ", "")
return s
}
func untypeoify(s string) string {
s = strings.Replace(s, "0", "O", -1)
s = strings.Replace(s, "1", "I", -1)
s = strings.Replace(s, "8", "B", -1)
s = strings.ReplaceAll(s, "0", "O")
s = strings.ReplaceAll(s, "1", "I")
s = strings.ReplaceAll(s, "8", "B")
return s
}
+47 -15
View File
@@ -8,18 +8,19 @@ package protocol
import (
"context"
"crypto/sha256"
"encoding/base32"
"encoding/binary"
"errors"
"fmt"
"io"
"strings"
"sync"
"time"
"github.com/gogo/protobuf/proto"
"github.com/miscreant/miscreant.go"
"github.com/syncthing/syncthing/lib/rand"
"github.com/syncthing/syncthing/lib/sha256"
"golang.org/x/crypto/chacha20poly1305"
"golang.org/x/crypto/hkdf"
"golang.org/x/crypto/scrypt"
@@ -41,11 +42,11 @@ const (
// must decrypt those and answer requests by encrypting the data.
type encryptedModel struct {
model Model
folderKeys map[string]*[keySize]byte // folder ID -> key
folderKeys *folderKeyRegistry
}
func (e encryptedModel) Index(deviceID DeviceID, folder string, files []FileInfo) error {
if folderKey, ok := e.folderKeys[folder]; ok {
if folderKey, ok := e.folderKeys.get(folder); ok {
// incoming index data to be decrypted
if err := decryptFileInfos(files, folderKey); err != nil {
return err
@@ -55,7 +56,7 @@ func (e encryptedModel) Index(deviceID DeviceID, folder string, files []FileInfo
}
func (e encryptedModel) IndexUpdate(deviceID DeviceID, folder string, files []FileInfo) error {
if folderKey, ok := e.folderKeys[folder]; ok {
if folderKey, ok := e.folderKeys.get(folder); ok {
// incoming index data to be decrypted
if err := decryptFileInfos(files, folderKey); err != nil {
return err
@@ -65,7 +66,7 @@ func (e encryptedModel) IndexUpdate(deviceID DeviceID, folder string, files []Fi
}
func (e encryptedModel) Request(deviceID DeviceID, folder, name string, blockNo, size int32, offset int64, hash []byte, weakHash uint32, fromTemporary bool) (RequestResponse, error) {
folderKey, ok := e.folderKeys[folder]
folderKey, ok := e.folderKeys.get(folder)
if !ok {
return e.model.Request(deviceID, folder, name, blockNo, size, offset, hash, weakHash, fromTemporary)
}
@@ -123,7 +124,7 @@ func (e encryptedModel) Request(deviceID DeviceID, folder, name string, blockNo,
}
func (e encryptedModel) DownloadProgress(deviceID DeviceID, folder string, updates []FileDownloadProgressUpdate) error {
if _, ok := e.folderKeys[folder]; !ok {
if _, ok := e.folderKeys.get(folder); !ok {
return e.model.DownloadProgress(deviceID, folder, updates)
}
@@ -135,42 +136,46 @@ func (e encryptedModel) ClusterConfig(deviceID DeviceID, config ClusterConfig) e
return e.model.ClusterConfig(deviceID, config)
}
func (e encryptedModel) Closed(conn Connection, err error) {
e.model.Closed(conn, err)
func (e encryptedModel) Closed(device DeviceID, err error) {
e.model.Closed(device, err)
}
// The encryptedConnection sits between the model and the encrypted device. It
// encrypts outgoing metadata and decrypts incoming responses.
type encryptedConnection struct {
ConnectionInfo
conn Connection
folderKeys map[string]*[keySize]byte // folder ID -> key
conn *rawConnection
folderKeys *folderKeyRegistry
}
func (e encryptedConnection) Start() {
e.conn.Start()
}
func (e encryptedConnection) SetFolderPasswords(passwords map[string]string) {
e.folderKeys.setPasswords(passwords)
}
func (e encryptedConnection) ID() DeviceID {
return e.conn.ID()
}
func (e encryptedConnection) Index(ctx context.Context, folder string, files []FileInfo) error {
if folderKey, ok := e.folderKeys[folder]; ok {
if folderKey, ok := e.folderKeys.get(folder); ok {
encryptFileInfos(files, folderKey)
}
return e.conn.Index(ctx, folder, files)
}
func (e encryptedConnection) IndexUpdate(ctx context.Context, folder string, files []FileInfo) error {
if folderKey, ok := e.folderKeys[folder]; ok {
if folderKey, ok := e.folderKeys.get(folder); ok {
encryptFileInfos(files, folderKey)
}
return e.conn.IndexUpdate(ctx, folder, files)
}
func (e encryptedConnection) Request(ctx context.Context, folder string, name string, blockNo int, offset int64, size int, hash []byte, weakHash uint32, fromTemporary bool) ([]byte, error) {
folderKey, ok := e.folderKeys[folder]
folderKey, ok := e.folderKeys.get(folder)
if !ok {
return e.conn.Request(ctx, folder, name, blockNo, offset, size, hash, weakHash, fromTemporary)
}
@@ -205,7 +210,7 @@ func (e encryptedConnection) Request(ctx context.Context, folder string, name st
}
func (e encryptedConnection) DownloadProgress(ctx context.Context, folder string, updates []FileDownloadProgressUpdate) {
if _, ok := e.folderKeys[folder]; !ok {
if _, ok := e.folderKeys.get(folder); !ok {
e.conn.DownloadProgress(ctx, folder, updates)
}
@@ -314,6 +319,7 @@ func encryptFileInfo(fi FileInfo, folderKey *[keySize]byte) FileInfo {
Permissions: 0644,
ModifiedS: 1234567890, // Sat Feb 14 00:31:30 CET 2009
Deleted: fi.Deleted,
RawInvalid: fi.IsInvalid(),
Version: version,
Sequence: fi.Sequence,
RawBlockSize: fi.RawBlockSize + blockOverhead,
@@ -487,8 +493,10 @@ func KeyFromPassword(folderID, password string) *[keySize]byte {
return &key
}
var hkdfSalt = []byte("syncthing")
func FileKey(filename string, folderKey *[keySize]byte) *[keySize]byte {
kdf := hkdf.New(sha256.New, append(folderKey[:], filename...), []byte("syncthing"), nil)
kdf := hkdf.New(sha256.New, append(folderKey[:], filename...), hkdfSalt, nil)
var fileKey [keySize]byte
n, err := io.ReadFull(kdf, fileKey[:])
if err != nil || n != keySize {
@@ -587,3 +595,27 @@ func isEncryptedParentFromComponents(pathComponents []string) bool {
}
return true
}
type folderKeyRegistry struct {
keys map[string]*[keySize]byte // folder ID -> key
mut sync.RWMutex
}
func newFolderKeyRegistry(passwords map[string]string) *folderKeyRegistry {
return &folderKeyRegistry{
keys: keysFromPasswords(passwords),
}
}
func (r *folderKeyRegistry) get(folder string) (*[keySize]byte, bool) {
r.mut.RLock()
key, ok := r.keys[folder]
r.mut.RUnlock()
return key, ok
}
func (r *folderKeyRegistry) setPasswords(passwords map[string]string) {
r.mut.Lock()
r.keys = keysFromPasswords(passwords)
r.mut.Unlock()
}
+43 -3
View File
@@ -12,9 +12,11 @@ import (
"reflect"
"regexp"
"strings"
"sync"
"testing"
"github.com/syncthing/syncthing/lib/rand"
"github.com/syncthing/syncthing/lib/sha256"
)
func TestEnDecryptName(t *testing.T) {
@@ -111,9 +113,8 @@ func TestEnDecryptBytes(t *testing.T) {
}
}
func TestEnDecryptFileInfo(t *testing.T) {
var key [32]byte
fi := FileInfo{
func encFileInfo() FileInfo {
return FileInfo{
Name: "hello",
Size: 45,
Permissions: 0755,
@@ -131,6 +132,11 @@ func TestEnDecryptFileInfo(t *testing.T) {
},
},
}
}
func TestEnDecryptFileInfo(t *testing.T) {
var key [32]byte
fi := encFileInfo()
enc := encryptFileInfo(fi, &key)
if bytes.Equal(enc.Blocks[0].Hash, enc.Blocks[1].Hash) {
@@ -153,6 +159,21 @@ func TestEnDecryptFileInfo(t *testing.T) {
}
}
func TestEncryptedFileInfoConsistency(t *testing.T) {
var key [32]byte
files := []FileInfo{
encFileInfo(),
encFileInfo(),
}
files[1].SetIgnored()
for i, f := range files {
enc := encryptFileInfo(f, &key)
if err := checkFileInfoConsistency(enc); err != nil {
t.Errorf("%v: %v", i, err)
}
}
}
func TestIsEncryptedParent(t *testing.T) {
comp := rand.String(maxPathComponent)
cases := []struct {
@@ -180,3 +201,22 @@ func TestIsEncryptedParent(t *testing.T) {
}
}
}
var benchmarkFileKey struct {
key [keySize]byte
sync.Once
}
func BenchmarkFileKey(b *testing.B) {
benchmarkFileKey.Do(func() {
sha256.SelectAlgo()
rand.Read(benchmarkFileKey.key[:])
})
b.ResetTimer()
b.ReportAllocs()
for i := 0; i < b.N; i++ {
FileKey("a_kind_of_long_filename.ext", &benchmarkFileKey.key)
}
}
+39
View File
@@ -135,6 +135,11 @@ type Connection struct {
result1 []byte
result2 error
}
SetFolderPasswordsStub func(map[string]string)
setFolderPasswordsMutex sync.RWMutex
setFolderPasswordsArgsForCall []struct {
arg1 map[string]string
}
StartStub func()
startMutex sync.RWMutex
startArgsForCall []struct {
@@ -817,6 +822,38 @@ func (fake *Connection) RequestReturnsOnCall(i int, result1 []byte, result2 erro
}{result1, result2}
}
func (fake *Connection) SetFolderPasswords(arg1 map[string]string) {
fake.setFolderPasswordsMutex.Lock()
fake.setFolderPasswordsArgsForCall = append(fake.setFolderPasswordsArgsForCall, struct {
arg1 map[string]string
}{arg1})
stub := fake.SetFolderPasswordsStub
fake.recordInvocation("SetFolderPasswords", []interface{}{arg1})
fake.setFolderPasswordsMutex.Unlock()
if stub != nil {
fake.SetFolderPasswordsStub(arg1)
}
}
func (fake *Connection) SetFolderPasswordsCallCount() int {
fake.setFolderPasswordsMutex.RLock()
defer fake.setFolderPasswordsMutex.RUnlock()
return len(fake.setFolderPasswordsArgsForCall)
}
func (fake *Connection) SetFolderPasswordsCalls(stub func(map[string]string)) {
fake.setFolderPasswordsMutex.Lock()
defer fake.setFolderPasswordsMutex.Unlock()
fake.SetFolderPasswordsStub = stub
}
func (fake *Connection) SetFolderPasswordsArgsForCall(i int) map[string]string {
fake.setFolderPasswordsMutex.RLock()
defer fake.setFolderPasswordsMutex.RUnlock()
argsForCall := fake.setFolderPasswordsArgsForCall[i]
return argsForCall.arg1
}
func (fake *Connection) Start() {
fake.startMutex.Lock()
fake.startArgsForCall = append(fake.startArgsForCall, struct {
@@ -1080,6 +1117,8 @@ func (fake *Connection) Invocations() map[string][][]interface{} {
defer fake.remoteAddrMutex.RUnlock()
fake.requestMutex.RLock()
defer fake.requestMutex.RUnlock()
fake.setFolderPasswordsMutex.RLock()
defer fake.setFolderPasswordsMutex.RUnlock()
fake.startMutex.RLock()
defer fake.startMutex.RUnlock()
fake.statisticsMutex.RLock()
+80 -75
View File
@@ -126,8 +126,8 @@ type Model interface {
Request(deviceID DeviceID, folder, name string, blockNo, size int32, offset int64, hash []byte, weakHash uint32, fromTemporary bool) (RequestResponse, error)
// A cluster configuration message was received
ClusterConfig(deviceID DeviceID, config ClusterConfig) error
// The peer device closed the connection
Closed(conn Connection, err error)
// The peer device closed the connection or an error occurred
Closed(device DeviceID, err error)
// The peer device sent progress updates for the files it is currently downloading
DownloadProgress(deviceID DeviceID, folder string, updates []FileDownloadProgressUpdate) error
}
@@ -140,6 +140,7 @@ type RequestResponse interface {
type Connection interface {
Start()
SetFolderPasswords(passwords map[string]string)
Close(err error)
ID() DeviceID
Index(ctx context.Context, folder string, files []FileInfo) error
@@ -225,24 +226,16 @@ const (
// Should not be modified in production code, just for testing.
var CloseTimeout = 10 * time.Second
func NewConnection(deviceID DeviceID, reader io.Reader, writer io.Writer, closer io.Closer, receiver Model, connInfo ConnectionInfo, compress Compression) Connection {
receiver = nativeModel{receiver}
rc := newRawConnection(deviceID, reader, writer, closer, receiver, connInfo, compress)
return wireFormatConnection{rc}
}
func NewEncryptedConnection(passwords map[string]string, deviceID DeviceID, reader io.Reader, writer io.Writer, closer io.Closer, receiver Model, connInfo ConnectionInfo, compress Compression) Connection {
keys := keysFromPasswords(passwords)
func NewConnection(deviceID DeviceID, reader io.Reader, writer io.Writer, closer io.Closer, receiver Model, connInfo ConnectionInfo, compress Compression, passwords map[string]string) Connection {
// Encryption / decryption is first (outermost) before conversion to
// native path formats.
nm := nativeModel{receiver}
em := encryptedModel{model: nm, folderKeys: keys}
em := &encryptedModel{model: nm, folderKeys: newFolderKeyRegistry(passwords)}
// We do the wire format conversion first (outermost) so that the
// metadata is in wire format when it reaches the encryption step.
rc := newRawConnection(deviceID, reader, writer, closer, em, connInfo, compress)
ec := encryptedConnection{ConnectionInfo: rc, conn: rc, folderKeys: keys}
ec := encryptedConnection{ConnectionInfo: rc, conn: rc, folderKeys: em.folderKeys}
wc := wireFormatConnection{ec}
return wc
@@ -296,7 +289,7 @@ func (c *rawConnection) Start() {
c.pingReceiver()
c.loopWG.Done()
}()
c.startTime = time.Now()
c.startTime = time.Now().Truncate(time.Second)
}
func (c *rawConnection) ID() DeviceID {
@@ -437,82 +430,61 @@ func (c *rawConnection) dispatcherLoop() (err error) {
case <-c.closed:
return ErrClosed
}
msgContext, err := messageContext(msg)
if err != nil {
return fmt.Errorf("protocol error: %w", err)
}
l.Debugf("handle %v message", msgContext)
switch msg := msg.(type) {
case *ClusterConfig:
l.Debugln("read ClusterConfig message")
if state == stateInitial {
state = stateReady
}
if err := c.receiver.ClusterConfig(c.id, *msg); err != nil {
return fmt.Errorf("receiving cluster config: %w", err)
}
case *Index:
l.Debugln("read Index message")
case *Close:
return fmt.Errorf("closed by remote: %v", msg.Reason)
default:
if state != stateReady {
return fmt.Errorf("protocol error: index message in state %d", state)
return newProtocolError(fmt.Errorf("invalid state %d", state), msgContext)
}
if err := checkIndexConsistency(msg.Files); err != nil {
return errors.Wrap(err, "protocol error: index")
}
if err := c.handleIndex(*msg); err != nil {
return fmt.Errorf("receiving index: %w", err)
}
state = stateReady
}
switch msg := msg.(type) {
case *Index:
err = checkIndexConsistency(msg.Files)
case *IndexUpdate:
l.Debugln("read IndexUpdate message")
if state != stateReady {
return fmt.Errorf("protocol error: index update message in state %d", state)
}
if err := checkIndexConsistency(msg.Files); err != nil {
return errors.Wrap(err, "protocol error: index update")
}
if err := c.handleIndexUpdate(*msg); err != nil {
return fmt.Errorf("receiving index update: %w", err)
}
state = stateReady
err = checkIndexConsistency(msg.Files)
case *Request:
err = checkFilename(msg.Name)
}
if err != nil {
return newProtocolError(err, msgContext)
}
switch msg := msg.(type) {
case *ClusterConfig:
err = c.receiver.ClusterConfig(c.id, *msg)
case *Index:
err = c.handleIndex(*msg)
case *IndexUpdate:
err = c.handleIndexUpdate(*msg)
case *Request:
l.Debugln("read Request message")
if state != stateReady {
return fmt.Errorf("protocol error: request message in state %d", state)
}
if err := checkFilename(msg.Name); err != nil {
return errors.Wrapf(err, "protocol error: request: %q", msg.Name)
}
go c.handleRequest(*msg)
case *Response:
l.Debugln("read Response message")
if state != stateReady {
return fmt.Errorf("protocol error: response message in state %d", state)
}
c.handleResponse(*msg)
case *DownloadProgress:
l.Debugln("read DownloadProgress message")
if state != stateReady {
return fmt.Errorf("protocol error: response message in state %d", state)
}
if err := c.receiver.DownloadProgress(c.id, msg.Folder, msg.Updates); err != nil {
return fmt.Errorf("receiving download progress: %w", err)
}
case *Ping:
l.Debugln("read Ping message")
if state != stateReady {
return fmt.Errorf("protocol error: ping message in state %d", state)
}
// Nothing
case *Close:
l.Debugln("read Close message")
return fmt.Errorf("closed by remote: %v", msg.Reason)
default:
l.Debugf("read unknown message: %+T", msg)
return fmt.Errorf("protocol error: %s: unknown or empty message", c.id)
err = c.receiver.DownloadProgress(c.id, msg.Folder, msg.Updates)
}
if err != nil {
return newHandleError(err, msgContext)
}
}
}
@@ -769,6 +741,8 @@ func (c *rawConnection) writerLoop() {
}
func (c *rawConnection) writeMessage(msg message) error {
msgContext, _ := messageContext(msg)
l.Debugf("Writing %v", msgContext)
if c.shouldCompressMessage(msg) {
return c.writeCompressedMessage(msg)
}
@@ -976,7 +950,7 @@ func (c *rawConnection) internalClose(err error) {
<-c.dispatcherLoopStopped
c.receiver.Closed(c, err)
c.receiver.Closed(c.ID(), err)
})
}
@@ -1040,7 +1014,7 @@ type Statistics struct {
func (c *rawConnection) Statistics() Statistics {
return Statistics{
At: time.Now(),
At: time.Now().Truncate(time.Second),
InBytesTotal: c.cr.Tot(),
OutBytesTotal: c.cw.Tot(),
StartedAt: c.startTime,
@@ -1078,3 +1052,34 @@ func (c *rawConnection) lz4Decompress(src []byte) ([]byte, error) {
}
return decoded, nil
}
func newProtocolError(err error, msgContext string) error {
return fmt.Errorf("protocol error on %v: %w", msgContext, err)
}
func newHandleError(err error, msgContext string) error {
return fmt.Errorf("handling %v: %w", msgContext, err)
}
func messageContext(msg message) (string, error) {
switch msg := msg.(type) {
case *ClusterConfig:
return "cluster-config", nil
case *Index:
return fmt.Sprintf("index for %v", msg.Folder), nil
case *IndexUpdate:
return fmt.Sprintf("index-update for %v", msg.Folder), nil
case *Request:
return fmt.Sprintf(`request for "%v" in %v`, msg.Name, msg.Folder), nil
case *Response:
return "response", nil
case *DownloadProgress:
return fmt.Sprintf("download-progress for %v", msg.Folder), nil
case *Ping:
return "ping", nil
case *Close:
return "close", nil
default:
return "", errors.New("unknown or empty message")
}
}
+25 -14
View File
@@ -31,10 +31,10 @@ func TestPing(t *testing.T) {
ar, aw := io.Pipe()
br, bw := io.Pipe()
c0 := NewConnection(c0ID, ar, bw, testutils.NoopCloser{}, newTestModel(), new(mockedConnectionInfo), CompressionAlways).(wireFormatConnection).Connection.(*rawConnection)
c0 := getRawConnection(NewConnection(c0ID, ar, bw, testutils.NoopCloser{}, newTestModel(), new(mockedConnectionInfo), CompressionAlways, nil))
c0.Start()
defer closeAndWait(c0, ar, bw)
c1 := NewConnection(c1ID, br, aw, testutils.NoopCloser{}, newTestModel(), new(mockedConnectionInfo), CompressionAlways).(wireFormatConnection).Connection.(*rawConnection)
c1 := getRawConnection(NewConnection(c1ID, br, aw, testutils.NoopCloser{}, newTestModel(), new(mockedConnectionInfo), CompressionAlways, nil))
c1.Start()
defer closeAndWait(c1, ar, bw)
c0.ClusterConfig(ClusterConfig{})
@@ -57,10 +57,10 @@ func TestClose(t *testing.T) {
ar, aw := io.Pipe()
br, bw := io.Pipe()
c0 := NewConnection(c0ID, ar, bw, testutils.NoopCloser{}, m0, new(mockedConnectionInfo), CompressionAlways).(wireFormatConnection).Connection.(*rawConnection)
c0 := getRawConnection(NewConnection(c0ID, ar, bw, testutils.NoopCloser{}, m0, new(mockedConnectionInfo), CompressionAlways, nil))
c0.Start()
defer closeAndWait(c0, ar, bw)
c1 := NewConnection(c1ID, br, aw, testutils.NoopCloser{}, m1, new(mockedConnectionInfo), CompressionAlways)
c1 := NewConnection(c1ID, br, aw, testutils.NoopCloser{}, m1, new(mockedConnectionInfo), CompressionAlways, nil)
c1.Start()
defer closeAndWait(c1, ar, bw)
c0.ClusterConfig(ClusterConfig{})
@@ -102,7 +102,7 @@ func TestCloseOnBlockingSend(t *testing.T) {
m := newTestModel()
rw := testutils.NewBlockingRW()
c := NewConnection(c0ID, rw, rw, testutils.NoopCloser{}, m, new(mockedConnectionInfo), CompressionAlways).(wireFormatConnection).Connection.(*rawConnection)
c := getRawConnection(NewConnection(c0ID, rw, rw, testutils.NoopCloser{}, m, new(mockedConnectionInfo), CompressionAlways, nil))
c.Start()
defer closeAndWait(c, rw)
@@ -153,10 +153,10 @@ func TestCloseRace(t *testing.T) {
ar, aw := io.Pipe()
br, bw := io.Pipe()
c0 := NewConnection(c0ID, ar, bw, testutils.NoopCloser{}, m0, new(mockedConnectionInfo), CompressionNever).(wireFormatConnection).Connection.(*rawConnection)
c0 := getRawConnection(NewConnection(c0ID, ar, bw, testutils.NoopCloser{}, m0, new(mockedConnectionInfo), CompressionNever, nil))
c0.Start()
defer closeAndWait(c0, ar, bw)
c1 := NewConnection(c1ID, br, aw, testutils.NoopCloser{}, m1, new(mockedConnectionInfo), CompressionNever)
c1 := NewConnection(c1ID, br, aw, testutils.NoopCloser{}, m1, new(mockedConnectionInfo), CompressionNever, nil)
c1.Start()
defer closeAndWait(c1, ar, bw)
c0.ClusterConfig(ClusterConfig{})
@@ -193,7 +193,7 @@ func TestClusterConfigFirst(t *testing.T) {
m := newTestModel()
rw := testutils.NewBlockingRW()
c := NewConnection(c0ID, rw, &testutils.NoopRW{}, testutils.NoopCloser{}, m, new(mockedConnectionInfo), CompressionAlways).(wireFormatConnection).Connection.(*rawConnection)
c := getRawConnection(NewConnection(c0ID, rw, &testutils.NoopRW{}, testutils.NoopCloser{}, m, new(mockedConnectionInfo), CompressionAlways, nil))
c.Start()
defer closeAndWait(c, rw)
@@ -245,7 +245,7 @@ func TestCloseTimeout(t *testing.T) {
m := newTestModel()
rw := testutils.NewBlockingRW()
c := NewConnection(c0ID, rw, rw, testutils.NoopCloser{}, m, new(mockedConnectionInfo), CompressionAlways).(wireFormatConnection).Connection.(*rawConnection)
c := getRawConnection(NewConnection(c0ID, rw, rw, testutils.NoopCloser{}, m, new(mockedConnectionInfo), CompressionAlways, nil))
c.Start()
defer closeAndWait(c, rw)
@@ -865,7 +865,7 @@ func TestClusterConfigAfterClose(t *testing.T) {
m := newTestModel()
rw := testutils.NewBlockingRW()
c := NewConnection(c0ID, rw, rw, testutils.NoopCloser{}, m, new(mockedConnectionInfo), CompressionAlways).(wireFormatConnection).Connection.(*rawConnection)
c := getRawConnection(NewConnection(c0ID, rw, rw, testutils.NoopCloser{}, m, new(mockedConnectionInfo), CompressionAlways, nil))
c.Start()
defer closeAndWait(c, rw)
@@ -889,7 +889,7 @@ func TestDispatcherToCloseDeadlock(t *testing.T) {
// the model callbacks (ClusterConfig).
m := newTestModel()
rw := testutils.NewBlockingRW()
c := NewConnection(c0ID, rw, &testutils.NoopRW{}, testutils.NoopCloser{}, m, new(mockedConnectionInfo), CompressionAlways).(wireFormatConnection).Connection.(*rawConnection)
c := getRawConnection(NewConnection(c0ID, rw, &testutils.NoopRW{}, testutils.NoopCloser{}, m, new(mockedConnectionInfo), CompressionAlways, nil))
m.ccFn = func(devID DeviceID, cc ClusterConfig) {
c.Close(errManual)
}
@@ -962,17 +962,28 @@ func TestIndexIDString(t *testing.T) {
}
}
func closeAndWait(c Connection, closers ...io.Closer) {
func closeAndWait(c interface{}, closers ...io.Closer) {
for _, closer := range closers {
closer.Close()
}
var raw *rawConnection
switch i := c.(type) {
case wireFormatConnection:
raw = i.Connection.(*rawConnection)
case *rawConnection:
raw = i
default:
raw = getRawConnection(c.(Connection))
}
raw.internalClose(ErrClosed)
raw.loopWG.Wait()
}
func getRawConnection(c Connection) *rawConnection {
var raw *rawConnection
switch i := c.(type) {
case wireFormatConnection:
raw = i.Connection.(encryptedConnection).conn
case encryptedConnection:
raw = i.conn
}
return raw
}
+4
View File
@@ -4,6 +4,7 @@ package protocol
import (
"errors"
"fmt"
"io"
)
@@ -86,6 +87,9 @@ func ReadMessage(r io.Reader) (interface{}, error) {
if header.magic != magic {
return nil, errors.New("magic mismatch")
}
if header.messageLength < 0 || header.messageLength > 1024 {
return nil, fmt.Errorf("bad length (%d)", header.messageLength)
}
buf = make([]byte, int(header.messageLength))
if _, err := io.ReadFull(r, buf); err != nil {
+6 -6
View File
@@ -195,17 +195,17 @@ func BenchmarkValidate(b *testing.B) {
for i := 0; i < blocksPerType; i++ {
var b block
b.data = make([]byte, 128<<10)
r.Read(b.data[:])
b.hash = sha256.Sum256(b.data[:])
b.weakhash = origAdler32.Checksum(b.data[:])
r.Read(b.data)
b.hash = sha256.Sum256(b.data)
b.weakhash = origAdler32.Checksum(b.data)
blocks = append(blocks, b)
}
// Blocks where the hash matches, but the weakhash doesn't.
for i := 0; i < blocksPerType; i++ {
var b block
b.data = make([]byte, 128<<10)
r.Read(b.data[:])
b.hash = sha256.Sum256(b.data[:])
r.Read(b.data)
b.hash = sha256.Sum256(b.data)
b.weakhash = 1 // Zeros causes Validate to skip the weakhash.
blocks = append(blocks, b)
}
@@ -215,7 +215,7 @@ func BenchmarkValidate(b *testing.B) {
for i := 0; i < b.N; i++ {
for _, b := range blocks {
Validate(b.data[:], b.hash[:], b.weakhash)
Validate(b.data, b.hash[:], b.weakhash)
}
}
}
+4
View File
@@ -97,6 +97,10 @@ func (s singleFileFS) Open(name string) (fs.File, error) {
return &fakeFile{s.name, s.filesize, 0}, nil
}
func (s singleFileFS) Options() []fs.Option {
return nil
}
type fakeInfo struct {
name string
size int64
+1 -1
View File
@@ -62,7 +62,7 @@ func (s *DeviceStatisticsReference) GetLastConnectionDuration() (time.Duration,
func (s *DeviceStatisticsReference) WasSeen() error {
l.Debugln("stats.DeviceStatisticsReference.WasSeen:", s.device)
return s.ns.PutTime(lastSeenKey, time.Now())
return s.ns.PutTime(lastSeenKey, time.Now().Truncate(time.Second))
}
func (s *DeviceStatisticsReference) LastConnectionDuration(d time.Duration) error {
+2 -2
View File
@@ -61,7 +61,7 @@ func (s *FolderStatisticsReference) GetLastFile() (LastFile, error) {
func (s *FolderStatisticsReference) ReceivedFile(file string, deleted bool) error {
l.Debugln("stats.FolderStatisticsReference.ReceivedFile:", s.folder, file)
if err := s.ns.PutTime("lastFileAt", time.Now()); err != nil {
if err := s.ns.PutTime("lastFileAt", time.Now().Truncate(time.Second)); err != nil {
return err
}
if err := s.ns.PutString("lastFileName", file); err != nil {
@@ -74,7 +74,7 @@ func (s *FolderStatisticsReference) ReceivedFile(file string, deleted bool) erro
}
func (s *FolderStatisticsReference) ScanCompleted() error {
return s.ns.PutTime("lastScan", time.Now())
return s.ns.PutTime("lastScan", time.Now().Truncate(time.Second))
}
func (s *FolderStatisticsReference) GetLastScanTime() (time.Time, error) {

Some files were not shown because too many files have changed in this diff Show More