Compare commits
53
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb4fdaf4c0 | ||
|
|
0d7a77ba85 | ||
|
|
924b96856f | ||
|
|
f7929229c8 | ||
|
|
6b25eb2e79 | ||
|
|
bc08a951f1 | ||
|
|
a87c5515bd | ||
|
|
ebcd22b02b | ||
|
|
4b02b7e6f1 | ||
|
|
fdd823d2cb | ||
|
|
8ef504f745 | ||
|
|
960e850a78 | ||
|
|
ea701a4e9e | ||
|
|
6c573a5762 | ||
|
|
3ac858b150 | ||
|
|
f4372710bf | ||
|
|
f39477bbd5 | ||
|
|
6e5514419d | ||
|
|
f014b7b919 | ||
|
|
81484699db | ||
|
|
6d93d9c488 | ||
|
|
0930bccf88 | ||
|
|
e321bd3941 | ||
|
|
4b02937862 | ||
|
|
21e6849f2d | ||
|
|
39c2d1bc1a | ||
|
|
cd21b8dfa5 | ||
|
|
40fbdc87ce | ||
|
|
1814f4693d | ||
|
|
3f2b584c4e | ||
|
|
e0dd737822 | ||
|
|
d2d4fcc1df | ||
|
|
273ee09925 | ||
|
|
bb886868d2 | ||
|
|
f80ee472c2 | ||
|
|
a12ede3bbe | ||
|
|
97a8777d03 | ||
|
|
8a4c00d82e | ||
|
|
31f859e909 | ||
|
|
4d979a1ce9 | ||
|
|
4465cdf8bc | ||
|
|
3938b61c3f | ||
|
|
cdef503db6 | ||
|
|
df08984a58 | ||
|
|
cf838c71f7 | ||
|
|
9a001051d6 | ||
|
|
5548a8eb7a | ||
|
|
727df34aa1 | ||
|
|
9587a523b3 | ||
|
|
22e44642a0 | ||
|
|
c00520281b | ||
|
|
587c89d979 | ||
|
|
310fba4c12 |
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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]
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
},
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:""`
|
||||
}
|
||||
@@ -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"`
|
||||
}
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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 odnoszą 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>
|
||||
<a href="https://docs.syncthing.net/users/foldertypes.html" target="_blank"><span class="fas fa-question-circle"></span> <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 & 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
@@ -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
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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).
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
}
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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
Reference in New Issue
Block a user