Compare commits

..
Author SHA1 Message Date
Jakob BorgandGitHub 058bcd7334 fix: open GUI when relaunched instead of printing error (fixes #10727) (#10852)
This is primarily to improve the experience in environments that start
Syncthing from a graphical environment, e.g., Windows.

When already running, instead of printing an error we open the GUI. If
the GUI is not available (but the lockfile indicates we are running),
print an error and exit.

If --no-browser is given or STNOBROWSER is set, act like before.

Closes #10736. Marking as `fix` because this does not deserve to trigger
a minor release on its own.

---------

Signed-off-by: Jakob Borg <jakob@kastelo.net>
2026-08-11 11:17:09 +00:00
Syncthing Release Automation 42ea7231c5 chore(gui, man, authors): update docs, translations, and contributors 2026-08-10 04:17:57 +00:00
Jakob BorgandGitHub 328d910aee fix(api): correctly return metrics, support bundle (fixes #10847) (#10849)
Our faked request wasn't good enough; improve it, adding a test.

Signed-off-by: Jakob Borg <jakob@kastelo.net>
2026-08-05 18:35:17 +00:00
Jakob BorgandGitHub d7df27a367 fix(model): correctly handle receive-only changed directories (fixes #8004) (#10843)
When adding a folder in receive-only mode where the contents were
already identical to a remote device, directories would remain as
locally changed when everything else had consolidated. The reason this
happened is that we only did the matching between locally changed files
and their global equivalent for changed items, but directories are
typically not "changed" much as we don't track their mtime, so they
wouldn't pass through this stage when scanning.

Now, instead, do the check when we're anyway walking all the files in
phase two of scanning. This catches all cases of identical items, files
or directories, regardless of how they came to be in the index.

Signed-off-by: Jakob Borg <jakob@kastelo.net>
2026-08-05 20:23:53 +02:00
Jakob BorgandGitHub 8ea09c0094 chore: style fixes from go fix (#10846)
Just `go fix ./...`

Signed-off-by: Jakob Borg <jakob@kastelo.net>
2026-08-05 20:23:22 +02:00
180 changed files with 1870 additions and 471 deletions
@@ -0,0 +1,88 @@
name: Build Infrastructure Images
on:
push:
branches:
- infrastructure
- infra-*
env:
GO_VERSION: "~1.26.0"
CGO_ENABLED: "0"
BUILD_USER: docker
BUILD_HOST: github.syncthing.net
permissions:
contents: read
packages: write
jobs:
docker-syncthing:
name: Build and push Docker images
if: github.repository_owner == 'syncthing'
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
pkg:
- stcrashreceiver
- strelaypoolsrv
- stupgrades
- ursrv
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: actions/setup-go@v6
with:
go-version: ${{ env.GO_VERSION }}
check-latest: true
- name: Login to Docker Hub
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GHCR
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build binaries
run: |
for arch in arm64 amd64; do
go run build.go -goos linux -goarch "$arch" build ${{ matrix.pkg }}
mv ${{ matrix.pkg }} ${{ matrix.pkg }}-linux-"$arch"
done
- name: Set up QEMU
uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 # v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
- name: Set Docker tags (all branches)
run: |
tags=docker.io/syncthing/${{ matrix.pkg }}:${{ github.sha }},ghcr.io/syncthing/infra/${{ matrix.pkg }}:${{ github.sha }}
echo "TAGS=$tags" >> $GITHUB_ENV
- name: Set Docker tags (latest)
if: github.ref == 'refs/heads/infrastructure'
run: |
tags=docker.io/syncthing/${{ matrix.pkg }}:latest,ghcr.io/syncthing/infra/${{ matrix.pkg }}:latest,${{ env.TAGS }}
echo "TAGS=$tags" >> $GITHUB_ENV
- name: Build and push
uses: docker/build-push-action@ca052bb54ab0790a636c9b5f226502c73d547a25 # v5
with:
context: .
file: ./Dockerfile.${{ matrix.pkg }}
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ env.TAGS }}
labels: |
org.opencontainers.image.revision=${{ github.sha }}
File diff suppressed because it is too large Load Diff
+21
View File
@@ -0,0 +1,21 @@
name: Mirrors
on: [push, delete]
permissions:
contents: read
jobs:
codeberg:
name: Mirror to Codeberg
if: github.repository_owner == 'syncthing'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: yesolutions/mirror-action@662fce0eced8996f64d7fa264d76cddd84827f33 # master
with:
REMOTE: ssh://git@codeberg.org/${{ github.repository }}.git
GIT_SSH_PRIVATE_KEY: ${{ secrets.CODEBERG_PUSH_KEY }}
GIT_SSH_NO_VERIFY_HOST: "true"
+21
View File
@@ -0,0 +1,21 @@
name: Org membership recommendations
on:
workflow_dispatch:
schedule:
- cron: '0 0 1 * *'
jobs:
run-recommendation:
name: Check for a recommendation
if: github.repository_owner == 'syncthing'
runs-on: ubuntu-latest
steps:
- uses: docker://ghcr.io/calmh/github-org-members:latest
env:
GITHUB_ORGANISATION: syncthing
GITHUB_TOKEN: ${{ secrets.GOM_GITHUB_TOKEN }}
GOM_IGNORE_USERS: ${{ secrets.GOM_IGNORE_USERS }}
GOM_ALSO_REPOS: ${{ secrets.GOM_ALSO_REPOS }}
+28
View File
@@ -0,0 +1,28 @@
name: PR metadata
on:
pull_request_target:
types:
- opened
- reopened
- edited
- synchronize
permissions:
contents: read
pull-requests: write
jobs:
#
# Set labels on PRs, which are then used to categorise release notes
#
labels:
name: Set labels
if: github.repository_owner == 'syncthing'
runs-on: ubuntu-latest
steps:
- uses: srvaroa/labeler@9c29ad1ef33d169f9ef33c52722faf47a566bcf3 # v1
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
+60
View File
@@ -0,0 +1,60 @@
name: Release Syncthing
on:
push:
branches:
- release
- release-rc*
permissions:
contents: write
jobs:
create-release-tag:
name: Create release tag
if: github.repository_owner == 'syncthing'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
ref: ${{ github.ref }} # https://github.com/actions/checkout/issues/882
token: ${{ secrets.ACTIONS_GITHUB_TOKEN }}
- uses: actions/setup-go@v6
with:
go-version: stable
- name: Determine version to release
run: |
if [[ "$GITHUB_REF_NAME" == "release" ]] ; then
next=$(go run ./script/next-version.go)
else
next=$(go run ./script/next-version.go --pre)
fi
echo "NEXT=$next" >> $GITHUB_ENV
echo "Next version is $next"
prev=$(git describe --exclude "*-*" --abbrev=0)
echo "PREV=$prev" >> $GITHUB_ENV
echo "Previous version is $prev"
- name: Determine release notes
run: |
go run ./script/relnotes.go --new-ver "$NEXT" --branch "$GITHUB_REF_NAME" --prev-ver "$PREV" > notes.md
env:
GITHUB_TOKEN: ${{ secrets.ACTIONS_GITHUB_TOKEN }}
- name: Create and push tag
run: |
git config --global user.name 'Syncthing Release Automation'
git config --global user.email 'release@syncthing.net'
git tag -a -F notes.md --cleanup=whitespace "$NEXT"
git push origin "$NEXT"
- name: Trigger the build
uses: benc-uk/workflow-dispatch@7a027648b88c2413826b6ddd6c76114894dc5ec4 # v1
with:
workflow: build-syncthing.yaml
ref: refs/tags/${{ env.NEXT }}
token: ${{ secrets.ACTIONS_GITHUB_TOKEN }}
+25
View File
@@ -0,0 +1,25 @@
name: Trigger nightly build & release
on:
workflow_dispatch:
schedule:
# Run nightly build at 01:00 UTC
- cron: '00 01 * * *'
permissions:
contents: write
jobs:
trigger-nightly:
name: Push to release-nightly to trigger build
if: github.repository_owner == 'syncthing'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
token: ${{ secrets.ACTIONS_GITHUB_TOKEN }}
fetch-depth: 0
- run: |
git push origin main:release-nightly
@@ -0,0 +1,31 @@
name: Update translations and documentation
on:
workflow_dispatch:
schedule:
- cron: '42 3 * * 1'
permissions:
contents: write
jobs:
update_transifex_docs:
runs-on: ubuntu-latest
name: Update translations and documentation
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
token: ${{ secrets.ACTIONS_GITHUB_TOKEN }}
- uses: actions/setup-go@v6
with:
go-version: stable
- run: |
set -euo pipefail
git config --global user.name 'Syncthing Release Automation'
git config --global user.email 'release@syncthing.net'
bash build.sh translate
bash build.sh prerelease
git push
env:
WEBLATE_TOKEN: ${{ secrets.WEBLATE_TOKEN }}
+4 -4
View File
@@ -17,10 +17,10 @@ import (
)
type event struct {
ID int `json:"id"`
Type string `json:"type"`
Time time.Time `json:"time"`
Data map[string]interface{} `json:"data"`
ID int `json:"id"`
Type string `json:"type"`
Time time.Time `json:"time"`
Data map[string]any `json:"data"`
}
func main() {
-1
View File
@@ -60,7 +60,6 @@ func checkServers(deviceID protocol.DeviceID, servers ...string) {
t0 := time.Now()
resc := make(chan checkResult)
for _, srv := range servers {
srv := srv
go func() {
res := checkServer(deviceID, srv)
res.server = srv
+2 -5
View File
@@ -34,7 +34,7 @@ func generateFiles(dir string, files, maxexp int, srcname string) error {
return err
}
for i := 0; i < files; i++ {
for range files {
n := randomName()
if rand.Float64() < 0.05 {
@@ -51,10 +51,7 @@ func generateFiles(dir string, files, maxexp int, srcname string) error {
p1 := filepath.Join(p0, n)
s := int64(1 << uint(rand.Intn(maxexp)))
a := int64(128 * 1024)
if a > s {
a = s
}
a := min(int64(128*1024), s)
s += rand.Int63n(a)
if err := generateOneFile(fd, p1, s); err != nil {
+2 -2
View File
@@ -138,7 +138,7 @@ func printProgress(prefix string, count *atomic.Int64) {
}
}
func saveCert(priv interface{}, derBytes []byte) {
func saveCert(priv any, derBytes []byte) {
certOut, err := os.Create("cert.pem")
if err != nil {
fmt.Println(err)
@@ -179,7 +179,7 @@ func saveCert(priv interface{}, derBytes []byte) {
}
}
func pemBlockForKey(priv interface{}) (*pem.Block, error) {
func pemBlockForKey(priv any) (*pem.Block, error) {
switch k := priv.(type) {
case *rsa.PrivateKey:
return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)}, nil
+1 -1
View File
@@ -203,7 +203,7 @@ func loadIgnorePatterns(path string) (*ignorePatterns, error) {
}
var patterns []*regexp.Regexp
for _, line := range strings.Split(string(bs), "\n") {
for line := range strings.SplitSeq(string(bs), "\n") {
line = strings.TrimSpace(line)
if line == "" {
continue
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build noassets
// +build noassets
package auto
+1 -1
View File
@@ -587,7 +587,7 @@ func loadRelays(file string, geoip *geoip.Provider) []*relay {
}
var relays []*relay
for _, line := range strings.Split(string(content), "\n") {
for line := range strings.SplitSeq(string(content), "\n") {
if line == "" {
continue
}
+1 -1
View File
@@ -17,7 +17,7 @@ import (
)
func init() {
for i := 0; i < 10; i++ {
for i := range 10 {
u := fmt.Sprintf("permanent%d", i)
permanentRelays = append(permanentRelays, &relay{URL: u})
}
+1 -1
View File
@@ -188,7 +188,7 @@ func (p *proxy) ServeHTTP(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(resp.StatusCode)
if strings.HasPrefix(ct, "application/json") {
// Special JSON handling; clean it up a bit.
var v interface{}
var v any
if err := json.NewDecoder(resp.Body).Decode(&v); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
+1 -4
View File
@@ -402,10 +402,7 @@ func (s *apiSrv) certificateBytes(req *http.Request) ([]byte, error) {
b.WriteByte('\n')
for i := 0; i < len(cert); i += 64 {
end := i + 64
if end > len(cert) {
end = len(cert)
}
end := min(i+64, len(cert))
b.WriteString(cert[i:end])
b.WriteByte('\n')
}
+3 -8
View File
@@ -7,7 +7,6 @@
package main
import (
"context"
"crypto/tls"
"fmt"
"io"
@@ -120,7 +119,7 @@ func TestRetryAfterSHistogram(t *testing.T) {
numBuckets := (notFoundRetryUnknownMaxSeconds + bucketSize - 1) / bucketSize
buckets := make([]int, numBuckets)
for i := 0; i < n; i++ {
for range n {
v := tracker.retryAfterS()
if v < notFoundRetryUnknownMinSeconds || v > notFoundRetryUnknownMaxSeconds {
t.Fatalf("retryAfterS() = %d, out of range [%d, %d]", v, notFoundRetryUnknownMinSeconds, notFoundRetryUnknownMaxSeconds)
@@ -142,10 +141,7 @@ func TestRetryAfterSHistogram(t *testing.T) {
barWidth := 60
for i, c := range buckets {
lo := i*bucketSize + 1
hi := (i + 1) * bucketSize
if hi > notFoundRetryUnknownMaxSeconds {
hi = notFoundRetryUnknownMaxSeconds
}
hi := min((i+1)*bucketSize, notFoundRetryUnknownMaxSeconds)
bar := ""
if maxCount > 0 {
bar = strings.Repeat("#", c*barWidth/maxCount)
@@ -156,8 +152,7 @@ func TestRetryAfterSHistogram(t *testing.T) {
func BenchmarkAPIRequests(b *testing.B) {
db := newInMemoryStore(b.TempDir(), 0, nil)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ctx := b.Context()
go db.Serve(ctx)
api := newAPISrv("127.0.0.1:0", tls.Certificate{}, db, nil, true, true, 1000, 1000)
srv := httptest.NewServer(http.HandlerFunc(api.handler))
+4 -4
View File
@@ -18,7 +18,7 @@ import (
var (
outboxesMut = sync.RWMutex{}
outboxes = make(map[syncthingprotocol.DeviceID]chan interface{})
outboxes = make(map[syncthingprotocol.DeviceID]chan any)
numConnections atomic.Int64
)
@@ -97,9 +97,9 @@ func protocolConnectionHandler(tcpConn net.Conn, config *tls.Config, token strin
id := syncthingprotocol.NewDeviceID(certs[0].Raw)
messages := make(chan interface{})
messages := make(chan any)
errors := make(chan error, 1)
outbox := make(chan interface{})
outbox := make(chan any)
// Read messages from the connection and send them on the messages
// channel. When there is an error, send it on the error channel and
@@ -364,7 +364,7 @@ func sessionConnectionHandler(conn net.Conn) {
}
}
func messageReader(conn net.Conn, messages chan<- interface{}, errors chan<- error) {
func messageReader(conn net.Conn, messages chan<- any, errors chan<- error) {
numConnections.Add(1)
defer numConnections.Add(-1)
+1 -4
View File
@@ -330,10 +330,7 @@ func take(tokens int, ls ...*rate.Limiter) {
for tokens > 0 {
// chunk is how many tokens we can consume at a time
chunk := tokens
if chunk > minBurst {
chunk = minBurst
}
chunk := min(tokens, minBurst)
// maxDelay is the longest delay mandated by any of the limiters for
// the chosen chunk size.
+2 -2
View File
@@ -38,7 +38,7 @@ func statusService(addr string) {
func getStatus(w http.ResponseWriter, _ *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
status := make(map[string]interface{})
status := make(map[string]any)
sessionMut.Lock()
// This can potentially be double the number of pending sessions, as each session has two keys, one for each side.
@@ -67,7 +67,7 @@ func getStatus(w http.ResponseWriter, _ *http.Request) {
rc.rate(30*60/10) * 8 / 1000,
rc.rate(60*60/10) * 8 / 1000,
}
status["options"] = map[string]interface{}{
status["options"] = map[string]any{
"network-timeout": networkTimeout / time.Second,
"ping-interval": pingInterval / time.Second,
"message-timeout": messageTimeout / time.Second,
+3 -3
View File
@@ -27,7 +27,7 @@ import (
type APIClient interface {
Get(url string) (*http.Response, error)
Post(url, body string) (*http.Response, error)
PutJSON(url string, o interface{}) (*http.Response, error)
PutJSON(url string, o any) (*http.Response, error)
}
type apiClient struct {
@@ -134,7 +134,7 @@ func (c *apiClient) RequestString(url, method, data string) (*http.Response, err
return c.Request(url, method, bytes.NewBufferString(data))
}
func (c *apiClient) RequestJSON(url, method string, o interface{}) (*http.Response, error) {
func (c *apiClient) RequestJSON(url, method string, o any) (*http.Response, error) {
data, err := json.Marshal(o)
if err != nil {
return nil, err
@@ -150,7 +150,7 @@ func (c *apiClient) Post(url, body string) (*http.Response, error) {
return c.RequestString(url, "POST", body)
}
func (c *apiClient) PutJSON(url string, o interface{}) (*http.Response, error) {
func (c *apiClient) PutJSON(url string, o any) (*http.Response, error) {
return c.RequestJSON(url, "PUT", o)
}
+1 -1
View File
@@ -81,7 +81,7 @@ func (c *configCommand) Run(ctx Context, outerCtx *kong.Context) error {
app.Name = "syncthing cli config"
app.HelpName = "syncthing cli config"
app.Description = outerCtx.Selected().Help
app.Metadata = map[string]interface{}{
app.Metadata = map[string]any{
"clientFactory": ctx.clientFactory,
}
app.CustomAppHelpTemplate = customAppHelpTemplate
+2 -2
View File
@@ -112,7 +112,7 @@ func getConfig(c APIClient) (config.Configuration, error) {
return cfg, nil
}
func prettyPrintJSON(data interface{}) error {
func prettyPrintJSON(data any) error {
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
return enc.Encode(data)
@@ -123,7 +123,7 @@ func prettyPrintResponse(response *http.Response) error {
if err != nil {
return err
}
var data interface{}
var data any
if err := json.Unmarshal(bytes, &data); err != nil {
return err
}
+1 -1
View File
@@ -125,7 +125,7 @@ func uploadPanicLog(ctx context.Context, urlBase, file string) error {
func filterLogLines(data []byte) []byte {
filtered := data[:0]
matched := false
for _, line := range bytes.Split(data, []byte("\n")) {
for line := range bytes.SplitSeq(data, []byte("\n")) {
switch {
case !matched && bytes.HasPrefix(line, []byte("Panic ")):
// This begins the panic trace, set the matched flag and append.
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build !windows
// +build !windows
package main
+55 -35
View File
@@ -313,21 +313,6 @@ func (c *serveCmd) Run() error {
return nil
}
func openGUI() error {
cfg, err := loadOrDefaultConfig()
if err != nil {
return err
}
if guiCfg := cfg.GUI(); guiCfg.Enabled {
if err := openURL(guiCfg.URL()); err != nil {
return err
}
} else {
slog.Error("Browser: GUI is currently disabled")
}
return nil
}
func logPackages() string {
packages := slogutil.PackageDescrs()
@@ -418,6 +403,28 @@ func upgradeViaRest() error {
}
func (c *serveCmd) syncthingMain() {
// Ensure we are the only running instance
lf := flock.New(locations.Get(locations.LockFile))
locked, err := lf.TryLock()
switch {
case err != nil:
slog.Error("Failed to acquire lock", slogutil.Error(err))
os.Exit(svcutil.ExitError.AsInt())
case !locked && c.NoBrowser:
slog.Error("Failed to acquire lock: is another Syncthing instance already running?")
os.Exit(svcutil.ExitError.AsInt())
case !locked:
slog.Info("Seems to already be running, launching GUI instead (use --no-browser to prevent)")
cmd := browserCmd{Verify: true}
if err := cmd.Run(); err != nil {
slog.Error("Failed to open browser", slogutil.Error(err))
os.Exit(svcutil.ExitNoRestart.AsInt())
}
return
}
if c.DebugProfileBlock {
startBlockProfiler()
}
@@ -436,18 +443,7 @@ func (c *serveCmd) syncthingMain() {
)
if err != nil {
slog.Error("Failed to load/generate certificate", slogutil.Error(err))
os.Exit(1)
}
// Ensure we are the only running instance
lf := flock.New(locations.Get(locations.LockFile))
locked, err := lf.TryLock()
if err != nil {
slog.Error("Failed to acquire lock", slogutil.Error(err))
os.Exit(1)
} else if !locked {
slog.Error("Failed to acquire lock: is another Syncthing instance already running?")
os.Exit(1)
os.Exit(svcutil.ExitError.AsInt())
}
ctx, cancel := context.WithCancel(context.Background())
@@ -502,13 +498,13 @@ func (c *serveCmd) syncthingMain() {
if err := syncthing.TryMigrateDatabase(ctx, c.DBDeleteRetentionInterval); err != nil {
slog.Error("Failed to migrate old-style database", slogutil.Error(err))
os.Exit(1)
os.Exit(svcutil.ExitError.AsInt())
}
sdb, err := syncthing.OpenDatabase(locations.Get(locations.Database), c.DBDeleteRetentionInterval)
if err != nil {
slog.Error("Error opening database", slogutil.Error(err))
os.Exit(1)
os.Exit(svcutil.ExitError.AsInt())
}
if c.DebugPerfStats {
@@ -915,7 +911,7 @@ func (u upgradeCmd) Run() error {
switch {
case err != nil && !os.IsNotExist(err):
slog.Error("Failed to lock for upgrade", slogutil.Error(err))
os.Exit(1)
os.Exit(svcutil.ExitError.AsInt())
case locked || os.IsNotExist(err):
// We got the lock, or the config directory didn't exist, so we
// can do a direct upgrade
@@ -935,14 +931,38 @@ func (u upgradeCmd) Run() error {
return nil
}
type browserCmd struct{}
type browserCmd struct {
Verify bool `help:"Verify that the GUI is reachable before launching browser"`
}
func (browserCmd) Run() error {
if err := openGUI(); err != nil {
slog.Error("Failed to open web UI", slogutil.Error(err))
func (c browserCmd) Run() error {
cfg, err := loadOrDefaultConfig()
if err != nil {
return err
}
guiCfg := cfg.GUI()
if !guiCfg.Enabled {
slog.Error("Browser: GUI is currently disabled")
os.Exit(svcutil.ExitError.AsInt())
}
return nil
url := guiCfg.URL()
if c.Verify {
// Do an HTTP request to verify the GUI/API is up and available
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return err
}
_, err = http.DefaultClient.Do(req) //nolint:bodyclose // we're exiting in a millisecond
if err != nil {
slog.Error("GUI not available", slogutil.Error(err))
os.Exit(svcutil.ExitError.AsInt()) //nolint:gocritic // deferred cancel
}
}
return openURL(url)
}
type debugCmd struct {
+8 -3
View File
@@ -171,10 +171,11 @@ func (c *serveCmd) monitorMain() {
exiterr := &exec.ExitError{}
if errors.As(err, &exiterr) {
exitCode := exiterr.ExitCode()
if stopped || c.NoRestart {
switch {
case stopped || c.NoRestart:
os.Exit(exitCode)
}
if exitCode == svcutil.ExitUpgrade.AsInt() {
case exitCode == svcutil.ExitUpgrade.AsInt():
// Restart the monitor process to release the .old
// binary as part of the upgrade process.
slog.Info("Restarting monitor...")
@@ -182,6 +183,10 @@ func (c *serveCmd) monitorMain() {
slog.Error("Failed to restart monitor", slogutil.Error(err))
}
os.Exit(exitCode)
case exitCode == svcutil.ExitNoRestart.AsInt():
// Requested to not restart the child
os.Exit(exitCode)
}
}
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build !windows
// +build !windows
package main
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build windows
// +build windows
package main
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build !solaris && !windows
// +build !solaris,!windows
package main
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build solaris || windows
// +build solaris windows
package main
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build go1.7
// +build go1.7
package main
+2
View File
@@ -125,6 +125,8 @@
"Discovery Status": "حالة الاكتشاف",
"Dismiss": "رفض",
"Do not add it to the ignore list, so this notification may recur.": "لا تقم بإضافته إلى قائمة التجاهل، لذلك قد يتكرر هذا الإشعار.",
"Do not add it to the ignore list, so this notification will reappear if the device connects again.": "لا تقم بإضافته إلى قائمة التجاهل، حتى يتم اخطارك عند اعادة اتصال الجهاز.",
"Do not add it to the ignore list, so this notification will reappear if the device offering this folder connects again.": "لا تقم بإضافته إلى قائمة التجاهل، حتى يتم اخطارك عند اعادة اتصال الجهاز المزوّد لهذا المجلد.",
"Do not restore": "الغاء الاستعادة",
"Do not restore all": "الغاء استعادة الكل",
"Do you want to enable watching for changes for all your folders?": "هل تريد تفعيل مراقبة التغيرات على كل المجلدات؟",
+2
View File
@@ -125,6 +125,8 @@
"Discovery Status": "Discovery Status",
"Dismiss": "Dismiss",
"Do not add it to the ignore list, so this notification may recur.": "Do not add it to the ignore list, so this notification may recur.",
"Do not add it to the ignore list, so this notification will reappear if the device connects again.": "Do not add it to the ignore list, so this notification will reappear if the device connects again.",
"Do not add it to the ignore list, so this notification will reappear if the device offering this folder connects again.": "Do not add it to the ignore list, so this notification will reappear if the device offering this folder connects again.",
"Do not restore": "Do not restore",
"Do not restore all": "Do not restore all",
"Do you want to enable watching for changes for all your folders?": "Do you want to enable watching for changes for all your folders?",
+3
View File
@@ -125,6 +125,8 @@
"Discovery Status": "탐지 현황",
"Dismiss": "나중에",
"Do not add it to the ignore list, so this notification may recur.": "무시 항목에 추가되지 않으니 이 알림이 다시 표시될 수 있습니다.",
"Do not add it to the ignore list, so this notification will reappear if the device connects again.": "무시 항목에 추가되지 않으니 해당 기기가 다시 연결되면 이 알림은 다시 표시될 것입니다.",
"Do not add it to the ignore list, so this notification will reappear if the device offering this folder connects again.": "무시 항목에 추가되지 않으니 이 폴더를 공유하는 기가가 연결되면 이 알림은 다시 표시될 것입니다.",
"Do not restore": "복구하지 않기",
"Do not restore all": "모두 복구하지 않기",
"Do you want to enable watching for changes for all your folders?": "변경 항목 감시를 모든 폴더에서 활성화하시겠습니까?",
@@ -395,6 +397,7 @@
"Staggered": "시차제",
"Staggered File Versioning": "시차제 파일 버전 관리",
"Start Browser": "브라우저 열기",
"Starting": "시작 중",
"Statistics": "통계",
"Stay logged in": "로그인 상태 유지",
"Stopped": "중지됨",
+2 -2
View File
@@ -125,8 +125,8 @@
"Discovery Status": "Stan odnajdywania",
"Dismiss": "Odrzuć",
"Do not add it to the ignore list, so this notification may recur.": "Nie dodaje do listy ignorowanych, więc powiadomienie to może się powtórzyć.",
"Do not add it to the ignore list, so this notification will reappear if the device connects again.": "Nie dodaje do listy ignorowanych, więc powiadomienie pojawi się ponownie, gdy urządzenie połączy się ponownie.",
"Do not add it to the ignore list, so this notification will reappear if the device offering this folder connects again.": "Nie dodaje do listy ignorowanych, więc powiadomienie pojawi się ponownie, gdy urządzenie oferujące ten folder ponownie się połączy.",
"Do not add it to the ignore list, so this notification will reappear if the device connects again.": "Nie dodaje do listy ignorowanych, więc powiadomienie pojawi się ponownie, gdy urządzenie po raz kolejny się połączy.",
"Do not add it to the ignore list, so this notification will reappear if the device offering this folder connects again.": "Nie dodaje do listy ignorowanych, więc powiadomienie pojawi się ponownie, gdy urządzenie oferujące ten folder po raz kolejny się połączy.",
"Do not restore": "Nie przywracaj",
"Do not restore all": "Nie przywracaj wszystkich",
"Do you want to enable watching for changes for all your folders?": "Czy chcesz włączyć obserwowanie zmian we wszystkich folderach?",
+2 -1
View File
@@ -137,7 +137,7 @@
"Edit Device Defaults": "Изменить настройки устройств",
"Edit Folder": "Редактирование папки",
"Edit Folder Defaults": "Изменить настройки папок",
"Editing {%path%}.": "Правка {{path}}.",
"Editing {%path%}.": "Редактирование {{path}}.",
"Enable Crash Reporting": "Включить отчёты о сбоях",
"Enable NAT traversal": "Использовать обход NAT",
"Enable Relaying": "Использовать ретрансляторы",
@@ -396,6 +396,7 @@
"Staggered": "Ступенчато",
"Staggered File Versioning": "Ступенчатое управление версиями файлов",
"Start Browser": "Запускать браузер",
"Starting": "Запуск",
"Statistics": "Статистика",
"Stay logged in": "Оставаться в системе",
"Stopped": "Остановлено",
-3
View File
@@ -1017,9 +1017,6 @@
</div> <!-- /row -->
</div> <!-- /container -->
<footer class="container text-center text-muted small" aria-label="Custom build marker">
It syncs .stignore now!
</footer>
</div> <!-- /ng-cloak -->
<ng-include src="'syncthing/core/networkErrorDialogView.html'"></ng-include>
+1 -1
View File
@@ -313,7 +313,7 @@ nextScript:
// files on lines containing only a semicolon and execute them
// separately. We require it on a separate line because there are
// also statement-internal semicolons in the triggers.
for _, stmt := range strings.Split(string(bs), "\n;") {
for stmt := range strings.SplitSeq(string(bs), "\n;") {
if _, err := tx.Exec(s.expandTemplateVars(stmt)); err != nil {
if strings.Contains(stmt, "syncthing:ignore-failure") {
// We're ok with this failing. Just note it.
+1 -4
View File
@@ -474,10 +474,7 @@ func (s *folderDB) recalcGlobalForFile(txp *txPreparedStmts, file string) error
// The global version is the first one in the list that is not invalid,
// or just the first one in the list if all are invalid.
var global fileRow
globIdx := slices.IndexFunc(es, func(e fileRow) bool { return !e.IsInvalid() })
if globIdx < 0 {
globIdx = 0
}
globIdx := max(slices.IndexFunc(es, func(e fileRow) bool { return !e.IsInvalid() }), 0)
global = es[globIdx]
// We "have" the file if the position in the list of versions is at the
+2 -2
View File
@@ -41,8 +41,8 @@ func SetDefaultLevel(level slog.Level) {
}
func SetLevelOverrides(sttrace string) {
pkgs := strings.Split(sttrace, ",")
for _, pkg := range pkgs {
pkgs := strings.SplitSeq(sttrace, ",")
for pkg := range pkgs {
pkg = strings.TrimSpace(pkg)
if pkg == "" {
continue
+2 -2
View File
@@ -45,11 +45,11 @@ type adapter struct {
l *slog.Logger
}
func (a adapter) Debugln(vals ...interface{}) {
func (a adapter) Debugln(vals ...any) {
a.log(strings.TrimSpace(fmt.Sprintln(vals...)), slog.LevelDebug)
}
func (a adapter) Debugf(format string, vals ...interface{}) {
func (a adapter) Debugf(format string, vals ...any) {
a.log(fmt.Sprintf(format, vals...), slog.LevelDebug)
}
+28 -20
View File
@@ -202,7 +202,7 @@ func (s *service) getListener(guiCfg config.GUIConfiguration) (net.Listener, err
return listener, nil
}
func sendJSON(w http.ResponseWriter, jsonObject interface{}) {
func sendJSON(w http.ResponseWriter, jsonObject any) {
w.Header().Set("Content-Type", "application/json")
// Marshalling might fail, in which case we should return a 500 with the
// actual error.
@@ -696,7 +696,7 @@ func (*service) getSystemPaths(w http.ResponseWriter, _ *http.Request) {
}
func (s *service) getJSMetadata(w http.ResponseWriter, _ *http.Request) {
meta, _ := json.Marshal(map[string]interface{}{
meta, _ := json.Marshal(map[string]any{
"deviceID": s.id.String(),
"deviceIDShort": s.id.Short().String(),
"authenticated": true,
@@ -706,7 +706,7 @@ func (s *service) getJSMetadata(w http.ResponseWriter, _ *http.Request) {
}
func (*service) getSystemVersion(w http.ResponseWriter, _ *http.Request) {
sendJSON(w, map[string]interface{}{
sendJSON(w, map[string]any{
"version": build.Version,
"codename": build.Codename,
"longVersion": build.LongVersion,
@@ -838,7 +838,7 @@ func (s *service) getDBNeed(w http.ResponseWriter, r *http.Request) {
}
// Convert the struct to a more loose structure, and inject the size.
sendJSON(w, map[string]interface{}{
sendJSON(w, map[string]any{
"progress": toJsonFileInfoSlice(progress),
"queued": toJsonFileInfoSlice(queued),
"rest": toJsonFileInfoSlice(rest),
@@ -866,7 +866,7 @@ func (s *service) getDBRemoteNeed(w http.ResponseWriter, r *http.Request) {
return
}
sendJSON(w, map[string]interface{}{
sendJSON(w, map[string]any{
"files": toJsonFileInfoSlice(files),
"page": page,
"perpage": perpage,
@@ -886,7 +886,7 @@ func (s *service) getDBLocalChanged(w http.ResponseWriter, r *http.Request) {
return
}
sendJSON(w, map[string]interface{}{
sendJSON(w, map[string]any{
"files": toJsonFileInfoSlice(files),
"page": page,
"perpage": perpage,
@@ -951,7 +951,7 @@ func (s *service) getDBFile(w http.ResponseWriter, r *http.Request) {
return
}
sendJSON(w, map[string]interface{}{
sendJSON(w, map[string]any{
"global": jsonFileInfo(gf),
"local": jsonFileInfo(lf),
"availability": av,
@@ -967,7 +967,7 @@ func (s *service) getDebugFile(w http.ResponseWriter, r *http.Request) {
gf, _, _ := s.model.CurrentGlobalFile(folder, file)
av, _ := s.model.Availability(folder, protocol.FileInfo{Name: file}, protocol.BlockInfo{})
sendJSON(w, map[string]interface{}{
sendJSON(w, map[string]any{
"global": jsonFileInfo(gf),
"local": jsonFileInfo(lf),
"availability": av,
@@ -1037,7 +1037,7 @@ func (s *service) getSystemStatus(w http.ResponseWriter, _ *http.Request) {
runtime.ReadMemStats(&m)
tilde, _ := fs.ExpandTilde("~")
res := make(map[string]interface{})
res := make(map[string]any)
res["myID"] = s.id.String()
res["goroutines"] = runtime.NumGoroutine()
res["alloc"] = m.Alloc
@@ -1184,10 +1184,7 @@ func (s *service) getSupportBundle(w http.ResponseWriter, r *http.Request) {
}
// Metrics data as text
var metricsBuf bytes.Buffer
wr := bufferedResponseWriter{Writer: &metricsBuf}
promhttp.Handler().ServeHTTP(wr, &http.Request{Method: http.MethodGet})
files = append(files, fileEntry{name: "metrics.txt", data: metricsBuf.Bytes()})
files = append(files, fileEntry{name: "metrics.txt", data: prometheusMetrics()})
// Connection data as JSON
connStats := s.model.ConnectionStats()
@@ -1258,6 +1255,16 @@ func (s *service) getSupportBundle(w http.ResponseWriter, r *http.Request) {
io.Copy(w, &zipFilesBuffer)
}
func prometheusMetrics() []byte {
var metricsBuf bytes.Buffer
wr := bufferedResponseWriter{Writer: &metricsBuf}
promhttp.Handler().ServeHTTP(wr, &http.Request{
Method: http.MethodGet,
URL: &url.URL{Scheme: "http://", Host: "localhost", Path: "/metrics"},
})
return metricsBuf.Bytes()
}
func (s *service) getSystemDiscovery(w http.ResponseWriter, _ *http.Request) {
devices := make(map[string]discover.CacheEntry)
@@ -1302,7 +1309,7 @@ func (s *service) getDBIgnores(w http.ResponseWriter, r *http.Request) {
folder := qs.Get("folder")
lines, patterns, err := s.model.LoadIgnores(folder)
sendJSON(w, map[string]interface{}{
sendJSON(w, map[string]any{
"ignore": lines,
"expanded": patterns,
"error": errorString(err),
@@ -1411,7 +1418,7 @@ func (s *service) getSystemUpgrade(w http.ResponseWriter, _ *http.Request) {
httpError(w, err)
return
}
res := make(map[string]interface{})
res := make(map[string]any)
res["running"] = build.Version
res["latest"] = rel.Tag
res["newer"] = upgrade.CompareVersions(rel.Tag, build.Version) == upgrade.Newer
@@ -1439,7 +1446,7 @@ func (*service) getDeviceID(w http.ResponseWriter, r *http.Request) {
func (*service) getLang(w http.ResponseWriter, r *http.Request) {
lang := r.Header.Get("Accept-Language")
weights := make(map[string]float64)
for _, l := range strings.Split(lang, ",") {
for l := range strings.SplitSeq(lang, ",") {
parts := strings.SplitN(l, ";", 2)
code := strings.ToLower(strings.TrimSpace(parts[0]))
weights[code] = 1.0
@@ -1638,7 +1645,7 @@ func (s *service) getFolderErrors(w http.ResponseWriter, r *http.Request) {
}
}
sendJSON(w, map[string]interface{}{
sendJSON(w, map[string]any{
"folder": folder,
"errors": errors,
"page": page,
@@ -1770,8 +1777,8 @@ func (f jsonFileInfo) MarshalJSON() ([]byte, error) {
return json.Marshal(m)
}
func fileIntfJSONMap(f protocol.FileInfo) map[string]interface{} {
out := map[string]interface{}{
func fileIntfJSONMap(f protocol.FileInfo) map[string]any {
out := map[string]any{
"name": f.FileName(),
"type": f.FileType().String(),
"size": f.Size,
@@ -1946,7 +1953,8 @@ func sanitizedHostname(name string) (string, error) {
return r > unicode.MaxASCII ||
!unicode.IsLetter(r) && !unicode.IsNumber(r) &&
r != '.' && r != '-'
})))
})),
)
name, _, err := transform.String(t, name)
if err != nil {
return "", err
+1 -4
View File
@@ -311,10 +311,7 @@ func authLDAP(username string, password string, cfg config.LDAPConfiguration) bo
func formatOptionalPercentS(template string, username string) string {
var replacements []any
nReps := strings.Count(template, "%s") - strings.Count(template, "%%s")
if nReps < 0 {
nReps = 0
}
nReps := max(strings.Count(template, "%s")-strings.Count(template, "%%s"), 0)
for range nReps {
replacements = append(replacements, username)
}
+9 -2
View File
@@ -433,7 +433,6 @@ func TestAPIServiceRequests(t *testing.T) {
}
for _, tc := range cases {
tc := tc
t.Run(tc.URL, func(t *testing.T) {
t.Parallel()
testHTTPRequest(t, baseURL, tc, testAPIKey)
@@ -1723,7 +1722,7 @@ func TestConfigChanges(t *testing.T) {
return resp
}
mod := func(method, path string, data interface{}) {
mod := func(method, path string, data any) {
t.Helper()
bs, err := json.Marshal(data)
if err != nil {
@@ -1830,6 +1829,14 @@ func TestSanitizedHostname(t *testing.T) {
}
}
func TestPrometheusMetrics(t *testing.T) {
// We should get some form of reasonable metrics response
bs := prometheusMetrics()
if !bytes.Contains(bs, []byte("TYPE go_info gauge")) {
t.Error("metrics should include go_info gauge")
}
}
// runningInContainer returns true if we are inside Docker or LXC. It might
// be prone to false negatives if things change in the future, but likely
// not false positives.
-42
View File
@@ -1,42 +0,0 @@
// Copyright (C) 2026 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 auto
import (
"compress/gzip"
"io"
"strings"
"testing"
)
const customBuildMarker = "It syncs .stignore now!"
func TestCustomBuildMarkerIsEmbedded(t *testing.T) {
asset, ok := Assets()["default/index.html"]
if !ok {
t.Fatal("default/index.html is missing from embedded GUI assets")
}
content := asset.Content
if asset.Gzipped {
reader, err := gzip.NewReader(strings.NewReader(asset.Content))
if err != nil {
t.Fatal(err)
}
defer reader.Close()
data, err := io.ReadAll(reader)
if err != nil {
t.Fatal(err)
}
content = string(data)
}
if !strings.Contains(content, customBuildMarker) {
t.Fatalf("embedded GUI assets do not contain custom build marker %q", customBuildMarker)
}
}
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build noassets
// +build noassets
package auto
+1 -1
View File
@@ -441,7 +441,7 @@ func (c *configMuxBuilder) adjustLDAP(w http.ResponseWriter, r *http.Request, ld
}
// Unmarshals the content of the given body and stores it in to (i.e. to must be a pointer).
func unmarshalTo(body io.ReadCloser, to interface{}) error {
func unmarshalTo(body io.ReadCloser, to any) error {
bs, err := io.ReadAll(body)
body.Close()
if err != nil {
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build race
// +build race
package build
+2 -2
View File
@@ -664,7 +664,7 @@ func (defaults *Defaults) prepare(myID protocol.DeviceID, existingDevices map[pr
defaults.Device.prepare(nil)
}
func ensureZeroForNodefault(empty interface{}, target interface{}) {
func ensureZeroForNodefault(empty any, target any) {
copyMatchingTag(empty, target, "nodefault", func(v string) bool {
if len(v) > 0 && v != "true" {
panic(fmt.Sprintf(`unexpected tag value: %s. expected untagged or "true"`, v))
@@ -674,7 +674,7 @@ func ensureZeroForNodefault(empty interface{}, target interface{}) {
}
// copyMatchingTag copies fields tagged tag:"value" from "from" struct onto "to" struct.
func copyMatchingTag(from interface{}, to interface{}, tag string, shouldCopy func(value string) bool) {
func copyMatchingTag(from any, to any, tag string, shouldCopy func(value string) bool) {
fromStruct := reflect.ValueOf(from).Elem()
fromType := fromStruct.Type()
+6 -6
View File
@@ -1158,8 +1158,8 @@ func TestInvalidDeviceIDRejected(t *testing.T) {
// Change the device ID of the first device to "invalid". Fast and loose
// with the type assertions as we know what the JSON decoder returns.
devs := cfg["devices"].([]interface{})
dev0 := devs[0].(map[string]interface{})
devs := cfg["devices"].([]any)
dev0 := devs[0].(map[string]any)
dev0["deviceID"] = tc.id
devs[0] = dev0
@@ -1197,8 +1197,8 @@ func TestInvalidFolderIDRejected(t *testing.T) {
// Change the folder ID of the first folder to the empty string.
// Fast and loose with the type assertions as we know what the JSON
// decoder returns.
devs := cfg["folders"].([]interface{})
dev0 := devs[0].(map[string]interface{})
devs := cfg["folders"].([]any)
dev0 := devs[0].(map[string]any)
dev0["id"] = tc.id
devs[0] = dev0
@@ -1324,7 +1324,7 @@ func adjustFolderConfiguration(cfg *FolderConfiguration, id, label string, fsTyp
// defaultConfigAsMap returns a valid default config as a JSON-decoded
// map[string]interface{}. This is useful to override random elements and
// re-encode into JSON.
func defaultConfigAsMap() map[string]interface{} {
func defaultConfigAsMap() map[string]any {
cfg := New(device1)
dev := cfg.Defaults.Device.Copy()
adjustDeviceConfiguration(&dev, device2, "name")
@@ -1337,7 +1337,7 @@ func defaultConfigAsMap() map[string]interface{} {
// can't happen
panic(err)
}
var tmp map[string]interface{}
var tmp map[string]any
if err := json.Unmarshal(bs, &tmp); err != nil {
// can't happen
panic(err)
+2 -3
View File
@@ -9,6 +9,7 @@ package config
import (
"encoding/json"
"encoding/xml"
"maps"
"slices"
"strings"
@@ -45,9 +46,7 @@ type internalParam struct {
func (c VersioningConfiguration) Copy() VersioningConfiguration {
cp := c
cp.Params = make(map[string]string, len(c.Params))
for k, v := range c.Params {
cp.Params[k] = v
}
maps.Copy(cp.Params, c.Params)
return cp
}
+2 -2
View File
@@ -401,7 +401,7 @@ func TestConnectionEstablishment(t *testing.T) {
}
}
func withConnectionPair(b interface{ Fatal(...interface{}) }, connUri string, h func(client, server internalConn)) {
func withConnectionPair(b interface{ Fatal(...any) }, connUri string, h func(client, server internalConn)) {
// Root of the service tree.
supervisor := suture.New("main", suture.Spec{
PassThroughPanics: true,
@@ -494,7 +494,7 @@ func withConnectionPair(b interface{ Fatal(...interface{}) }, connUri string, h
_ = serverConn.Close()
}
func mustGetCert(b interface{ Fatal(...interface{}) }) tls.Certificate {
func mustGetCert(b interface{ Fatal(...any) }) tls.Certificate {
cert, err := tlsutil.NewCertificateInMemory("bench", 10)
if err != nil {
b.Fatal(err)
+2 -2
View File
@@ -54,7 +54,7 @@ func TestDialQueueSort(t *testing.T) {
var seen1, seen2 int
for i := 0; i < 100; i++ {
for range 100 {
queue.Sort()
res := shortDevices(queue)
if reflect.DeepEqual(res, expected1) {
@@ -90,7 +90,7 @@ func TestDialQueueSort(t *testing.T) {
var seen1, seen2 int
for i := 0; i < 100; i++ {
for range 100 {
queue.Sort()
res := shortDevices(queue)
if reflect.DeepEqual(res, expected1) {
+5 -9
View File
@@ -256,18 +256,14 @@ func (w *limitedWriter) Write(buf []byte) (int, error) {
// try to be a bit adaptable. We range from the minimum write size of 1
// KiB up to the limiter burst size, aiming for about a write every
// 10ms.
singleWriteSize := int(w.waiter.Limit() / 100) // 10ms worth of data
singleWriteSize = ((singleWriteSize / 1024) + 1) * 1024 // round up to the next kibibyte
if singleWriteSize > limiterBurstSize {
singleWriteSize = limiterBurstSize
}
singleWriteSize := int(w.waiter.Limit() / 100) // 10ms worth of data
singleWriteSize = min(
// round up to the next kibibyte
((singleWriteSize/1024)+1)*1024, limiterBurstSize)
written := 0
for written < len(buf) {
toWrite := singleWriteSize
if toWrite > len(buf)-written {
toWrite = len(buf) - written
}
toWrite := min(singleWriteSize, len(buf)-written)
w.take(toWrite)
n, err := w.writer.Write(buf[written : written+toWrite])
written += n
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build go1.15 && !noquic
// +build go1.15,!noquic
package connections
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build !noquic
// +build !noquic
package connections
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build !noquic
// +build !noquic
package connections
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build noquic
// +build noquic
package connections
+6 -6
View File
@@ -18,23 +18,23 @@ import (
type Registry struct {
mut sync.Mutex
available map[string][]interface{}
available map[string][]any
}
func New() *Registry {
return &Registry{
available: make(map[string][]interface{}),
available: make(map[string][]any),
}
}
func (r *Registry) Register(scheme string, item interface{}) {
func (r *Registry) Register(scheme string, item any) {
r.mut.Lock()
defer r.mut.Unlock()
r.available[scheme] = append(r.available[scheme], item)
}
func (r *Registry) Unregister(scheme string, item interface{}) {
func (r *Registry) Unregister(scheme string, item any) {
r.mut.Lock()
defer r.mut.Unlock()
@@ -49,12 +49,12 @@ func (r *Registry) Unregister(scheme string, item interface{}) {
// Get returns an item for a schema compatible with the given scheme.
// If any item satisfies preferred, that has precedence over other items.
func (r *Registry) Get(scheme string, preferred func(interface{}) bool) interface{} {
func (r *Registry) Get(scheme string, preferred func(any) bool) any {
r.mut.Lock()
defer r.mut.Unlock()
var (
best interface{}
best any
bestPref bool
bestScheme string
)
+4 -4
View File
@@ -14,8 +14,8 @@ import (
func TestRegistry(t *testing.T) {
r := New()
want := func(i int) func(interface{}) bool {
return func(x interface{}) bool { return x.(int) == i }
want := func(i int) func(any) bool {
return func(x any) bool { return x.(int) == i }
}
if res := r.Get("int", want(1)); res != nil {
@@ -73,7 +73,7 @@ func TestShortSchemeFirst(t *testing.T) {
r.Register("foobar", 1)
// If we don't care about the value, we should get the one with "foo".
res := r.Get("foo", func(interface{}) bool { return false })
res := r.Get("foo", func(any) bool { return false })
if res != 0 {
t.Error("unexpected", res)
}
@@ -89,7 +89,7 @@ func BenchmarkGet(b *testing.B) {
b.ResetTimer()
for i := 0; i < b.N; i++ {
r.Get("tcp", func(x interface{}) bool {
r.Get("tcp", func(x any) bool {
return x.(*net.TCPAddr).IP.IsUnspecified()
})
}
+3 -4
View File
@@ -19,6 +19,7 @@ import (
"fmt"
"io"
"log/slog"
"maps"
"math"
"net"
"net/url"
@@ -817,7 +818,7 @@ func (s *service) createListener(factory listenerFactory, uri *url.URL) bool {
}
func (s *service) logListenAddressesChangedEvent(l ListenerAddresses) {
s.evLogger.Log(events.ListenAddressesChanged, map[string]interface{}{
s.evLogger.Log(events.ListenAddressesChanged, map[string]any{
"address": l.URI,
"lan": l.LANAddresses,
"wan": l.WANAddresses,
@@ -995,9 +996,7 @@ func newConnectionStatusHandler() connectionStatusHandler {
func (s *connectionStatusHandler) ConnectionStatus() map[string]ConnectionStatusEntry {
result := make(map[string]ConnectionStatusEntry)
s.connectionStatusMut.RLock()
for k, v := range s.connectionStatus {
result[k] = v
}
maps.Copy(result, s.connectionStatus)
s.connectionStatusMut.RUnlock()
return result
}
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build !solaris && !windows
// +build !solaris,!windows
package dialer
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build solaris
// +build solaris
package dialer
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build windows
// +build windows
package dialer
+1 -1
View File
@@ -111,7 +111,7 @@ func DialContextReusePortFunc(registry *registry.Registry) func(ctx context.Cont
return DialContext(ctx, network, addr)
}
localAddrInterface := registry.Get(network, func(addr interface{}) bool {
localAddrInterface := registry.Get(network, func(addr any) bool {
return addr.(*net.TCPAddr).IP.IsUnspecified()
})
if localAddrInterface == nil {
+2 -3
View File
@@ -7,6 +7,7 @@
package discover
import (
"maps"
"sync"
"time"
@@ -60,9 +61,7 @@ func (c *cache) Get(id protocol.DeviceID) (CacheEntry, bool) {
func (c *cache) Cache() map[protocol.DeviceID]CacheEntry {
c.mut.Lock()
m := make(map[protocol.DeviceID]CacheEntry, len(c.entries))
for k, v := range c.entries {
m[k] = v
}
maps.Copy(m, c.entries)
c.mut.Unlock()
return m
}
+1 -1
View File
@@ -294,7 +294,7 @@ func (c *localClient) registerDevice(src net.Addr, device *discoproto.Announce)
})
if isNewDevice {
c.evLogger.Log(events.DeviceDiscovered, map[string]interface{}{
c.evLogger.Log(events.DeviceDiscovered, map[string]any{
"device": id.String(),
"addrs": validAddresses,
})
+7 -7
View File
@@ -235,7 +235,7 @@ const BufferSize = 64
type Logger interface {
suture.Service
Log(t EventType, data interface{})
Log(t EventType, data any)
Subscribe(mask EventType) Subscription
}
@@ -253,10 +253,10 @@ type Event struct {
// Per-subscription sequential event ID. Named "id" for backwards compatibility with the REST API
SubscriptionID int `json:"id"`
// Global ID of the event across all subscriptions
GlobalID int `json:"globalID"`
Time time.Time `json:"time"`
Type EventType `json:"type"`
Data interface{} `json:"data"`
GlobalID int `json:"globalID"`
Time time.Time `json:"time"`
Type EventType `json:"type"`
Data any `json:"data"`
}
type Subscription interface {
@@ -325,7 +325,7 @@ loop:
return nil
}
func (l *logger) Log(t EventType, data interface{}) {
func (l *logger) Log(t EventType, data any) {
l.events <- Event{
Time: time.Now(), // intentionally high precision
Type: t,
@@ -559,7 +559,7 @@ var NoopLogger Logger = &noopLogger{}
func (*noopLogger) Serve(_ context.Context) error { return nil }
func (*noopLogger) Log(_ EventType, _ interface{}) {}
func (*noopLogger) Log(_ EventType, _ any) {}
func (*noopLogger) Subscribe(_ EventType) Subscription {
return &noopSubscription{}
+4 -4
View File
@@ -127,7 +127,7 @@ func TestBufferOverflow(t *testing.T) {
t0 := time.Now()
const nEvents = BufferSize * 2
for i := 0; i < nEvents; i++ {
for range nEvents {
l.Log(DeviceConnected, "foo")
}
if d := time.Since(t0); d > 15*time.Second {
@@ -237,7 +237,7 @@ func TestBufferedSub(t *testing.T) {
bs := NewBufferedSubscription(s, 10*BufferSize)
go func() {
for i := 0; i < 10*BufferSize; i++ {
for i := range 10 * BufferSize {
l.Log(DeviceConnected, fmt.Sprintf("event-%d", i))
if i%30 == 0 {
// Give the buffer routine time to pick up the events
@@ -378,7 +378,7 @@ func TestUnsubscribeContention(t *testing.T) {
stopListeners := make(chan struct{})
var listenerWg sync.WaitGroup
for i := 0; i < listeners; i++ {
for range listeners {
listenerWg.Go(func() {
s := l.Subscribe(AllEvents)
defer s.Unsubscribe()
@@ -400,7 +400,7 @@ func TestUnsubscribeContention(t *testing.T) {
stopSenders := make(chan struct{})
defer close(stopSenders)
var senderWg sync.WaitGroup
for i := 0; i < senders; i++ {
for range senders {
senderWg.Go(func() {
t := time.NewTicker(time.Millisecond)
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build linux
// +build linux
package fs
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build windows
// +build windows
package fs
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build linux
// +build linux
package fs
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build linux || solaris
// +build linux solaris
package fs
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build !windows
// +build !windows
package fs
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build linux || android
// +build linux android
package fs
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build !linux && !android && !windows
// +build !linux,!android,!windows
package fs
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build windows
// +build windows
package fs
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build !windows
// +build !windows
package fs
+1 -1
View File
@@ -580,7 +580,7 @@ func TestXattr(t *testing.T) {
// Create a set of random attributes that we will set and read back
var attrs []protocol.Xattr
for i := 0; i < 10; i++ {
for i := range 10 {
key := fmt.Sprintf("user.test-%d", i)
value := make([]byte, xattrSize())
rand.Read(value)
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build !windows
// +build !windows
package fs
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build linux
// +build linux
package fs
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build !linux
// +build !linux
package fs
@@ -5,7 +5,6 @@
// You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build darwin && !kqueue && cgo && !ios
// +build darwin,!kqueue,cgo,!ios
package fs
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build solaris && cgo
// +build solaris,cgo
package fs
@@ -5,7 +5,6 @@
// You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build linux
// +build linux
package fs
@@ -5,7 +5,6 @@
// You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build dragonfly || freebsd || netbsd || openbsd || ios || kqueue
// +build dragonfly freebsd netbsd openbsd ios kqueue
package fs
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build !linux && !windows && !dragonfly && !freebsd && !netbsd && !openbsd && !solaris && !darwin && !cgo && !ios
// +build !linux,!windows,!dragonfly,!freebsd,!netbsd,!openbsd,!solaris,!darwin,!cgo,!ios
// Catch all platforms that are not specifically handled to use the generic
// event types.
@@ -5,7 +5,6 @@
// You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build windows
// +build windows
package fs
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build !dragonfly && !freebsd && !netbsd && !openbsd && !kqueue && !ios
// +build !dragonfly,!freebsd,!netbsd,!openbsd,!kqueue,!ios
package fs
+2 -4
View File
@@ -5,7 +5,6 @@
// You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build (!solaris && !darwin) || (solaris && cgo) || (darwin && cgo)
// +build !solaris,!darwin solaris,cgo darwin,cgo
package fs
@@ -364,8 +363,7 @@ func TestWatchSymlinkedRoot(t *testing.T) {
linkedFs := NewFilesystem(FilesystemTypeBasic, filepath.Join(testFs.URI(), link))
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ctx := t.Context()
if _, _, err := linkedFs.Watch(".", fakeMatcher{}, ctx, false); err != nil {
panic(err)
}
@@ -635,6 +633,6 @@ func (fakeEventInfo) Event() notify.Event {
return notify.Write
}
func (fakeEventInfo) Sys() interface{} {
func (fakeEventInfo) Sys() any {
return nil
}
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at http://mozilla.org/MPL/2.0/.
//go:build (solaris && !cgo) || (darwin && !cgo) || (darwin && kqueue)
// +build solaris,!cgo darwin,!cgo darwin,kqueue
package fs
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build windows
// +build windows
package fs
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build freebsd || netbsd
// +build freebsd netbsd
package fs
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build linux || darwin
// +build linux darwin
package fs
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build !windows && !dragonfly && !illumos && !solaris && !openbsd
// +build !windows,!dragonfly,!illumos,!solaris,!openbsd
package fs
-1
View File
@@ -5,7 +5,6 @@
// You can obtain one at https://mozilla.org/MPL/2.0/.
//go:build windows || dragonfly || illumos || solaris || openbsd
// +build windows dragonfly illumos solaris openbsd
package fs
+1 -1
View File
@@ -1017,6 +1017,6 @@ func (f *fakeFileInfo) Group() int {
return f.gid
}
func (*fakeFileInfo) Sys() interface{} {
func (*fakeFileInfo) Sys() any {
return nil
}

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