Compare commits

..

99 Commits

Author SHA1 Message Date
Jakob Borg
9c3cee9ae4 Translation update 2015-03-11 21:15:55 +01:00
Jakob Borg
fc521b5f9d Protocol dep update 2015-03-11 21:13:17 +01:00
Audrius Butkevicius
5253368acc Merge pull request #1448 from calmh/xp
Fall back to %AppData% is %LocalAppData% is blank (fixes #1446)
2015-03-11 20:06:13 +00:00
Jakob Borg
51cfc3d4be Fall back to %AppData% is %LocalAppData% is blank (fixes #1446) 2015-03-11 21:04:10 +01:00
Audrius Butkevicius
80bffd93e7 Merge pull request #1447 from calmh/silencio
Don't yell about discovery listening and resolving (ref #1418)
2015-03-11 20:03:37 +00:00
Jakob Borg
df4f22e899 Don't yell about discovery listening and resolving (ref #1418) 2015-03-11 20:57:20 +01:00
Audrius Butkevicius
8cc70843a5 Merge pull request #1445 from calmh/compression
Compress only metadata by default (fixes #1374)
2015-03-11 19:35:15 +00:00
Jakob Borg
70c841f23a Compress only metadata by default (fixes #1374) 2015-03-11 19:10:57 +01:00
Jakob Borg
7b22e09805 Merge pull request #1438 from moshen/runit-reparenting
Fix syncthing process reparenting with runit
2015-03-10 08:21:41 +01:00
Jakob Borg
c5838c143c Merge pull request #1425 from AudriusButkevicius/laan
Allow not to limit bandwidth in LAN (fixes #1336)
2015-03-10 08:19:29 +01:00
Colin Kennedy
eaf71db7c9 Fix syncthing process reparenting with runit
When you: `sudo sv down /etc/service/syncthing/` the `TERM` signal
isn't propogated or trapped, so syncthing is orphaned and adopted by
init (PID 1).

- Changed call to `chpst` to `exec`
- Moved logging to `log/run` per `runsv` standard
2015-03-10 01:01:52 -05:00
Jakob Borg
b322b527b3 Add SVG versions of logo 2015-03-10 00:02:46 +01:00
Jakob Borg
cc4b231875 Merge pull request #1429 from moshen/runit-fix
Use chpst instead of djb name setuidgid
2015-03-09 23:50:08 +01:00
Jakob Borg
05642a3e17 Merge remote-tracking branch 'syncthing/pr/1436'
* syncthing/pr/1436:
  Remove red if we managed to report to atleast one discovery server (fixes #1427)

Conflicts:
	internal/auto/gui.files.go
2015-03-09 23:47:41 +01:00
Jakob Borg
7dcc6bb579 Add moshen 2015-03-09 23:45:03 +01:00
Audrius Butkevicius
f15c416e59 Remove red if we managed to report to atleast one discovery server (fixes #1427) 2015-03-09 21:55:14 +00:00
Audrius Butkevicius
6fa97eeec7 Allow not to limit bandwidth in LAN (fixes #1336) 2015-03-09 20:54:33 +00:00
Jakob Borg
03bbf273b3 Update protocol dep 2015-03-09 21:21:50 +01:00
Audrius Butkevicius
1e376cd3a6 Update assets 2015-03-09 12:13:43 +00:00
Audrius Butkevicius
49cf939c04 Add missing translation strings (fixes #1430) 2015-03-09 12:02:30 +00:00
Colin Kennedy
338394f8c3 Use chpst instead of djb name setuidgid
Some distros (Ubuntu, Debian?) don't link `chpst` to `setuidgid`, as it
could conflict with djb daemontools installation.  If daemontools isn't
going to be referenced in the README, then the example runit config
should reference the runit packaged utility.
2015-03-08 18:50:54 -05:00
Audrius Butkevicius
575b62d77b Merge pull request #1424 from AudriusButkevicius/scanner
Make sure we start scanning at an indexed location (fixes #1399)
2015-03-08 19:46:21 +00:00
Audrius Butkevicius
57fc0eb5b1 Make sure we start scanning at an indexed location (fixes #1399) 2015-03-08 19:45:47 +00:00
Jakob Borg
3a19fe3663 Merge pull request #1423 from AudriusButkevicius/warn
Silence discovery warnings when v6 not available
2015-03-08 19:50:35 +01:00
Audrius Butkevicius
0c049179b4 Silence discovery warnings when v6 not available (fixes #1418) 2015-03-08 16:49:12 +00:00
Jakob Borg
e22c873ec4 Repair integration tests 2015-03-08 08:41:43 +01:00
Jakob Borg
d644ebab09 Translation update 2015-03-08 07:52:52 +01:00
Jakob Borg
2fdc578a88 Update goleveldb (fixes #1414) 2015-03-08 07:49:02 +01:00
Jakob Borg
aeb3a3f7b5 Handle crap at the end of commit subjects 2015-03-07 22:50:54 +01:00
Audrius Butkevicius
044b7ce070 Merge pull request #1415 from calmh/announce-v6
Add global announce server on IPv6
2015-03-07 21:39:53 +00:00
Jakob Borg
815e538f10 Merge pull request #1401 from Zillode/fix-chmod-android
Fix chmod android
2015-03-07 21:42:44 +01:00
Lode Hoste
758233f001 Do not error when chmod failes when permissions are ignored (fixes #1404). 2015-03-07 21:38:16 +01:00
Jakob Borg
f4f4fda520 String slice uniquification must return a well defined order, or tests fail 2015-03-07 21:05:30 +01:00
Jakob Borg
1d77aeb69c Add global announce server on IPv6 2015-03-07 21:01:20 +01:00
Jakob Borg
29dbfc647d Add bencurthoys 2015-03-07 14:41:18 +01:00
Jakob Borg
55d9514e83 Merge pull request #1407 from bencurthoys/master
Windows Service Setup.exe
2015-03-07 14:38:43 +01:00
Jakob Borg
46bd7956a3 Merge branch 'pr-1410'
* pr-1410:
  Add test for osutil.InWritableDir
  Exit and error if the target is not a directory
2015-03-07 14:35:54 +01:00
Jakob Borg
e1ee394c26 Add test for osutil.InWritableDir 2015-03-07 14:35:29 +01:00
Lode Hoste
19884ade99 Exit and error if the target is not a directory 2015-03-06 22:02:29 +01:00
bencurthoys
f0a88061db Setup.exe for Windows
NSIS script to build setup file to install Syncthing and install as a service.
2015-03-06 15:30:50 +00:00
Audrius Butkevicius
6057138466 Merge pull request #1400 from calmh/negproto
Verify negotiated protocol bep/1.0
2015-03-05 15:16:59 +00:00
Jakob Borg
aaaa6556f3 Some commentary on the initial connection checks 2015-03-05 16:09:20 +01:00
Jakob Borg
4745431cda Verify negotiated protocol bep/1.0 2015-03-05 15:58:16 +01:00
Jakob Borg
0455a948a9 Merge pull request #1337 from AudriusButkevicius/fileslevels
Add /rest/tree API call
2015-03-05 15:24:51 +01:00
Audrius Butkevicius
bf3e249237 Add GlobalDirectoryTree benchmarks 2015-03-04 23:39:33 +00:00
Audrius Butkevicius
fb649e9525 Fix benchmarks, cleanup tests 2015-03-04 23:39:32 +00:00
Audrius Butkevicius
9d1e2d9f46 Add /rest/tree API call 2015-03-04 23:39:27 +00:00
Audrius Butkevicius
9876d93b60 Fix tests on Windows while running as a simple user 2015-03-04 22:39:33 +00:00
Jakob Borg
b3dd05580b Don't follow the prototype chain when looking for a folder name (fixes #1387) 2015-03-01 22:10:34 +01:00
Jakob Borg
32847f33fd Translation update 2015-03-01 21:51:56 +01:00
Jakob Borg
bff9723fe3 Merge branch 'fix-1373'
* fix-1373:
  fixup alterFiles
  Ensure progress when delete-by-rename fails (fixes #1373)
  Handle weird Lstat() returns for disappeared items (ref #1373)
  Alter files into directories and the other way around
2015-03-01 21:51:26 +01:00
Jakob Borg
d114648c16 fixup alterFiles 2015-03-01 21:38:04 +01:00
Jakob Borg
44d0da02d0 Ensure progress when delete-by-rename fails (fixes #1373) 2015-03-01 10:55:48 +01:00
Jakob Borg
c25107eff3 Handle weird Lstat() returns for disappeared items (ref #1373) 2015-03-01 10:55:43 +01:00
Jakob Borg
af5c36d2a8 Merge remote-tracking branch 'syncthing/pr/1373' into fix-1373
* syncthing/pr/1373:
  Alter files into directories and the other way around
2015-03-01 10:55:34 +01:00
Audrius Butkevicius
0828a67145 Merge pull request #1275 from calmh/kv-cache
Namespaced key value store and value cache
2015-02-26 13:51:43 +00:00
Jakob Borg
617fb84983 Also build Solaris, NetBSD by default 2015-02-26 12:21:20 +01:00
Jakob Borg
6f8ac2b61c Refactor: add and use db.NamespacedKV 2015-02-26 09:56:11 +01:00
Jakob Borg
6f2b4b96cf Refactor: use leveldb/util.BytesPrefix 2015-02-26 09:56:11 +01:00
Jakob Borg
3cc288a169 Build binary packages for Dragonfly 2015-02-26 08:58:58 +01:00
Jakob Borg
0bbbf3eb3b Compile on Dragonfly 2015-02-26 08:42:39 +01:00
Jakob Borg
4b1b56fee8 Reduce CPU usage (fixes #1376) 2015-02-25 23:30:24 +01:00
Jakob Borg
154fc59e93 Switch back to original kardianos/osext 2015-02-24 20:44:49 +01:00
Lode Hoste
218c4c128c Alter files into directories and the other way around 2015-02-23 12:12:31 +01:00
Audrius Butkevicius
53f1af0cab Merge pull request #1375 from calmh/fix-987
Attempt recovery of corrupted DB at startup (fixes #987)
2015-02-23 09:17:29 +00:00
Jakob Borg
f9577a38dc Attempt recovery of corrupted DB at startup (fixes #987) 2015-02-23 08:22:39 +01:00
Jakob Borg
fadc7d9ba5 Merge pull request #1366 from krozycki/master
All folder panels collapsed, fixes #1034
2015-02-20 10:18:50 +01:00
Jakob Borg
1e4b2133f6 Also handle ()| in glob patterns (fixes #1365) 2015-02-20 10:12:06 +01:00
Karol Różycki
bfefa6d016 All folder panels collapsed, fixes #1034 2015-02-19 15:48:43 +01:00
Jakob Borg
8b66472949 Fix sync benchmark for latest test changes 2015-02-19 13:15:51 +02:00
Jakob Borg
3b3aa94c4e Refactor out connection related functions to a separate file 2015-02-19 12:05:26 +02:00
Audrius Butkevicius
dc05275670 Merge pull request #1357 from calmh/truncate-v3
Simplify FileInfoTruncated
2015-02-19 10:04:53 +00:00
Jakob Borg
7921082ece Correctly handle ^ and $ in ignore patterns (fixes #1365) 2015-02-19 09:10:32 +02:00
Jakob Borg
efd6a29909 Translation update 2015-02-15 16:55:44 +01:00
Jakob Borg
88c44b303d Simplify FileInfoTruncated 2015-02-15 12:50:03 +01:00
Jakob Borg
e7dbb8ccdc Fix test for unknown flags 2015-02-15 09:51:39 +01:00
Jakob Borg
fe2a743c8d Protocol update 2015-02-15 09:41:32 +01:00
Audrius Butkevicius
1f5c124ac4 Merge pull request #1359 from krozycki/master
Typo fix. Fixes #1358
2015-02-14 15:17:45 +00:00
Karol Różycki
64a5bc038a Typo fix. Fixes #1358 2015-02-14 16:10:43 +01:00
Jakob Borg
5d9396334c Merge pull request #1311 from krozycki/master
Button to rescan all folders (fixes #1151)
2015-02-13 08:36:17 +01:00
Karol Różycki
ec160f1f0a Button to rescan all folders, fixes #1151 2015-02-12 21:03:35 +01:00
Jakob Borg
bbaeca96eb Merge pull request #1326 from rumpelsepp/systemd
Some systemd tweaks
2015-02-12 16:58:54 +01:00
Jakob Borg
3ab779895f Add tnn2 2015-02-12 12:14:56 +01:00
Jakob Borg
203c7360e7 Merge remote-tracking branch 'syncthing/pr/1332'
* syncthing/pr/1332:
  Implement memorySize() for NetBSD
2015-02-12 12:13:35 +01:00
Jakob Borg
a831f174ef Add marclaporte 2015-02-12 12:08:49 +01:00
Stefan Tatschner
153091f52f Some systemd tweaks
- Removed environment file to keep the service file minimal.
  "systemctl edit syncthing.service" does the job if somebody wants
  to customize the service.
- Changed "cmdline.target" to "default.target" as "cmdline.target"
  does not exist in systemd.special:
  http://www.freedesktop.org/software/systemd/man/systemd.special.html
- Added a missing "After=network.target".
- Added a documentation hint, thx @jaystrictor
2015-02-12 09:23:12 +01:00
Jakob Borg
35d3af5039 Use syncthing/build:latest for building 2015-02-11 10:17:15 +01:00
Jakob Borg
57e8cd6eab Regenerate assets 2015-02-11 09:35:08 +01:00
Jakob Borg
fc123a71af Merge pull request #1341 from AudriusButkevicius/configtest
Fix tests on Windows
2015-02-11 08:13:54 +01:00
Audrius Butkevicius
f14836cf02 Merge pull request #1346 from uok/match-icons
Match icons for ignore patterns
2015-02-10 21:42:53 +00:00
Ben Schulz
4178feb65f Match icons for ignore patterns 2015-02-10 21:38:56 +01:00
Audrius Butkevicius
2edaf22590 Merge pull request #1345 from calmh/smaller-batches
Reduce memory usage by writing smaller batches
2015-02-10 19:53:53 +00:00
Audrius Butkevicius
acd3dab957 Fix tests on Windows 2015-02-10 19:52:14 +00:00
Jakob Borg
6bbd74adcd Merge pull request #1321 from AudriusButkevicius/bitcheck
Refuse files with unknown bits set (fixes #1276)
2015-02-10 20:27:14 +01:00
Jakob Borg
9bb928bb38 Reduce memory usage by writing smaller batches 2015-02-10 20:24:25 +01:00
Jakob Borg
c87a6c5969 Use single filename for heap profiles 2015-02-10 19:50:27 +01:00
Jakob Borg
c482c13dcb Configurable heap profiling rate 2015-02-10 19:45:32 +01:00
Audrius Butkevicius
b87ed97402 Refuse files with unknown bits set (fixes #1276) 2015-02-09 23:32:33 +00:00
Tobias Nygren
078790bd0f Implement memorySize() for NetBSD 2015-02-05 20:31:25 +01:00
111 changed files with 3412 additions and 1141 deletions

View File

@@ -5,6 +5,7 @@ Alexander Graf <register-github@alex-graf.de>
Andrew Dunham <andrew@du.nham.ca>
Audrius Butkevicius <audrius.butkevicius@gmail.com>
Arthur Axel fREW Schmidt <frew@afoolishmanifesto.com> <frioux@gmail.com>
Ben Curthoys <ben@bencurthoys.com>
Ben Schulz <ueomkail@gmail.com> <uok@users.noreply.github.com>
Ben Sidhom <bsidhom@gmail.com>
Brandon Philips <brandon@ifup.org>
@@ -12,6 +13,7 @@ Brendan Long <self@brendanlong.com>
Caleb Callaway <enlightened.despot@gmail.com>
Cathryne Linenweaver <cathryne.linenweaver@gmail.com> <Cathryne@users.noreply.github.com>
Chris Joel <chris@scriptolo.gy>
Colin Kennedy <moshen.colin@gmail.com>
Daniel Martí <mvdan@mvdan.cc>
Dennis Wilson <dw@risu.io>
Dominik Heidler <dominik@heidler.eu>
@@ -27,6 +29,7 @@ Jochen Voss <voss@seehuhn.de>
Karol Różycki <rozycki.karol@gmail.com>
Lode Hoste <zillode@zillode.be>
Marcin Dziadus <dziadus.marcin@gmail.com>
Marc Laporte <marc@marclaporte.com>
Michael Jephcote <rewt0r@gmx.com> <Rewt0r@users.noreply.github.com>
Michael Tilli <pyfisch@gmail.com>
Peter Hoeg <peter@speartail.com>
@@ -36,6 +39,7 @@ Piotr Bejda <piotrb10@gmail.com>
Ryan Sullivan <kayoticsully@gmail.com>
Stefan Tatschner <stefan@sevenbyte.org>
Tim Abell <tim@timwise.co.uk>
Tobias Nygren <tnn@nygren.pp.se>
Tomas Cerveny <kozec@kozec.com>
Tully Robinson <tully@tojr.org>
Veeti Paananen <veeti.paananen@rojekti.fi>

12
Godeps/Godeps.json generated
View File

@@ -17,10 +17,6 @@
"ImportPath": "github.com/calmh/luhn",
"Rev": "0c8388ff95fa92d4094011e5a04fc99dea3d1632"
},
{
"ImportPath": "github.com/calmh/osext",
"Rev": "9bf61584e5f1f172e8766ddc9022d9c401faaa5e"
},
{
"ImportPath": "github.com/calmh/xdr",
"Rev": "ff948d7666c5e0fd18d398f6278881724d36a90b"
@@ -29,13 +25,17 @@
"ImportPath": "github.com/juju/ratelimit",
"Rev": "f9f36d11773655c0485207f0ad30dc2655f69d56"
},
{
"ImportPath": "github.com/kardianos/osext",
"Rev": "91292666f7e40f03185cdd1da7d85633c973eca7"
},
{
"ImportPath": "github.com/syncthing/protocol",
"Rev": "442e93d3fc7728d7560c8d039a4199a77879b9f6"
"Rev": "1a4398cc55c8fe82a964097eaf59f2475b020a49"
},
{
"ImportPath": "github.com/syndtr/goleveldb/leveldb",
"Rev": "63c9e642efad852f49e20a6f90194cae112fd2ac"
"Rev": "e3f32eb300aa1e514fe8ba58d008da90a062273d"
},
{
"ImportPath": "github.com/syndtr/gosnappy/snappy",

View File

@@ -1,20 +0,0 @@
Copyright (c) 2012 Daniel Theophanes
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.

View File

@@ -0,0 +1,27 @@
Copyright (c) 2012 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -0,0 +1,14 @@
### Extensions to the "os" package.
## Find the current Executable and ExecutableFolder.
There is sometimes utility in finding the current executable file
that is running. This can be used for upgrading the current executable
or finding resources located relative to the executable file.
Multi-platform and supports:
* Linux
* OS X
* Windows
* Plan 9
* BSDs.

View File

@@ -25,8 +25,3 @@ func ExecutableFolder() (string, error) {
folder, _ := filepath.Split(p)
return folder, nil
}
// Depricated. Same as Executable().
func GetExePath() (exePath string, err error) {
return Executable()
}

View File

@@ -5,16 +5,16 @@
package osext
import (
"syscall"
"os"
"strconv"
"os"
"strconv"
"syscall"
)
func executable() (string, error) {
f, err := os.Open("/proc/" + strconv.Itoa(os.Getpid()) + "/text")
if err != nil {
return "", err
}
defer f.Close()
return syscall.Fd2path(int(f.Fd()))
f, err := os.Open("/proc/" + strconv.Itoa(os.Getpid()) + "/text")
if err != nil {
return "", err
}
defer f.Close()
return syscall.Fd2path(int(f.Fd()))
}

View File

@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build linux netbsd openbsd solaris
// +build linux netbsd openbsd solaris dragonfly
package osext
@@ -19,7 +19,7 @@ func executable() (string, error) {
return os.Readlink("/proc/self/exe")
case "netbsd":
return os.Readlink("/proc/curproc/exe")
case "openbsd":
case "openbsd", "dragonfly":
return os.Readlink("/proc/curproc/file")
case "solaris":
return os.Readlink(fmt.Sprintf("/proc/%d/path/a.out", os.Getpid()))

View File

@@ -0,0 +1,53 @@
// Copyright (C) 2015 The Protocol Authors.
package protocol
import "fmt"
type Compression int
const (
CompressMetadata Compression = iota // zero value is the default, default should be "metadata"
CompressNever
CompressAlways
compressionThreshold = 128 // don't bother compressing messages smaller than this many bytes
)
var compressionMarshal = map[Compression]string{
CompressNever: "never",
CompressMetadata: "metadata",
CompressAlways: "always",
}
var compressionUnmarshal = map[string]Compression{
// Legacy
"false": CompressNever,
"true": CompressMetadata,
// Current
"never": CompressNever,
"metadata": CompressMetadata,
"always": CompressAlways,
}
func (c Compression) String() string {
s, ok := compressionMarshal[c]
if !ok {
return fmt.Sprintf("unknown:%d", c)
}
return s
}
func (c Compression) GoString() string {
return fmt.Sprintf("%q", c.String())
}
func (c Compression) MarshalText() ([]byte, error) {
return []byte(compressionMarshal[c]), nil
}
func (c *Compression) UnmarshalText(bs []byte) error {
*c = compressionUnmarshal[string(bs)]
return nil
}

View File

@@ -0,0 +1,49 @@
// Copyright (C) 2015 The Protocol Authors.
package protocol
import "testing"
func TestCompressionMarshal(t *testing.T) {
uTestcases := []struct {
s string
c Compression
}{
{"true", CompressMetadata},
{"false", CompressNever},
{"never", CompressNever},
{"metadata", CompressMetadata},
{"always", CompressAlways},
{"whatever", CompressMetadata},
}
mTestcases := []struct {
s string
c Compression
}{
{"never", CompressNever},
{"metadata", CompressMetadata},
{"always", CompressAlways},
}
var c Compression
for _, tc := range uTestcases {
err := c.UnmarshalText([]byte(tc.s))
if err != nil {
t.Error(err)
}
if c != tc.c {
t.Errorf("%s unmarshalled to %d, not %d", tc.s, c, tc.c)
}
}
for _, tc := range mTestcases {
bs, err := tc.c.MarshalText()
if err != nil {
t.Error(err)
}
if s := string(bs); s != tc.s {
t.Errorf("%d marshalled to %q, not %q", tc.c, s, tc.s)
}
}
}

View File

@@ -7,7 +7,7 @@ package protocol
import "fmt"
type IndexMessage struct {
Folder string // max:64
Folder string
Files []FileInfo
Flags uint32
Options []Option // max:64
@@ -83,9 +83,9 @@ type ResponseMessage struct {
}
type ClusterConfigMessage struct {
ClientName string // max:64
ClientVersion string // max:64
Folders []Folder // max:64
ClientName string // max:64
ClientVersion string // max:64
Folders []Folder
Options []Option // max:64
}

View File

@@ -41,7 +41,7 @@ IndexMessage Structure:
struct IndexMessage {
string Folder<64>;
string Folder<>;
FileInfo Files<>;
unsigned int Flags;
Option Options<64>;
@@ -74,9 +74,6 @@ func (o IndexMessage) AppendXDR(bs []byte) ([]byte, error) {
}
func (o IndexMessage) encodeXDR(xw *xdr.Writer) (int, error) {
if l := len(o.Folder); l > 64 {
return xw.Tot(), xdr.ElementSizeExceeded("Folder", l, 64)
}
xw.WriteString(o.Folder)
xw.WriteUint32(uint32(len(o.Files)))
for i := range o.Files {
@@ -111,7 +108,7 @@ func (o *IndexMessage) UnmarshalXDR(bs []byte) error {
}
func (o *IndexMessage) decodeXDR(xr *xdr.Reader) error {
o.Folder = xr.ReadStringMax(64)
o.Folder = xr.ReadString()
_FilesSize := int(xr.ReadUint32())
o.Files = make([]FileInfo, _FilesSize)
for i := range o.Files {
@@ -559,7 +556,7 @@ ClusterConfigMessage Structure:
struct ClusterConfigMessage {
string ClientName<64>;
string ClientVersion<64>;
Folder Folders<64>;
Folder Folders<>;
Option Options<64>;
}
@@ -598,9 +595,6 @@ func (o ClusterConfigMessage) encodeXDR(xw *xdr.Writer) (int, error) {
return xw.Tot(), xdr.ElementSizeExceeded("ClientVersion", l, 64)
}
xw.WriteString(o.ClientVersion)
if l := len(o.Folders); l > 64 {
return xw.Tot(), xdr.ElementSizeExceeded("Folders", l, 64)
}
xw.WriteUint32(uint32(len(o.Folders)))
for i := range o.Folders {
_, err := o.Folders[i].encodeXDR(xw)
@@ -636,9 +630,6 @@ func (o *ClusterConfigMessage) decodeXDR(xr *xdr.Reader) error {
o.ClientName = xr.ReadStringMax(64)
o.ClientVersion = xr.ReadStringMax(64)
_FoldersSize := int(xr.ReadUint32())
if _FoldersSize > 64 {
return xdr.ElementSizeExceeded("Folders", _FoldersSize, 64)
}
o.Folders = make([]Folder, _FoldersSize)
for i := range o.Folders {
(&o.Folders[i]).decodeXDR(xr)

View File

@@ -43,6 +43,8 @@ const (
FlagSymlink = 1 << 16
FlagSymlinkMissingTarget = 1 << 17
FlagsAll = (1 << 18) - 1
SymlinkTypeMask = FlagDirectory | FlagSymlinkMissingTarget
)
@@ -104,7 +106,7 @@ type rawConnection struct {
closed chan struct{}
once sync.Once
compressionThreshold int // compress messages larger than this many bytes
compression Compression
rdbuf0 []byte // used & reused by readMessage
rdbuf1 []byte // used & reused by readMessage
@@ -133,25 +135,21 @@ const (
pingIdleTime = 60 * time.Second
)
func NewConnection(deviceID DeviceID, reader io.Reader, writer io.Writer, receiver Model, name string, compress bool) Connection {
func NewConnection(deviceID DeviceID, reader io.Reader, writer io.Writer, receiver Model, name string, compress Compression) Connection {
cr := &countingReader{Reader: reader}
cw := &countingWriter{Writer: writer}
compThres := 1<<31 - 1 // compression disabled
if compress {
compThres = 128 // compress messages that are 128 bytes long or larger
}
c := rawConnection{
id: deviceID,
name: name,
receiver: nativeModel{receiver},
state: stateInitial,
cr: cr,
cw: cw,
outbox: make(chan hdrMsg),
nextID: make(chan int),
closed: make(chan struct{}),
compressionThreshold: compThres,
id: deviceID,
name: name,
receiver: nativeModel{receiver},
state: stateInitial,
cr: cr,
cw: cw,
outbox: make(chan hdrMsg),
nextID: make(chan int),
closed: make(chan struct{}),
compression: compress,
}
go c.readerLoop()
@@ -569,7 +567,15 @@ func (c *rawConnection) writerLoop() {
return
}
if len(uncBuf) >= c.compressionThreshold {
compress := false
switch c.compression {
case CompressAlways:
compress = true
case CompressMetadata:
compress = hm.hdr.msgType != messageTypeResponse
}
if compress && len(uncBuf) >= compressionThreshold {
// Use compression for large messages
hm.hdr.compression = true

View File

@@ -67,8 +67,8 @@ func TestPing(t *testing.T) {
ar, aw := io.Pipe()
br, bw := io.Pipe()
c0 := NewConnection(c0ID, ar, bw, nil, "name", true).(wireFormatConnection).next.(*rawConnection)
c1 := NewConnection(c1ID, br, aw, nil, "name", true).(wireFormatConnection).next.(*rawConnection)
c0 := NewConnection(c0ID, ar, bw, nil, "name", CompressAlways).(wireFormatConnection).next.(*rawConnection)
c1 := NewConnection(c1ID, br, aw, nil, "name", CompressAlways).(wireFormatConnection).next.(*rawConnection)
if ok := c0.ping(); !ok {
t.Error("c0 ping failed")
@@ -91,8 +91,8 @@ func TestPingErr(t *testing.T) {
eaw := &ErrPipe{PipeWriter: *aw, max: i, err: e}
ebw := &ErrPipe{PipeWriter: *bw, max: j, err: e}
c0 := NewConnection(c0ID, ar, ebw, m0, "name", true).(wireFormatConnection).next.(*rawConnection)
NewConnection(c1ID, br, eaw, m1, "name", true)
c0 := NewConnection(c0ID, ar, ebw, m0, "name", CompressAlways).(wireFormatConnection).next.(*rawConnection)
NewConnection(c1ID, br, eaw, m1, "name", CompressAlways)
res := c0.ping()
if (i < 8 || j < 8) && res {
@@ -167,8 +167,8 @@ func TestVersionErr(t *testing.T) {
ar, aw := io.Pipe()
br, bw := io.Pipe()
c0 := NewConnection(c0ID, ar, bw, m0, "name", true).(wireFormatConnection).next.(*rawConnection)
NewConnection(c1ID, br, aw, m1, "name", true)
c0 := NewConnection(c0ID, ar, bw, m0, "name", CompressAlways).(wireFormatConnection).next.(*rawConnection)
NewConnection(c1ID, br, aw, m1, "name", CompressAlways)
w := xdr.NewWriter(c0.cw)
w.WriteUint32(encodeHeader(header{
@@ -190,8 +190,8 @@ func TestTypeErr(t *testing.T) {
ar, aw := io.Pipe()
br, bw := io.Pipe()
c0 := NewConnection(c0ID, ar, bw, m0, "name", true).(wireFormatConnection).next.(*rawConnection)
NewConnection(c1ID, br, aw, m1, "name", true)
c0 := NewConnection(c0ID, ar, bw, m0, "name", CompressAlways).(wireFormatConnection).next.(*rawConnection)
NewConnection(c1ID, br, aw, m1, "name", CompressAlways)
w := xdr.NewWriter(c0.cw)
w.WriteUint32(encodeHeader(header{
@@ -213,8 +213,8 @@ func TestClose(t *testing.T) {
ar, aw := io.Pipe()
br, bw := io.Pipe()
c0 := NewConnection(c0ID, ar, bw, m0, "name", true).(wireFormatConnection).next.(*rawConnection)
NewConnection(c1ID, br, aw, m1, "name", true)
c0 := NewConnection(c0ID, ar, bw, m0, "name", CompressAlways).(wireFormatConnection).next.(*rawConnection)
NewConnection(c1ID, br, aw, m1, "name", CompressAlways)
c0.close(nil)

View File

@@ -4,7 +4,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// +build go1.3
// +build !go1.2
package leveldb

View File

@@ -0,0 +1,30 @@
// Copyright (c) 2012, Suryandaru Triandana <syndtr@gmail.com>
// All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// +build !go1.2
package cache
import (
"math/rand"
"testing"
)
func BenchmarkLRUCache(b *testing.B) {
c := NewCache(NewLRU(10000))
b.SetParallelism(10)
b.RunParallel(func(pb *testing.PB) {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
for pb.Next() {
key := uint64(r.Intn(1000000))
c.Get(0, key, func() (int, Value) {
return 1, key
}).Release()
}
})
}

View File

@@ -552,19 +552,3 @@ func TestLRUCache_Close(t *testing.T) {
t.Errorf("delFunc isn't called 1 times: got=%d", delFuncCalled)
}
}
func BenchmarkLRUCache(b *testing.B) {
c := NewCache(NewLRU(10000))
b.SetParallelism(10)
b.RunParallel(func(pb *testing.PB) {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
for pb.Next() {
key := uint64(r.Intn(1000000))
c.Get(0, key, func() (int, Value) {
return 1, key
}).Release()
}
})
}

View File

@@ -347,12 +347,14 @@ func recoverTable(s *session, o *opt.Options) error {
return err
}
iter := tr.NewIterator(nil, nil)
iter.(iterator.ErrorCallbackSetter).SetErrorCallback(func(err error) {
if errors.IsCorrupted(err) {
s.logf("table@recovery block corruption @%d %q", file.Num(), err)
tcorruptedBlock++
}
})
if itererr, ok := iter.(iterator.ErrorCallbackSetter); ok {
itererr.SetErrorCallback(func(err error) {
if errors.IsCorrupted(err) {
s.logf("table@recovery block corruption @%d %q", file.Num(), err)
tcorruptedBlock++
}
})
}
// Scan the table.
for iter.Next() {

4
NICKS
View File

@@ -10,6 +10,7 @@ Zillode <zillode@zillode.be>
alex2108 <register-github@alex-graf.de>
andrew-d <andrew@du.nham.ca>
asdil12 <dominik@heidler.eu>
bencurthoys <ben@bencurthoys.com>
bigbear2nd <bigbear2nd@gmail.com>
brendanlong <self@brendanlong.com>
bsidhom <bsidhom@gmail.com>
@@ -26,6 +27,8 @@ jpjp <jamespatterson@operamail.com> <jpjp@users.noreply.github.com>
kozec <kozec@kozec.com>
krozycki <rozycki.karol@gmail.com>
marcindziadus <dziadus.marcin@gmail.com>
marclaporte <marc@marclaporte.com>
moshen <moshen.colin@gmail.com>
mvdan <mvdan@mvdan.cc>
peterhoeg <peter@speartail.com>
philips <brandon@ifup.org>
@@ -37,6 +40,7 @@ qbit <qbit@deftly.net>
seehuhn <voss@seehuhn.de>
snnd <dw@risu.io>
timabell <tim@timwise.co.uk>
tnn2 <tnn@nygren.pp.se>
tojrobinson <tully@tojr.org>
uok <ueomkail@gmail.com> <uok@users.noreply.github.com>
veeti <veeti.paananen@rojekti.fi>

BIN
assets/logo-horizontal.svg Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

BIN
assets/logo-only.svg Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
assets/logo-vertical.svg Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -2,7 +2,6 @@
set -euo pipefail
IFS=$'\n\t'
DOCKERIMGV=1.4.1-1
STTRACE=${STTRACE:-}
case "${1:-default}" in
@@ -51,20 +50,28 @@ case "${1:-default}" in
;;
all)
go run build.go -goos linux -goarch amd64 tar
go run build.go -goos linux -goarch 386 tar
go run build.go -goos linux -goarch arm tar
go run build.go -goos freebsd -goarch amd64 tar
go run build.go -goos freebsd -goarch 386 tar
go run build.go -goos openbsd -goarch amd64 tar
go run build.go -goos openbsd -goarch 386 tar
go run build.go -goos darwin -goarch amd64 tar
go run build.go -goos windows -goarch amd64 zip
go run build.go -goos dragonfly -goarch 386 tar
go run build.go -goos dragonfly -goarch amd64 tar
go run build.go -goos freebsd -goarch 386 tar
go run build.go -goos freebsd -goarch amd64 tar
go run build.go -goos linux -goarch 386 tar
go run build.go -goos linux -goarch amd64 tar
go run build.go -goos linux -goarch arm tar
go run build.go -goos netbsd -goarch 386 tar
go run build.go -goos netbsd -goarch amd64 tar
go run build.go -goos openbsd -goarch 386 tar
go run build.go -goos openbsd -goarch amd64 tar
go run build.go -goos solaris -goarch amd64 tar
go run build.go -goos windows -goarch 386 zip
go run build.go -goos windows -goarch amd64 zip
;;
setup)
@@ -103,16 +110,12 @@ case "${1:-default}" in
fi
;;
docker-init)
docker build -q -t syncthing/build:$DOCKERIMGV docker
;;
docker-all)
docker run --rm -h syncthing-builder -u $(id -u) -t \
-v $(pwd):/go/src/github.com/syncthing/syncthing \
-w /go/src/github.com/syncthing/syncthing \
-e "STTRACE=$STTRACE" \
syncthing/build:$DOCKERIMGV \
syncthing/build:latest \
sh -c './build.sh clean \
&& go vet ./cmd/... ./internal/... \
&& ( golint ./cmd/... ; golint ./internal/... ) | egrep -v "comment on exported|should have comment" \
@@ -125,7 +128,7 @@ case "${1:-default}" in
-v $(pwd):/go/src/github.com/syncthing/syncthing \
-w /go/src/github.com/syncthing/syncthing \
-e "STTRACE=$STTRACE" \
syncthing/build:$DOCKERIMGV \
syncthing/build:latest \
sh -euxc './build.sh clean \
&& go run build.go -race \
&& export GOPATH=$(pwd)/Godeps/_workspace:$GOPATH \

View File

@@ -5,5 +5,5 @@ if [[ -z $since ]] ; then
since="$(git describe --abbrev=0 HEAD^).."
fi
git log --reverse --pretty=format:'* %s, @%aN)' "$since" | egrep 'fixes #\d|ref #\d' | sed 's/),/,/' | sed 's/fixes #/#/g' | sed 's/ref #/#/g'
git log --reverse --pretty=format:'* %s, @%aN)' "$since" | egrep 'fixes #\d|ref #\d' | sed 's/)[,. ]*,/,/' | sed 's/fixes #/#/g' | sed 's/ref #/#/g'

View File

@@ -0,0 +1,305 @@
// Copyright (C) 2015 The Syncthing Authors.
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"crypto/tls"
"fmt"
"io"
"net"
"strings"
"time"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/internal/events"
"github.com/syncthing/syncthing/internal/model"
)
func listenConnect(myID protocol.DeviceID, m *model.Model, tlsCfg *tls.Config) {
var conns = make(chan *tls.Conn)
// Listen
for _, addr := range cfg.Options().ListenAddress {
go listenTLS(conns, addr, tlsCfg)
}
// Connect
go dialTLS(m, conns, tlsCfg)
next:
for conn := range conns {
cs := conn.ConnectionState()
// We should have negotiated the next level protocol "bep/1.0" as part
// of the TLS handshake. If we didn't, we're not speaking to another
// BEP-speaker so drop the connection.
if !cs.NegotiatedProtocolIsMutual || cs.NegotiatedProtocol != bepProtocolName {
l.Infof("Peer %s did not negotiate bep/1.0", conn.RemoteAddr())
conn.Close()
continue
}
// We should have received exactly one certificate from the other
// side. If we didn't, they don't have a device ID and we drop the
// connection.
certs := cs.PeerCertificates
if cl := len(certs); cl != 1 {
l.Infof("Got peer certificate list of length %d != 1 from %s; protocol error", cl, conn.RemoteAddr())
conn.Close()
continue
}
remoteCert := certs[0]
remoteID := protocol.NewDeviceID(remoteCert.Raw)
// The device ID should not be that of ourselves. It can happen
// though, especially in the presense of NAT hairpinning, multiple
// clients between the same NAT gateway, and global discovery.
if remoteID == myID {
l.Infof("Connected to myself (%s) - should not happen", remoteID)
conn.Close()
continue
}
// We should not already be connected to the other party. TODO: This
// could use some better handling. If the old connection is dead but
// hasn't timed out yet we may want to drop *that* connection and keep
// this one. But in case we are two devices connecting to each other
// in parallell we don't want to do that or we end up with no
// connections still established...
if m.ConnectedTo(remoteID) {
l.Infof("Connected to already connected device (%s)", remoteID)
conn.Close()
continue
}
for deviceID, deviceCfg := range cfg.Devices() {
if deviceID == remoteID {
// Verify the name on the certificate. By default we set it to
// "syncthing" when generating, but the user may have replaced
// the certificate and used another name.
certName := deviceCfg.CertName
if certName == "" {
certName = tlsDefaultCommonName
}
err := remoteCert.VerifyHostname(certName)
if err != nil {
// Incorrect certificate name is something the user most
// likely wants to know about, since it's an advanced
// config. Warn instead of Info.
l.Warnf("Bad certificate from %s (%v): %v", remoteID, conn.RemoteAddr(), err)
conn.Close()
continue next
}
// If rate limiting is set, and based on the address we should
// limit the connection, then we wrap it in a limiter.
limit := shouldLimit(conn.RemoteAddr())
wr := io.Writer(conn)
if limit && writeRateLimit != nil {
wr = &limitedWriter{conn, writeRateLimit}
}
rd := io.Reader(conn)
if limit && readRateLimit != nil {
rd = &limitedReader{conn, readRateLimit}
}
name := fmt.Sprintf("%s-%s", conn.LocalAddr(), conn.RemoteAddr())
protoConn := protocol.NewConnection(remoteID, rd, wr, m, name, deviceCfg.Compression)
l.Infof("Established secure connection to %s at %s", remoteID, name)
if debugNet {
l.Debugf("cipher suite: %04X in lan: %t", conn.ConnectionState().CipherSuite, !limit)
}
events.Default.Log(events.DeviceConnected, map[string]string{
"id": remoteID.String(),
"addr": conn.RemoteAddr().String(),
})
m.AddConnection(conn, protoConn)
continue next
}
}
if !cfg.IgnoredDevice(remoteID) {
events.Default.Log(events.DeviceRejected, map[string]string{
"device": remoteID.String(),
"address": conn.RemoteAddr().String(),
})
l.Infof("Connection from %s with unknown device ID %s", conn.RemoteAddr(), remoteID)
} else {
l.Infof("Connection from %s with ignored device ID %s", conn.RemoteAddr(), remoteID)
}
conn.Close()
}
}
func listenTLS(conns chan *tls.Conn, addr string, tlsCfg *tls.Config) {
if debugNet {
l.Debugln("listening on", addr)
}
tcaddr, err := net.ResolveTCPAddr("tcp", addr)
if err != nil {
l.Fatalln("listen (BEP):", err)
}
listener, err := net.ListenTCP("tcp", tcaddr)
if err != nil {
l.Fatalln("listen (BEP):", err)
}
for {
conn, err := listener.Accept()
if err != nil {
l.Warnln("Accepting connection:", err)
continue
}
if debugNet {
l.Debugln("connect from", conn.RemoteAddr())
}
tcpConn := conn.(*net.TCPConn)
setTCPOptions(tcpConn)
tc := tls.Server(conn, tlsCfg)
err = tc.Handshake()
if err != nil {
l.Infoln("TLS handshake:", err)
tc.Close()
continue
}
conns <- tc
}
}
func dialTLS(m *model.Model, conns chan *tls.Conn, tlsCfg *tls.Config) {
delay := time.Second
for {
nextDevice:
for deviceID, deviceCfg := range cfg.Devices() {
if deviceID == myID {
continue
}
if m.ConnectedTo(deviceID) {
continue
}
var addrs []string
for _, addr := range deviceCfg.Addresses {
if addr == "dynamic" {
if discoverer != nil {
t := discoverer.Lookup(deviceID)
if len(t) == 0 {
continue
}
addrs = append(addrs, t...)
}
} else {
addrs = append(addrs, addr)
}
}
for _, addr := range addrs {
host, port, err := net.SplitHostPort(addr)
if err != nil && strings.HasPrefix(err.Error(), "missing port") {
// addr is on the form "1.2.3.4"
addr = net.JoinHostPort(addr, "22000")
} else if err == nil && port == "" {
// addr is on the form "1.2.3.4:"
addr = net.JoinHostPort(host, "22000")
}
if debugNet {
l.Debugln("dial", deviceCfg.DeviceID, addr)
}
raddr, err := net.ResolveTCPAddr("tcp", addr)
if err != nil {
if debugNet {
l.Debugln(err)
}
continue
}
conn, err := net.DialTCP("tcp", nil, raddr)
if err != nil {
if debugNet {
l.Debugln(err)
}
continue
}
setTCPOptions(conn)
tc := tls.Client(conn, tlsCfg)
err = tc.Handshake()
if err != nil {
l.Infoln("TLS handshake:", err)
tc.Close()
continue
}
conns <- tc
continue nextDevice
}
}
time.Sleep(delay)
delay *= 2
if maxD := time.Duration(cfg.Options().ReconnectIntervalS) * time.Second; delay > maxD {
delay = maxD
}
}
}
func setTCPOptions(conn *net.TCPConn) {
var err error
if err = conn.SetLinger(0); err != nil {
l.Infoln(err)
}
if err = conn.SetNoDelay(false); err != nil {
l.Infoln(err)
}
if err = conn.SetKeepAlivePeriod(60 * time.Second); err != nil {
l.Infoln(err)
}
if err = conn.SetKeepAlive(true); err != nil {
l.Infoln(err)
}
}
func shouldLimit(addr net.Addr) bool {
if cfg.Options().LimitBandwidthInLan {
return true
}
tcpaddr, ok := addr.(*net.TCPAddr)
if !ok {
return true
}
for _, lan := range lans {
if lan.Contains(tcpaddr.IP) {
return false
}
}
return !tcpaddr.IP.IsLoopback()
}

View File

@@ -130,6 +130,7 @@ func startGUI(cfg config.GUIConfiguration, assetDir string, m *model.Model) erro
getRestMux.HandleFunc("/rest/system", restGetSystem)
getRestMux.HandleFunc("/rest/upgrade", restGetUpgrade)
getRestMux.HandleFunc("/rest/version", restGetVersion)
getRestMux.HandleFunc("/rest/tree", withModel(m, restGetTree))
getRestMux.HandleFunc("/rest/stats/device", withModel(m, restGetDeviceStats))
getRestMux.HandleFunc("/rest/stats/folder", withModel(m, restGetFolderStats))
@@ -262,6 +263,24 @@ func restGetVersion(w http.ResponseWriter, r *http.Request) {
})
}
func restGetTree(m *model.Model, w http.ResponseWriter, r *http.Request) {
qs := r.URL.Query()
folder := qs.Get("folder")
prefix := qs.Get("prefix")
dirsonly := qs.Get("dirsonly") != ""
levels, err := strconv.Atoi(qs.Get("levels"))
if err != nil {
levels = -1
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
tree := m.GlobalDirectoryTree(folder, prefix, levels, dirsonly)
json.NewEncoder(w).Encode(tree)
}
func restGetCompletion(m *model.Model, w http.ResponseWriter, r *http.Request) {
var qs = r.URL.Query()
var folder = qs.Get("folder")
@@ -457,7 +476,7 @@ func restGetSystem(w http.ResponseWriter, r *http.Request) {
cpusum += p
}
cpuUsageLock.RUnlock()
res["cpuPercent"] = cpusum / 10
res["cpuPercent"] = cpusum / float64(len(cpuUsagePercent)) / float64(runtime.NumCPU())
res["pathSeparator"] = string(filepath.Separator)
w.Header().Set("Content-Type", "application/json; charset=utf-8")
@@ -652,10 +671,18 @@ func restPostUpgrade(w http.ResponseWriter, r *http.Request) {
func restPostScan(m *model.Model, w http.ResponseWriter, r *http.Request) {
qs := r.URL.Query()
folder := qs.Get("folder")
sub := qs.Get("sub")
err := m.ScanFolderSub(folder, sub)
if err != nil {
http.Error(w, err.Error(), 500)
if folder != "" {
sub := qs.Get("sub")
err := m.ScanFolderSub(folder, sub)
if err != nil {
http.Error(w, err.Error(), 500)
}
} else {
errors := m.ScanFolders()
if len(errors) > 0 {
http.Error(w, "Error scanning folders", 500)
json.NewEncoder(w).Encode(errors)
}
}
}
@@ -802,8 +829,7 @@ func toNeedSlice(fs []db.FileInfoTruncated) []map[string]interface{} {
"Modified": file.Modified,
"Version": file.Version,
"LocalVersion": file.LocalVersion,
"NumBlocks": file.NumBlocks,
"Size": db.BlocksToSize(int(file.NumBlocks)),
"Size": file.Size(),
}
}
return output

View File

@@ -20,27 +20,32 @@ import (
"os"
"runtime"
"runtime/pprof"
"strconv"
"syscall"
"time"
)
func init() {
if innerProcess && os.Getenv("STHEAPPROFILE") != "" {
rate := 1
if i, err := strconv.Atoi(os.Getenv("STHEAPPROFILE")); err == nil {
rate = i
}
l.Debugln("Starting heap profiling")
go saveHeapProfiles()
go saveHeapProfiles(rate)
}
}
func saveHeapProfiles() {
runtime.MemProfileRate = 1
func saveHeapProfiles(rate int) {
runtime.MemProfileRate = rate
var memstats, prevMemstats runtime.MemStats
t0 := time.Now()
for t := range time.NewTicker(250 * time.Millisecond).C {
startms := int(t.Sub(t0).Seconds() * 1000)
name := fmt.Sprintf("heap-%05d.pprof", syscall.Getpid())
for {
runtime.ReadMemStats(&memstats)
if memstats.HeapInuse > prevMemstats.HeapInuse {
fd, err := os.Create(fmt.Sprintf("heap-%05d-%07d.pprof", syscall.Getpid(), startms))
fd, err := os.Create(name + ".tmp")
if err != nil {
panic(err)
}
@@ -52,7 +57,16 @@ func saveHeapProfiles() {
if err != nil {
panic(err)
}
_ = os.Remove(name) // Error deliberately ignored
err = os.Rename(name+".tmp", name)
if err != nil {
panic(err)
}
prevMemstats = memstats
}
time.Sleep(250 * time.Millisecond)
}
}

View File

@@ -19,7 +19,6 @@ import (
"crypto/tls"
"flag"
"fmt"
"io"
"log"
"net"
"net/http"
@@ -48,6 +47,7 @@ import (
"github.com/syncthing/syncthing/internal/upgrade"
"github.com/syncthing/syncthing/internal/upnp"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/errors"
"github.com/syndtr/goleveldb/leveldb/opt"
"golang.org/x/crypto/bcrypt"
)
@@ -72,6 +72,8 @@ const (
exitUpgrading = 4
)
const bepProtocolName = "bep/1.0"
var l = logger.DefaultLogger
func init() {
@@ -113,6 +115,7 @@ var (
externalPort int
igd *upnp.IGD
cert tls.Certificate
lans []*net.IPNet
)
const (
@@ -461,7 +464,7 @@ func syncthingMain() {
tlsCfg := &tls.Config{
Certificates: []tls.Certificate{cert},
NextProtos: []string{"bep/1.0"},
NextProtos: []string{bepProtocolName},
ClientAuth: tls.RequestClientCert,
SessionTicketsDisabled: true,
InsecureSkipVerify: true,
@@ -492,7 +495,16 @@ func syncthingMain() {
readRateLimit = ratelimit.NewBucketWithRate(float64(1000*opts.MaxRecvKbps), int64(5*1000*opts.MaxRecvKbps))
}
ldb, err := leveldb.OpenFile(filepath.Join(confDir, "index"), &opt.Options{OpenFilesCacheCapacity: 100})
if opts.MaxRecvKbps > 0 || opts.MaxSendKbps > 0 {
lans, _ = osutil.GetLans()
}
dbFile := filepath.Join(confDir, "index")
dbOpts := &opt.Options{OpenFilesCacheCapacity: 100}
ldb, err := leveldb.OpenFile(dbFile, dbOpts)
if err != nil && errors.IsCorrupted(err) {
ldb, err = leveldb.RecoverFile(dbFile, dbOpts)
}
if err != nil {
l.Fatalln("Cannot open database:", err, "- Is another copy of Syncthing already running?")
}
@@ -613,7 +625,7 @@ func syncthingMain() {
} else if IsRelease {
go autoUpgrade()
} else {
l.Infof("No automatic upgrades; %s is not a relase version.", Version)
l.Infof("No automatic upgrades; %s is not a release version.", Version)
}
}
@@ -900,239 +912,6 @@ func shutdown() {
stop <- exitSuccess
}
func listenConnect(myID protocol.DeviceID, m *model.Model, tlsCfg *tls.Config) {
var conns = make(chan *tls.Conn)
// Listen
for _, addr := range cfg.Options().ListenAddress {
go listenTLS(conns, addr, tlsCfg)
}
// Connect
go dialTLS(m, conns, tlsCfg)
next:
for conn := range conns {
certs := conn.ConnectionState().PeerCertificates
if cl := len(certs); cl != 1 {
l.Infof("Got peer certificate list of length %d != 1 from %s; protocol error", cl, conn.RemoteAddr())
conn.Close()
continue
}
remoteCert := certs[0]
remoteID := protocol.NewDeviceID(remoteCert.Raw)
if remoteID == myID {
l.Infof("Connected to myself (%s) - should not happen", remoteID)
conn.Close()
continue
}
if m.ConnectedTo(remoteID) {
l.Infof("Connected to already connected device (%s)", remoteID)
conn.Close()
continue
}
for deviceID, deviceCfg := range cfg.Devices() {
if deviceID == remoteID {
// Verify the name on the certificate. By default we set it to
// "syncthing" when generating, but the user may have replaced
// the certificate and used another name.
certName := deviceCfg.CertName
if certName == "" {
certName = tlsDefaultCommonName
}
err := remoteCert.VerifyHostname(certName)
if err != nil {
// Incorrect certificate name is something the user most
// likely wants to know about, since it's an advanced
// config. Warn instead of Info.
l.Warnf("Bad certificate from %s (%v): %v", remoteID, conn.RemoteAddr(), err)
conn.Close()
continue next
}
// If rate limiting is set, we wrap the connection in a
// limiter.
wr := io.Writer(conn)
if writeRateLimit != nil {
wr = &limitedWriter{conn, writeRateLimit}
}
rd := io.Reader(conn)
if readRateLimit != nil {
rd = &limitedReader{conn, readRateLimit}
}
name := fmt.Sprintf("%s-%s", conn.LocalAddr(), conn.RemoteAddr())
protoConn := protocol.NewConnection(remoteID, rd, wr, m, name, deviceCfg.Compression)
l.Infof("Established secure connection to %s at %s", remoteID, name)
if debugNet {
l.Debugf("cipher suite %04X", conn.ConnectionState().CipherSuite)
}
events.Default.Log(events.DeviceConnected, map[string]string{
"id": remoteID.String(),
"addr": conn.RemoteAddr().String(),
})
m.AddConnection(conn, protoConn)
continue next
}
}
if !cfg.IgnoredDevice(remoteID) {
events.Default.Log(events.DeviceRejected, map[string]string{
"device": remoteID.String(),
"address": conn.RemoteAddr().String(),
})
l.Infof("Connection from %s with unknown device ID %s", conn.RemoteAddr(), remoteID)
} else {
l.Infof("Connection from %s with ignored device ID %s", conn.RemoteAddr(), remoteID)
}
conn.Close()
}
}
func listenTLS(conns chan *tls.Conn, addr string, tlsCfg *tls.Config) {
if debugNet {
l.Debugln("listening on", addr)
}
tcaddr, err := net.ResolveTCPAddr("tcp", addr)
if err != nil {
l.Fatalln("listen (BEP):", err)
}
listener, err := net.ListenTCP("tcp", tcaddr)
if err != nil {
l.Fatalln("listen (BEP):", err)
}
for {
conn, err := listener.Accept()
if err != nil {
l.Warnln("Accepting connection:", err)
continue
}
if debugNet {
l.Debugln("connect from", conn.RemoteAddr())
}
tcpConn := conn.(*net.TCPConn)
setTCPOptions(tcpConn)
tc := tls.Server(conn, tlsCfg)
err = tc.Handshake()
if err != nil {
l.Infoln("TLS handshake:", err)
tc.Close()
continue
}
conns <- tc
}
}
func dialTLS(m *model.Model, conns chan *tls.Conn, tlsCfg *tls.Config) {
delay := time.Second
for {
nextDevice:
for deviceID, deviceCfg := range cfg.Devices() {
if deviceID == myID {
continue
}
if m.ConnectedTo(deviceID) {
continue
}
var addrs []string
for _, addr := range deviceCfg.Addresses {
if addr == "dynamic" {
if discoverer != nil {
t := discoverer.Lookup(deviceID)
if len(t) == 0 {
continue
}
addrs = append(addrs, t...)
}
} else {
addrs = append(addrs, addr)
}
}
for _, addr := range addrs {
host, port, err := net.SplitHostPort(addr)
if err != nil && strings.HasPrefix(err.Error(), "missing port") {
// addr is on the form "1.2.3.4"
addr = net.JoinHostPort(addr, "22000")
} else if err == nil && port == "" {
// addr is on the form "1.2.3.4:"
addr = net.JoinHostPort(host, "22000")
}
if debugNet {
l.Debugln("dial", deviceCfg.DeviceID, addr)
}
raddr, err := net.ResolveTCPAddr("tcp", addr)
if err != nil {
if debugNet {
l.Debugln(err)
}
continue
}
conn, err := net.DialTCP("tcp", nil, raddr)
if err != nil {
if debugNet {
l.Debugln(err)
}
continue
}
setTCPOptions(conn)
tc := tls.Client(conn, tlsCfg)
err = tc.Handshake()
if err != nil {
l.Infoln("TLS handshake:", err)
tc.Close()
continue
}
conns <- tc
continue nextDevice
}
}
time.Sleep(delay)
delay *= 2
if maxD := time.Duration(cfg.Options().ReconnectIntervalS) * time.Second; delay > maxD {
delay = maxD
}
}
}
func setTCPOptions(conn *net.TCPConn) {
var err error
if err = conn.SetLinger(0); err != nil {
l.Infoln(err)
}
if err = conn.SetNoDelay(false); err != nil {
l.Infoln(err)
}
if err = conn.SetKeepAlivePeriod(60 * time.Second); err != nil {
l.Infoln(err)
}
if err = conn.SetKeepAlive(true); err != nil {
l.Infoln(err)
}
}
func discovery(extPort int) *discover.Discoverer {
opts := cfg.Options()
disc := discover.NewDiscoverer(myID, opts.ListenAddress)
@@ -1169,7 +948,10 @@ func ensureDir(dir string, mode int) {
func getDefaultConfDir() (string, error) {
switch runtime.GOOS {
case "windows":
return filepath.Join(os.Getenv("LocalAppData"), "Syncthing"), nil
if p := os.Getenv("LocalAppData"); p != "" {
return filepath.Join(p, "Syncthing"), nil
}
return filepath.Join(os.Getenv("AppData"), "Syncthing"), nil
case "darwin":
return osutil.ExpandTilde("~/Library/Application Support/Syncthing")

View File

@@ -0,0 +1,40 @@
// Copyright (C) 2014 The Syncthing Authors.
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"errors"
"os/exec"
"strconv"
"strings"
)
func memorySize() (int64, error) {
cmd := exec.Command("/sbin/sysctl", "hw.physmem64")
out, err := cmd.Output()
if err != nil {
return 0, err
}
fs := strings.Fields(string(out))
if len(fs) != 3 {
return 0, errors.New("sysctl parse error")
}
bytes, err := strconv.ParseInt(fs[2], 10, 64)
if err != nil {
return 0, err
}
return bytes, nil
}

View File

@@ -13,7 +13,7 @@
// You should have received a copy of the GNU General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>.
// +build freebsd openbsd
// +build freebsd openbsd dragonfly
package main

View File

@@ -1,75 +0,0 @@
FROM debian:squeeze
MAINTAINER Jakob Borg <jakob@nym.se>
ENV GOLANG_VERSION 1.4.1
# SCMs for "go get", gcc for cgo
RUN apt-get update && apt-get install -y \
ca-certificates curl gcc libc6-dev make \
bzr git mercurial unzip patch \
--no-install-recommends \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Get the binary dist of Go to be able to bootstrap gonative.
RUN curl -sSL https://golang.org/dl/go${GOLANG_VERSION}.linux-amd64.tar.gz \
| tar -v -C /usr/local -xz
ENV PATH /usr/local/go/bin:$PATH
ENV GOPATH /go
ENV GO386 387
ENV GOARM 5
ENV PATH /go/bin:$PATH
RUN mkdir /go
WORKDIR /go
# Use gonative to install native Go for most arch/OS combos
RUN go get github.com/calmh/gonative \
&& cd /usr/local \
&& rm -rf go \
&& gonative -version $GOLANG_VERSION
# Rebuild the special and missing versions, using patches as appropriate
# RUN mkdir /tmp/patches
# ADD *.patch /tmp/patches/
# RUN bash -xec '\
# cd /usr/local/go ; \
# for patch in /tmp/patches/*.patch ; do \
# patch -p0 < "$patch" ; \
# done \
# '
RUN bash -xec '\
cd /usr/local/go/src; \
for platform in linux/386 freebsd/386 windows/386 linux/arm openbsd/amd64 openbsd/386; do \
GOOS=${platform%/*} \
GOARCH=${platform##*/} \
CGO_ENABLED=0 \
./make.bash --no-clean 2>&1; \
done \
&& ./make.bash --no-clean \
'
# Install packages needed for test coverage
RUN go get github.com/tools/godep \
&& go get golang.org/x/tools/cmd/cover \
&& go get github.com/axw/gocov/gocov \
&& go get github.com/AlekSi/gocov-xml
# Install tools "go vet" and "golint"
RUN go get golang.org/x/tools/cmd/vet \
&& go get github.com/golang/lint/golint
# Build standard library for race
RUN go install -race std
# Random build users needs to be able to create stuff in /go
RUN chmod -R 777 /go/bin /go/pkg /go/src

View File

@@ -1,29 +0,0 @@
Docker Build
============
Official builds are produced using a Docker image specified by the
Dockerfile in this directory. The following commands exactly reproduce
the official build process.
Create an image called `syncthing/build` with the build environment.
```
./build.sh docker-init
```
> This is a Debian based image containing the latest stable version of
> Go set up for cross compilation. The cross compilation uses the
> dynamically linked standard libraries and SSE instructions for amd64
> builds, but static linking and minimal instruction set for the 386 and
> arm builds. The command should be run in the main repo directory, as a
> user with permission to perform Docker operations.
Build the full set of supported binaries.
```
./build.sh docker-all
```
> This uses a temporary container with the image from above and a volume
> mapped to the directory containing the source. Tests are run and
> binary packages created.

View File

@@ -9,7 +9,8 @@ other platforms also using runit.
recommended to place it in a directory writeable by the running user
so that automatic upgrades work.
3. Copy the edited `run` file to `/etc/service/syncthing/run`.
3. Copy this directory (containing the edited `run` file and `log` folder) to
`/etc/service/syncthing`.
Log output is sent to syslogd.

4
etc/linux-runit/log/run Executable file
View File

@@ -0,0 +1,4 @@
#!/bin/sh
exec logger -t syncthing

View File

@@ -4,5 +4,6 @@ export USERNAME=jb
export HOME="/home/$USERNAME"
export SYNCTHING="$HOME/bin/syncthing"
setuidgid "$USERNAME" "$SYNCTHING" -logflags 0 2>&1 | logger -t syncthing
exec 2>&1
exec chpst -u "$USERNAME" "$SYNCTHING" -logflags 0

View File

@@ -1,13 +1,12 @@
[Unit]
Description=Syncthing service for %i
Description=Syncthing - Open Source Continuous File Synchronization for %I
Documentation=https://github.com/syncthing/syncthing/wiki
After=network.target
[Service]
User=%i
Environment=STARGS=
EnvironmentFile=-/etc/default/syncthing
Environment=STNORESTART=yes
ExecStart=/usr/bin/syncthing ${STARGS}
ExecStart=/usr/bin/syncthing
Restart=on-failure
RestartPreventExitStatus=1
SuccessExitStatus=2

View File

@@ -1,15 +1,15 @@
[Unit]
Description=Syncthing service
Description=Syncthing - Open Source Continuous File Synchronization
Documentation=https://github.com/syncthing/syncthing/wiki
After=network.target
[Service]
Environment=STARGS=
EnvironmentFile=-%h/.config/syncthing/environment
Environment=STNORESTART=yes
ExecStart=/usr/bin/syncthing ${STARGS}
ExecStart=/usr/bin/syncthing
Restart=on-failure
RestartPreventExitStatus=1
SuccessExitStatus=2
RestartForceExitStatus=3 4
[Install]
WantedBy=cmdline.target
WantedBy=default.target

13
etc/windows/ReadMe.txt Normal file
View File

@@ -0,0 +1,13 @@
A first pass at an install script for SyncThing as a Windows Service
Script uses NSIS http://nsis.sourceforge.net/Download
Requires the NSIS Simple Service Plugin http://nsis.sourceforge.net/NSIS_Simple_Service_Plugin
And uses the Windows Service Wrapper https://github.com/kohsuke/winsw
To build the setup file:
1. Install NSIS, download the Simple Service Plugin DLL into the NSIS plugin folder
2. Create a folder (referenced by the $SOURCEPATH variable in the .nsi file) with all the syncthing output in it (exe file, licences, etc)
3. Download winsw.exe from https://github.com/kohsuke/winsw/releases, rename it to syncthingservice.exe and save that in $SOURCEPATH
4. Put syncthingservice.xml in there too
5. Compile SyncthingSetup.nsi using NSIS

View File

@@ -0,0 +1,116 @@
;--------------------------------
;Include Modern UI
!include "MUI2.nsh"
;--------------------------------
;General
;Name and file
!define SOURCEPATH "C:\SourceCode\SyncThing\Binaries"
Name "SyncThing Windows Service Install"
OutFile "SyncThingSetup.exe"
;Default installation folder
InstallDir "$PROGRAMFILES\SyncThing"
;Get installation folder from registry if available
InstallDirRegKey HKCU "Software\SyncThing" ""
;Request application privileges for Windows Vista
RequestExecutionLevel admin
;--------------------------------
;Interface Settings
!define MUI_ABORTWARNING
;--------------------------------
;Pages
!insertmacro MUI_PAGE_COMPONENTS
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
;--------------------------------
;Languages
!insertmacro MUI_LANGUAGE "English"
;--------------------------------
;Installer Sections
Section "SyncThing" SecSyncThing
SectionIn RO
SetOutPath "$INSTDIR"
IfFileExists syncthingservice.exe 0 +2
SimpleSC::StopService "SyncThing" 1 30
File /r "${SOURCEPATH}\syncthing.exe"
File /r "${SOURCEPATH}\syncthing.exe.md5"
File /r "${SOURCEPATH}\AUTHORS.txt"
File /r "${SOURCEPATH}\LICENSE.txt"
File /r "${SOURCEPATH}\README.txt"
File /r "${SOURCEPATH}\FAQ.pdf"
File /r "${SOURCEPATH}\Getting-Started.pdf"
;Store installation folder
WriteRegStr HKCU "Software\SyncThing" "" $INSTDIR
;Create uninstaller
WriteUninstaller "$INSTDIR\Uninstall.exe"
SectionEnd
Section "Command Line Interface" SecSyncThingCLI
SetOutPath "$INSTDIR"
File /r "${SOURCEPATH}\syncthing-cli.exe"
SectionEnd
Section "Windows Service" SecSyncThingService
SetOutPath "$INSTDIR"
File /r "${SOURCEPATH}\syncthingservice.exe"
File /r "${SOURCEPATH}\syncthingservice.xml"
ExecWait 'syncthingservice.exe install'
ExecWait 'syncthingservice.exe start'
SectionEnd
;--------------------------------
;Descriptions
;Language strings
LangString DESC_SecSyncThing ${LANG_ENGLISH} "SyncThing"
LangString DESC_SecSyncThingCLI ${LANG_ENGLISH} "Command Line Interface"
LangString DESC_SecSyncThingService ${LANG_ENGLISH} "Windows Service"
;Assign language strings to sections
!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
!insertmacro MUI_DESCRIPTION_TEXT ${SecSyncThing} $(DESC_SecSyncThing)
!insertmacro MUI_DESCRIPTION_TEXT ${SecSyncThingCLI} $(DESC_SecSyncThingCLI)
!insertmacro MUI_DESCRIPTION_TEXT ${SecSyncThingService} $(DESC_SecSyncThingService)
!insertmacro MUI_FUNCTION_DESCRIPTION_END
;--------------------------------
;Uninstaller Section
Section "Uninstall"
Delete "$INSTDIR\Uninstall.exe"
RMDir "$INSTDIR"
DeleteRegKey /ifempty HKCU "Software\SyncThing"
SectionEnd

View File

@@ -0,0 +1,8 @@
<service>
<id>Syncthing</id>
<name>Syncthing</name>
<description>This service runs Syncthing</description>
<executable>syncthing.exe</executable>
<arguments>-home .\ServiceHome -no-console -no-browser</arguments>
<logmode>rotate</logmode>
</service>

View File

@@ -16,18 +16,18 @@
"Changelog": "Сьпіс зьменаў",
"Close": "Зачыніць",
"Comment, when used at the start of a line": "Comment, when used at the start of a line",
"Compression is recommended in most setups.": "Compression is recommended in most setups.",
"Compression is recommended in most setups.": "Сьцісканьне рэкамэндавана ў большасьці выпадкаў.",
"Connection Error": "Connection Error",
"Copied from elsewhere": "Copied from elsewhere",
"Copied from original": "Copied from original",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Copyright © 2014 Jakob Borg and the following Contributors:",
"Delete": "Выдаліць",
"Device ID": "ID прылады",
"Device Identification": "Device Identification",
"Device Identification": "Ідэнтыфікацыя прылады",
"Device Name": "Назва прылады",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
"Devices": "Прылады",
"Disconnected": "Disconnected",
"Disconnected": "Адлучана",
"Documentation": "Дакумэнтацыя",
"Download Rate": "Хуткасьць спампоўваньня",
"Downloaded": "Downloaded",
@@ -40,7 +40,7 @@
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.",
"Enter ignore patterns, one per line.": "Enter ignore patterns, one per line.",
"Error": "Error",
"File Versioning": "File Versioning",
"File Versioning": "Вэрсіі файлаў",
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "File permission bits are ignored when looking for changes. Use on FAT filesystems.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.": "Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.",
@@ -51,34 +51,35 @@
"GUI Authentication Password": "GUI Authentication Password",
"GUI Authentication User": "GUI Authentication User",
"GUI Listen Addresses": "GUI Listen Addresses",
"Generate": "Generate",
"Global Discovery": "Global Discovery",
"Global Discovery Server": "Global Discovery Server",
"Generate": "Сгенераваць",
"Global Discovery": "Глябальнае вызначэньне",
"Global Discovery Server": "Сэрвер глябальнага вызначэньня",
"Global State": "Глябальны стан",
"Idle": "Idle",
"Ignore": "Ignore",
"Ignore Patterns": "Ignore Patterns",
"Ignore Patterns": "Ігнараваць шаблёны",
"Ignore Permissions": "Ігнараваць правы",
"Incoming Rate Limit (KiB/s)": "Incoming Rate Limit (KiB/s)",
"Introducer": "Introducer",
"Inversion of the given condition (i.e. do not exclude)": "Inversion of the given condition (i.e. do not exclude)",
"Keep Versions": "Keep Versions",
"Last File Received": "Last File Received",
"Last File Synced": "Апошні файл",
"Last seen": "Last seen",
"Keep Versions": "Трымаць вэрсій",
"Last File Received": "Апошні атрыманы файл",
"Last File Synced": "Апошні сынхранізаваны файл",
"Last seen": "Апошні раз бачылі",
"Later": "Later",
"Latest Release": "Latest Release",
"Local Discovery": "Local Discovery",
"Local Discovery": "Лякальнае вызначэньне",
"Local State": "Лякальны стан",
"Maximum Age": "Maximum Age",
"Move to top of queue": "Move to top of queue",
"Multi level wildcard (matches multiple directory levels)": "Multi level wildcard (matches multiple directory levels)",
"Never": "Never",
"Never": "Ніколі",
"New Device": "New Device",
"New Folder": "New Folder",
"No": "No",
"No File Versioning": "No File Versioning",
"No": "Не",
"No File Versioning": "Не захоўваць вэрсіі",
"Notice": "Notice",
"OK": "OK",
"OK": "Добра",
"Offline": "Offline",
"Online": "Online",
"Out Of Sync": "Несынхранізавана",
@@ -93,39 +94,40 @@
"Quick guide to supported patterns": "Quick guide to supported patterns",
"RAM Utilization": "Выкарыстаньне памяці",
"Rescan": "Перачытаць",
"Rescan All": "Rescan All",
"Rescan Interval": "Інтэрвал перачытваньня",
"Restart": "Перастартаваць",
"Restart Needed": "Патрэбна перастартоўваньне",
"Restarting": "Перастартоўваньне",
"Reused": "Reused",
"Save": "Захаваць",
"Scanning": "Scanning",
"Select the devices to share this folder with.": "Select the devices to share this folder with.",
"Select the folders to share with this device.": "Select the folders to share with this device.",
"Scanning": "Скануецца",
"Select the devices to share this folder with.": "Пазначце прылады зь якімі трэба абагуліць гэты каталёг.",
"Select the folders to share with this device.": "Пазначце каталёгі якія трэба абагуліць з гэтай прыладай.",
"Settings": "Налады",
"Share": "Share",
"Share": "Абагуліць",
"Share Folder": "Share Folder",
"Share Folders With Device": "Share Folders With Device",
"Share With Devices": "Share With Devices",
"Share this folder?": "Share this folder?",
"Shared With": "Shared With",
"Share With Devices": "Абагуліць з прыладамі",
"Share this folder?": "Абагуліць гэты каталёг ?",
"Shared With": "Абагульны з",
"Short identifier for the folder. Must be the same on all cluster devices.": "Short identifier for the folder. Must be the same on all cluster devices.",
"Show ID": "Паказаць ID",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.",
"Shutdown": "Выключыць",
"Shutdown Complete": "Выключэньне завершанае",
"Simple File Versioning": "Simple File Versioning",
"Simple File Versioning": "Простае захоўваньне вэрсій",
"Single level wildcard (matches within a directory only)": "Single level wildcard (matches within a directory only)",
"Source Code": "Зыходнікі",
"Staggered File Versioning": "Staggered File Versioning",
"Staggered File Versioning": "Адаптыўнае захоўваньне вэрсій",
"Start Browser": "Start Browser",
"Stopped": "Stopped",
"Support": "Падтрымка",
"Support / Forum": "Падтрымка / Форум",
"Sync Protocol Listen Addresses": "Sync Protocol Listen Addresses",
"Synchronization": "Сынхранізацыя",
"Syncing": "Syncing",
"Syncing": "Сынхранізуецца",
"Syncthing has been shut down.": "Syncthing has been shut down.",
"Syncthing includes the following software or portions thereof:": "Syncthing includes the following software or portions thereof:",
"Syncthing is restarting.": "Syncthing перастартоўвае.",
@@ -145,7 +147,7 @@
"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.": "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.",
"The maximum age must be a number and cannot be blank.": "The maximum age must be a number and cannot be blank.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "The maximum time to keep a version (in days, set to 0 to keep versions forever).",
"The number of old versions to keep, per file.": "The number of old versions to keep, per file.",
"The number of old versions to keep, per file.": "Колькі старых вэрсій трымаць, для кожнага файлу.",
"The number of versions must be a number and cannot be blank.": "The number of versions must be a number and cannot be blank.",
"The rescan interval must be a non-negative number of seconds.": "The rescan interval must be a non-negative number of seconds.",
"The rescan interval must be at least 5 seconds.": "The rescan interval must be at least 5 seconds.",
@@ -154,16 +156,16 @@
"Unused": "Unused",
"Up to Date": "Найноўшае",
"Upgrade To {%version%}": "Upgrade To {{version}}",
"Upgrading": "Upgrading",
"Upgrading": "Абнаўленьне",
"Upload Rate": "Хуткасьць запампоўваньня",
"Use Compression": "Use Compression",
"Use Compression": "Выкарыстоўваць сьцісканьне",
"Use HTTPS for GUI": "Use HTTPS for GUI",
"Version": "Вэрсія",
"Versions Path": "Versions Path",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "When adding a new device, keep in mind that this device must be added on the other side too.",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.",
"Yes": "Yes",
"Yes": "Так",
"You must keep at least one version.": "You must keep at least one version.",
"full documentation": "full documentation",
"items": "складнікаў",

View File

@@ -71,6 +71,7 @@
"Local Discovery": "Локално Откриване",
"Local State": "Локално състояние",
"Maximum Age": "Максимална Възраст",
"Move to top of queue": "Move to top of queue",
"Multi level wildcard (matches multiple directory levels)": "Маска на много нива (покрива папки с много нива)",
"Never": "Никога",
"New Device": "Ново устройство",
@@ -93,6 +94,7 @@
"Quick guide to supported patterns": "Бърз наръчник към поддържаните шаблони",
"RAM Utilization": "RAM Натоварване",
"Rescan": "Повторно Сканиране",
"Rescan All": "Rescan All",
"Rescan Interval": "Интервал за Повторно Сканиране",
"Restart": "Рестартирай",
"Restart Needed": "Изискава се Рестартиране",

View File

@@ -71,6 +71,7 @@
"Local Discovery": "Descobriment Local",
"Local State": "Estat local",
"Maximum Age": "Antiguitat Màxima",
"Move to top of queue": "Move to top of queue",
"Multi level wildcard (matches multiple directory levels)": "Caràcter comodí de nivell múltiple (aparella en carpetes de nivells múltiples)",
"Never": "Mai",
"New Device": "Nou dispositiu",
@@ -93,6 +94,7 @@
"Quick guide to supported patterns": "Guia ràpida per als possibles patrons",
"RAM Utilization": "Utilització de la RAM",
"Rescan": "Re-escanejar",
"Rescan All": "Rescan All",
"Rescan Interval": "Interval de re-escaneig",
"Restart": "Reiniciar",
"Restart Needed": "És Necessari Reiniciar",

View File

@@ -9,7 +9,7 @@
"Addresses": "Adresy",
"Allow Anonymous Usage Reporting?": "Povolit anonymní hlášení o používání?",
"Anonymous Usage Reporting": "Anonymní hlášení o používání",
"Any devices configured on an introducer device will be added to this device as well.": "Jakékoliv přístroje nakonfigurované na zavaděči budou přidány na tento přístroj také.",
"Any devices configured on an introducer device will be added to this device as well.": "Jakékoliv přístroje nakonfigurované na zavaděči budou přidány také na tento přístroj.",
"Automatic upgrades": "Automatický upgrade",
"Bugs": "Chyby",
"CPU Utilization": "Využití CPU",
@@ -27,7 +27,7 @@
"Device Name": "Jméno přístroje",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Přístroj {{device}} ({{address}}) žádá o připojení. Chcete ho přidat?",
"Devices": "Přístroje",
"Disconnected": "Odpojeno",
"Disconnected": "Odpojen",
"Documentation": "Dokumentace",
"Download Rate": "Rychlost stahování",
"Downloaded": "Staženo",
@@ -37,13 +37,13 @@
"Edit Folder": "Upravit adresář",
"Editing": "Upravuje se",
"Enable UPnP": "Povolit UPnP",
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Vlož čárkou oddělené adresy \"ip:port\" nebo \"dynamic\" pro automatické zjišní adres. ",
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Vlož čárkou oddělené adresy \"ip:port\" nebo \"dynamic\" (pro automatické zjišťování adres). ",
"Enter ignore patterns, one per line.": "Vložit ignorované vzory, jeden na řádek.",
"Error": "Chyba",
"File Versioning": "Verze souborů",
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "Bity označující práva souborů jsou ignorovány při hledání změn. Použít pro souborové systémy FAT.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.": "Soubory jsou přesunuty do verzí označených daty v adresáři .stversions po nahrazení nebo smazání aplikací syncthing.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Soubory jsou chráněny před změnami na ostatních přístrojích, ale změny provedené z tohoto umístění budou rozeslány na zbytek clusteru.",
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "Bity označující práva souborů jsou při hledání změn ignorovány. Použít pro souborové systémy FAT.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.": "Po nahrazení nebo smazání aplikací Syncthing jsou soubory přesunuty do verzí označených daty v adresáři .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.": "Soubory jsou chráněny před změnami na ostatních přístrojích, ale změny provedené z tohoto ístroje budou rozeslány na zbytek clusteru.",
"Folder ID": "ID adresáře",
"Folder Master": "Master adresář",
"Folder Path": "Cesta k adresáři",
@@ -63,14 +63,15 @@
"Introducer": "Zavaděč",
"Inversion of the given condition (i.e. do not exclude)": "Prohození zadané podmínky (např. nevynechat)",
"Keep Versions": "Ponechat verze",
"Last File Received": "Poslední soubor přijat",
"Last File Synced": "Poslední soubor synchronizován",
"Last File Received": "Poslední přijatý soubor",
"Last File Synced": "Poslední synchronizovaný soubor",
"Last seen": "Naposledy spatřen",
"Later": "Později",
"Latest Release": "Poslední vydání",
"Local Discovery": "Místní oznamování",
"Local State": "Místní status",
"Maximum Age": "Maximální časový limit",
"Move to top of queue": "Přesunout na začátek fronty",
"Multi level wildcard (matches multiple directory levels)": "Víceúrovňový zástupný znak (shoda skrz více úrovní adresářů)",
"Never": "Nikdy",
"New Device": "Nový přístroj",
@@ -93,6 +94,7 @@
"Quick guide to supported patterns": "Rychlá nápověda k podporovaným vzorům",
"RAM Utilization": "Využití RAM",
"Rescan": "Opakovat skenování",
"Rescan All": "Opakovat skenování všech",
"Rescan Interval": "Interval opakování skenování",
"Restart": "Restart",
"Restart Needed": "Je nutný restart",
@@ -100,21 +102,21 @@
"Reused": "Opakovaně použité",
"Save": "Uložit",
"Scanning": "Skenování",
"Select the devices to share this folder with.": "Vybrat přístroje se kterými sdílet tento adresář.",
"Select the folders to share with this device.": "Vybrat adresáře sdílené tomuto přístroji.",
"Select the devices to share this folder with.": "Vybrat přístroje, se kterými sdílet tento adresář.",
"Select the folders to share with this device.": "Vybrat adresáře sdílené s tímto přístrojem.",
"Settings": "Nastavení",
"Share": "Sdílet",
"Share Folder": "Sdílet adresář",
"Share Folders With Device": "Sdílet adresáře tomuto přístroji",
"Share Folders With Device": "Sdílet adresáře s tímto přístrojem",
"Share With Devices": "Sdílet s přístroji",
"Share this folder?": "Sdílet tento adresář?",
"Shared With": "Sdíleno s",
"Short identifier for the folder. Must be the same on all cluster devices.": "Krátký identifikátor tohoto adresáře. Musí být stejný na všech přístrojích v clusteru.",
"Show ID": "Zobrazit ID",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Zobrazeno místo ID přístroje na náhledu stavu clusteru. Bude odesíláno ostatním přístrojům jako dodatečné výchozí jméno.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Zobrazeno místo ID přístroje na náhledu stavu clusteru. Bude aktualizováno na jméno které přístroj odesílá pokud nebylo vyplněno.",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Zobrazeno místo ID přístroje na náhledu stavu clusteru. Bude odesíláno ostatním přístrojům jako výchozí jméno přístroje.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Zobrazeno místo ID přístroje na náhledu stavu clusteru. Pokud nebude vyplněno, bude nastaveno na jméno, které přístroj odesílá.",
"Shutdown": "Vypnout",
"Shutdown Complete": "Vypnutí ukončeno",
"Shutdown Complete": "Vypnutí dokončeno",
"Simple File Versioning": "Jednoduché verze souborů",
"Single level wildcard (matches within a directory only)": "Jednoúrovňový zástupný znak (shody pouze uvnitř adresáře)",
"Source Code": "Zdrojový kód",
@@ -135,9 +137,9 @@
"The aggregated statistics are publicly available at {%url%}.": "Souhrnné statistiky jsou veřejně dostupné na {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Konfigurace byla uložena, ale není aktivována. Pro aktivaci nové konfigurace je třeba restartovat Syncthing.",
"The device ID cannot be blank.": "ID přístroje nemůže být prázdné.",
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "ID přístroje, které je třeba vložit, lze nalézt v dialogu \"Upravit > Zobrazit ID\" na druhém přístroji. Mezery a pomlčky nejsou nutné (ignorovány).",
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "ID přístroje, které je třeba vložit, lze nalézt v dialogu \"Upravit > Zobrazit ID\" na druhém přístroji. Mezery a pomlčky nejsou nutné (budou ignorovány).",
"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.": "Šifrovaná data o využití jsou zasílána denně. Jsou používána pro zjištění nejobvyklejších platforem, velikosti adresářů a verzí aplikace. Pokud se hlášená data změní, budete opět upozorněni tímto dialogem.",
"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.": "Zadané ID přístroje není platné. Mělo by mít 52 nebo 56 znaků, být složeno z písmen a čísel s nepovinnými mezerami a pomlčkami.",
"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.": "Zadané ID přístroje není platné. Mělo by mít 52 nebo 56 znaků a mělo by obsahovat písmena a čísla. Mezery a pomlčky jsou nepovinné.",
"The folder ID cannot be blank.": "ID adresáře nemůže být prázdné.",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "ID adresáře musí být stručný popisek (64 znaků nebo méně) obsahující pouze písmena, čísla, tečku (.), pomlčku (-) a nebo podtržítko (_).",
"The folder ID must be unique.": "ID adresáře musí být unikátní.",
@@ -147,11 +149,11 @@
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Maximální doba pro zachování verze (dny, zapsáním hodnoty 0 bude ponecháno navždy).",
"The number of old versions to keep, per file.": "Počet starších verzí k zachování pro každý soubor.",
"The number of versions must be a number and cannot be blank.": "Počet verzí musí být číslo a nemůže být prázdné.",
"The rescan interval must be a non-negative number of seconds.": "Interval pro opakování skenování musí být pozitivní číslo.",
"The rescan interval must be a non-negative number of seconds.": "Interval opakování skenování musí být pozitivní číslo.",
"The rescan interval must be at least 5 seconds.": "Interval opakování skenování musí být delší než 5 sekund.",
"Unknown": "Neznámý",
"Unshared": "Nesdílené",
"Unused": "Nepoužité",
"Unshared": "Nesdílený",
"Unused": "Nepoužitý",
"Up to Date": "Aktuální",
"Upgrade To {%version%}": "Aktualizovat na {{version}}",
"Upgrading": "Aktualizuji",
@@ -160,9 +162,9 @@
"Use HTTPS for GUI": "Použít HTTPS pro grafické rozhraní",
"Version": "Verze",
"Versions Path": "Cesta verzí",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Verze jsou automaticky smazány pokud jsou starší než maximální časový limit nebo překročí počet souborů povolených pro interval.",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Verze jsou automaticky smazány, pokud jsou starší než maximální časový limit nebo překročí počet souborů povolených pro interval.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Při přidávání nového přístroje mějte na paměti, že je ho třeba také zadat na druhé straně.",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Při přidávání nového adresáře mějte na paměti, že jeho ID je použito ke svázání adresářů napříč přístoji. Rozlišují malá a velká písmena a musí přesně souhlasit mezi všemi přístroji.",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Při přidávání nového adresáře mějte na paměti, že jeho ID je použito ke svázání adresářů napříč přístoji. Rozlišují se malá a velká písmena a musí přesně souhlasit mezi všemi přístroji.",
"Yes": "Ano",
"You must keep at least one version.": "Je třeba ponechat alespoň jednu verzi.",
"full documentation": "plná dokumentace",

View File

@@ -71,6 +71,7 @@
"Local Discovery": "Lokale Auffindung",
"Local State": "Lokaler Status",
"Maximum Age": "Höchstalter",
"Move to top of queue": "An den Anfang der Warteschlange setzen",
"Multi level wildcard (matches multiple directory levels)": "Verschachteltes Maskenzeichen (wird für verschachtelte Verzeichnisse verwendet)",
"Never": "Nie",
"New Device": "Neues Gerät",
@@ -93,6 +94,7 @@
"Quick guide to supported patterns": "Schnellanleitung zu den unterstützten Suchstrukturen",
"RAM Utilization": "RAM Auslastung",
"Rescan": "Überprüfen",
"Rescan All": "Alle überprüfen",
"Rescan Interval": "Suchintervall",
"Restart": "Neustart",
"Restart Needed": "Neustart notwendig",

View File

@@ -0,0 +1,173 @@
{
"API Key": "Κλειδί API",
"About": "Σχετικά με το Syncthing",
"Add": "Προσθήκη",
"Add Device": "Προσθήκη συσκευής",
"Add Folder": "Προσθήκη φακέλου",
"Add new folder?": "Προσθήκη νέου φακέλου;",
"Address": "Διεύθυνση",
"Addresses": "Διευθύνσεις",
"Allow Anonymous Usage Reporting?": "Να επιτρέπεται η αποστολή ανώνυμων στοιχείων χρήσης;",
"Anonymous Usage Reporting": "Ανώνυμα στοιχεία χρήσης",
"Any devices configured on an introducer device will be added to this device as well.": "Αν δηλωθεί σαν «βασικός κόμβος», τότε όλες οι συσκευές που είναι δηλωμένες εκεί θα υπάρχουν και στον τοπικό κόμβο.",
"Automatic upgrades": "Αυτόματη αναβάθμιση",
"Bugs": "Bugs",
"CPU Utilization": "Επιβάρυνση του επεξεργαστή",
"Changelog": "Πληροφορίες εκδόσεων",
"Close": "Τέλος",
"Comment, when used at the start of a line": "Σχόλιο, όταν χρησιμοποιείται στην αρχή μιας γραμμής",
"Compression is recommended in most setups.": "Η συμπίεση προτείνεται στις περισσότερες εγκαταστάσεις",
"Connection Error": "Σφάλμα σύνδεσης",
"Copied from elsewhere": "Έχει αντιγραφεί από κάπου αλλού",
"Copied from original": "Έχει αντιγραφεί από το πρωτότυπο",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Copyright © 2014 από τον Jakob Borg και τους παρακάτω συνεισφορείς:",
"Delete": "Διαγραφή",
"Device ID": "Ταυτότητα συσκευής",
"Device Identification": "Ταυτότητα συσκευής",
"Device Name": "Όνομα συσκευής",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Η συσκευή {{device}} ({{address}}) επιθυμεί να συνδεθεί. Προσθήκη της νέας συσκευής1",
"Devices": "Συσκευές",
"Disconnected": "Αποσυνδεδεμένος",
"Documentation": "Τεκμηρίωση",
"Download Rate": "Ταχύτητα λήψης",
"Downloaded": "Έχει ληφθεί",
"Downloading": "Λήψη",
"Edit": "Επεξεργασία",
"Edit Device": "Επεξεργασία συσκευής",
"Edit Folder": "Επεξεργασία φακέλου",
"Editing": "Επεξεργασία σε εξέλιξη",
"Enable UPnP": "Ενεργοποίηση UPnP",
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Γράψε τις διευθύνσεις IP με τη μορφή «ip:θύρα», διαχωρισμένες με κόμμα. Αλλιώς γράψε «dynamic» για να πραγματοποιηθεί η αυτόματη εύρεση διευθύνσεων.",
"Enter ignore patterns, one per line.": "Δώσε τα πρότυπα που θα αγνοηθούν, ένα σε κάθε γραμμή.",
"Error": "Σφάλμα",
"File Versioning": "Τήρηση εκδόσεων",
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "Να αγνοούνται τα δικαιώματα των αρχείων όταν γίνεται αναζήτηση για αλλαγές. Χρησιμοποιείται σε συστήματα αρχείων τύπου FAT.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.": "Όταν τα αρχεία αντικατασταθούν ή διαγραφούν από το syncthing, τότε να μεταφέρονται χρονοσημασμένα σε φάκελο με το όνομα .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.": "Τα αρχεία προστατεύονται από αλλαγές που γίνονται σε άλλες συσκευές, αλλά όποιες αλλαγές γίνουν σε αυτή τη συσκευή θα αποσταλούν σε όλη τη συστάδα συσκευών.",
"Folder ID": "Ταυτότητα φακέλου",
"Folder Master": "Να μην επιτρέπονται αλλαγές",
"Folder Path": "Μονοπάτι φακέλου",
"Folders": "Φάκελοι",
"GUI Authentication Password": "Κωδικός για την πρόσβαση στη διεπαφή",
"GUI Authentication User": "Χρηστώνυμο για την πρόσβαση στη διεπαφή",
"GUI Listen Addresses": "Διευθύνσεις από τις οποίες θα είναι προσβάσιμη η διεπαφή",
"Generate": "Δημιουργία",
"Global Discovery": "Να μπορεί να ανευρεθεί καθολικά (από παντού)",
"Global Discovery Server": "Διακομιστής καθολικής ανεύρεσης κόμβου",
"Global State": "Καθολική κατάσταση",
"Idle": "Ανενεργό",
"Ignore": "Αγνόησε",
"Ignore Patterns": "Πρότυπο για αγνόηση",
"Ignore Permissions": "Αγνόησε τα δικαιώματα",
"Incoming Rate Limit (KiB/s)": "Περιορισμός ταχύτητας λήψης (KiB/δ)",
"Introducer": "Βασικός κόμβος",
"Inversion of the given condition (i.e. do not exclude)": "Αντιστροφή της δοσμένης συνθήκης (π.χ. να μην εξαιρείς) ",
"Keep Versions": "Διατήρηση εκδόσεων",
"Last File Received": "Πιο πρόσφατο αρχείο",
"Last File Synced": "Τελευταία αλλαγή",
"Last seen": "Τελευταία φορά συνδεδεμένος",
"Later": "Αργότερα",
"Latest Release": "Τελευταία έκδοση",
"Local Discovery": "Τοπική ανεύρεση",
"Local State": "Τοπική κατάσταση",
"Maximum Age": "Μέγιστη ηλικία",
"Move to top of queue": "Μεταφορά στην αρχή της λίστας",
"Multi level wildcard (matches multiple directory levels)": "Τελεστής μπαλαντέρ (*) για πολλά επίπεδα (χρησιμοποιείται για εμφωλευμένους φακέλους)",
"Never": "Ποτέ",
"New Device": "Νέα συσκευή",
"New Folder": "Νέος φάκελος",
"No": "Όχι",
"No File Versioning": "Να μην τηρούνται εκδόσεις",
"Notice": "Σημείωση",
"OK": "OK",
"Offline": "Εκτός σύνδεσης",
"Online": "Συνδεδεμένο",
"Out Of Sync": "Μη συγχρονισμένα",
"Out of Sync Items": "Μη συγχρονισμένα αντικείμενα",
"Outgoing Rate Limit (KiB/s)": "Ρυθμός αποστολής (KiB/s)",
"Override Changes": "Να αντικατασταθούν οι αλλαγές",
"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": "Μονοπάτι του φακέλου σε αυτόν τον υπολογιστή. Αν δεν υπάρχει θα δημιουργηθεί. Η περισπωμένη (~) μπορεί να μπει σαν συντόμευση για το",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Ο φάκελος στον οποίο θα αποθηκεύονται οι εκδόσεις των αρχείων (αν δεν οριστεί θα αποθηκεύονται στον υποφάκελο .stversions)",
"Please wait": "Παρακαλώ περιμένετε",
"Preview": "Προεπισκόπηση",
"Preview Usage Report": "Προεπισκόπηση αναφοράς χρήσης",
"Quick guide to supported patterns": "Σύντομη βοήθεια σχετικά με τα πρότυπα αναζήτησης που υποστηρίζονται",
"RAM Utilization": "Επιβάρυνση RAM",
"Rescan": "Έλεγξε για αλλαγές",
"Rescan All": "Έλεγξέ τα όλα για αλλαγές",
"Rescan Interval": "Κάθε πότε θα ελέγχεται για αλλαγές ",
"Restart": "Επανεκκίνηση",
"Restart Needed": "Απαιτείται επανεκκίνηση",
"Restarting": "Επανεκκίνηση",
"Reused": "Χρησιμοποιήθηκε ξανά",
"Save": "Αποθήκευση",
"Scanning": "Έλεγχος για αλλαγές",
"Select the devices to share this folder with.": "Διάλεξε τις συσκευές προς τις οποίες θα διαμοιράζεται αυτός ο φάκελος.",
"Select the folders to share with this device.": "Διάλεξε ποιοι φάκελοι θα διαμοιράζονται προς αυτή τη συσκευή.",
"Settings": "Ρυθμίσεις",
"Share": "Διαμοίραση",
"Share Folder": "Διαμοίραση φακέλου",
"Share Folders With Device": "Διαμοίρασε τους φακέλους προς αυτή τη συσκευή",
"Share With Devices": "Διαμοίρασε με αυτές τις συσκευές",
"Share this folder?": "Να διαμοιραστεί αυτός ο φάκελος;",
"Shared With": "Διαμοιράζεται με",
"Short identifier for the folder. Must be the same on all cluster devices.": "Σύντομη ταυτότητα για το φάκελο. Θα πρέπει να είναι η ίδια σε όλες τις συσκευές με τις οποίες διαμοιράζεται ο φάκελος αυτός.",
"Show ID": "Εμφάνιση ταυτότητας",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Θα φαίνεται αντί για την ταυτότητα της συσκευής στην προβολή της κατάστασης ολόκληρης της συστάδας. Θα γνωστοποιείται σαν το προαιρετικό όνομα της συσκευής.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Θα φαίνεται αντί για την ταυτότητα της συσκευής στην προβολή της κατάστασης ολόκληρης της συστάδας. Θα ενημερώνεται αυτόματα αν αλλάξει το όνομα της συσκευής.",
"Shutdown": "Απενεργοποίηση",
"Shutdown Complete": "Πλήρης απενεργοποίηση",
"Simple File Versioning": "Απλή τήρηση εκδόσεων",
"Single level wildcard (matches within a directory only)": "Τελεστής μπαλαντέρ (*) για ένα επίπεδο (χρησιμοποιείται για έναν φάκελο μόνο)",
"Source Code": "Πηγαίος κώδικας",
"Staggered File Versioning": "Να τηρούνται κλιμακούμενες εκδόσεις",
"Start Browser": "Εκκίνηση προγράμματος περιήγησης",
"Stopped": "Απενεργοποιημένο",
"Support": "Υποστήριξη",
"Support / Forum": "Υποστήριξη / Φόρουμ",
"Sync Protocol Listen Addresses": "Διευθύνσεις για το πρωτόκολλο συγχρονισμού",
"Synchronization": "Συγχρονισμός",
"Syncing": "Συγχρονίζω",
"Syncthing has been shut down.": "Το Syncthing έχει απενεργοποιηθεί.",
"Syncthing includes the following software or portions thereof:": "Το Syncthing περιλαμβάνει τα παρακάτω λογισμικά ή μέρη αυτών:",
"Syncthing is restarting.": "Το Syncthing επανεκκινείται.",
"Syncthing is upgrading.": "Το Syncthing αναβαθμίζεται.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Το Syncthing φαίνεται πως είναι απενεργοποιημένο ή υπάρχει πρόβλημα στη σύνδεσή σου στο διαδίκτυο. Προσπαθώ πάλι…",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Φαίνεται πως το Syncthing έχει κάποιο πρόβλημα με αυτό που ζήτησες. Αν το πρόβλημα επιμείνει φόρτωσε ξανά τη σελίδα ή επανεκκίνησε το Syncthing.",
"The aggregated statistics are publicly available at {%url%}.": "Τα στατιστικά που έχουν συλλεγεί είναι δημόσια διαθέσιμα στο {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Οι ρυθμίσεις έχουν αποθηκευτεί αλλά δεν έχουν ενεργοποιηθεί. Πρέπει να επανεκκινήσεις το Syncthing για να ισχύσουν οι νέες ρυθμίσεις.",
"The device ID cannot be blank.": "Η ταυτότητα της συσκευής δεν μπορεί να είναι κενή",
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "Η ταυτότητα της συσκευής που θα μπει εδώ βρίσκεται στο μενού «Επεξεργασία > Εμφάνιση ταυτότητας» στην άλλη συσκευή. Κενοί χαρακτήρες και παύλες είναι προαιρετικοί (απλά θα αγνοηθούν).",
"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.": "Η κρυπτογραφημένη αναφορά χρήσης στέλνεται καθημερινά. Χρησιμοποιείται για να παραχθούν στατιστικές για τα λειτουργικά συστήματα που χρησιμοποιούνται, τα μεγέθη των φακέλων και τις εκδόσεις των προγραμμάτων. Αν στο μέλλον συμπεριληφθούν και άλλα δεδομένα στην αναφορά χρήσης, τότε αυτό το παράθυρο θα εμφανιστεί ξανά.",
"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.": "Η ταυτότητα συσκευής που έδωσες δε φαίνεται έγκυρη. Θα πρέπει να είναι μια σειρά από 52 ή 56 χαρακτήρες (γράμματα και αριθμοί). Τα κενά και οι παύλες είναι προαιρετικά (αδιάφορα).",
"The folder ID cannot be blank.": "Η ταυτότητα του φακέλου δεν μπορεί να είναι κενή.",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "Η ταυτότητα του φακέλου πρέπει να είναι ένα σύντομο αναγνωριστικό (το πολύ 64 χαρακτήρες). Μπορεί να αποτελείται από γράμματα, αριθμούς, την τελεία (.), την παύλα (-) και την κάτω παύλα (_).",
"The folder ID must be unique.": "Η ταυτότητα του φακέλου πρέπει να είναι μοναδική.",
"The folder path cannot be blank.": "Το μονοπάτι του φακέλου δεν μπορεί να είναι κενό.",
"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.": "Θα χρησιμοποιούνται τα εξής διαστήματα: Την πρώτη ώρα θα τηρείται μια έκδοση κάθε 30 δευτερόλεπτα. Την πρώτη ημέρα, μια έκδοση κάθε μια ώρα. Τις πρώτες 30 ημέρες, μία έκδοση κάθε ημέρα. Από εκεί και έπειτα μέχρι τη μέγιστη ηλικία, θα τηρείται μια έκδοση κάθε εβδομάδα.",
"The maximum age must be a number and cannot be blank.": "Η μέγιστη ηλικία πρέπει να είναι αριθμός και σίγουρα όχι κενό.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Η μέγιστη ηλικία παλιότερων εκδόσεων (σε ημέρες, αν δώσεις 0 οι παλιότερες εκδόσεις θα διατηρούνται για πάντα).",
"The number of old versions to keep, per file.": "Πόσες παλιότερες εκδόσεις θα διατηρούνται, ανά αρχείο.",
"The number of versions must be a number and cannot be blank.": "Ο αριθμός εκδόσεων πρέπει να είναι αριθμός και σίγουρα όχι κενό.",
"The rescan interval must be a non-negative number of seconds.": "Ο χρόνος επανελέγχου για αλλαγές είναι σε δευτερόλεπτα (δηλ. θετικός αριθμός).",
"The rescan interval must be at least 5 seconds.": "Ο χρόνος επανελέγχου πρέπει να είναι πάνω από 5 δευτερόλεπτα.",
"Unknown": "Άγνωστο",
"Unshared": "Δεν μοιράζεται",
"Unused": "Δεν χρησιμοποιείται",
"Up to Date": "Ενημερωμένος",
"Upgrade To {%version%}": "Αναβάθμιση στην έκδοση {{version}}",
"Upgrading": "Αναβάθμιση",
"Upload Rate": "Ταχύτητα ανεβάσματος",
"Use Compression": "Χρήση συμπίεσης",
"Use HTTPS for GUI": "Χρήση HTTPS για τη διεπαφή",
"Version": "Έκδοση",
"Versions Path": "Φάκελος τήρησης εκδόσεων",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Οι παλιές εκδόσεις θα σβήνονται αυτόματα όταν ξεπεράσουν τη μέγιστη ηλικία ή όταν ξεπεραστεί ο μέγιστος αριθμός αρχείων ανά περίοδο.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Θυμήσου πως όταν προσθέτεις μια νέα συσκευή, ετούτη η συσκευή θα πρέπει να προστεθεί και στην άλλη πλευρά.",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Όταν προσθέτεις έναν νέο φάκελο, θυμήσου πως η ταυτότητα ενός φακέλου χρησιμοποιείται για να να συσχετίσει φακέλους μεταξύ συσκευών. Η ταυτότητα του φακέλου θα πρέπει να είναι η ίδια σε όλες τις συσκευές και έχουν σημασία τα πεζά ή κεφαλαία γράμματα.",
"Yes": "Ναι",
"You must keep at least one version.": "Πρέπει να τηρήσεις τουλάχιστον μια έκδοση.",
"full documentation": "πλήρης τεκμηρίωση",
"items": "εγγραφές",
"{%device%} wants to share folder \"{%folder%}\".": "Η συσκευή {{device}} θέλει να μοιράσει τον φάκελο «{{folder}}»."
}

View File

@@ -0,0 +1,173 @@
{
"API Key": "API Key",
"About": "About",
"Add": "Add",
"Add Device": "Add Device",
"Add Folder": "Add Folder",
"Add new folder?": "Add new folder?",
"Address": "Address",
"Addresses": "Addresses",
"Allow Anonymous Usage Reporting?": "Allow Anonymous Usage Reporting?",
"Anonymous Usage Reporting": "Anonymous Usage Reporting",
"Any devices configured on an introducer device will be added to this device as well.": "Any devices configured on an introducer device will be added to this device as well.",
"Automatic upgrades": "Automatic upgrades",
"Bugs": "Bugs",
"CPU Utilization": "CPU Utilisation",
"Changelog": "Changelog",
"Close": "Close",
"Comment, when used at the start of a line": "Comment, when used at the start of a line",
"Compression is recommended in most setups.": "Compression is recommended in most setups.",
"Connection Error": "Connection Error",
"Copied from elsewhere": "Copied from elsewhere",
"Copied from original": "Copied from original",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Copyright © 2014 Jakob Borg and the following Contributors:",
"Delete": "Delete",
"Device ID": "Device ID",
"Device Identification": "Device Identification",
"Device Name": "Device Name",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
"Devices": "Devices",
"Disconnected": "Disconnected",
"Documentation": "Documentation",
"Download Rate": "Download Rate",
"Downloaded": "Downloaded",
"Downloading": "Downloading",
"Edit": "Edit",
"Edit Device": "Edit Device",
"Edit Folder": "Edit Folder",
"Editing": "Editing",
"Enable UPnP": "Enable UPnP",
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.",
"Enter ignore patterns, one per line.": "Enter ignore patterns, one per line.",
"Error": "Error",
"File Versioning": "File Versioning",
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "File permission bits are ignored when looking for changes. Use on FAT filesystems.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.": "Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.",
"Folder ID": "Folder ID",
"Folder Master": "Folder Master",
"Folder Path": "Folder Path",
"Folders": "Folders",
"GUI Authentication Password": "GUI Authentication Password",
"GUI Authentication User": "GUI Authentication User",
"GUI Listen Addresses": "GUI Listen Addresses",
"Generate": "Generate",
"Global Discovery": "Global Discovery",
"Global Discovery Server": "Global Discovery Server",
"Global State": "Global State",
"Idle": "Idle",
"Ignore": "Ignore",
"Ignore Patterns": "Ignore Patterns",
"Ignore Permissions": "Ignore Permissions",
"Incoming Rate Limit (KiB/s)": "Incoming Rate Limit (KiB/s)",
"Introducer": "Introducer",
"Inversion of the given condition (i.e. do not exclude)": "Inversion of the given condition (i.e. do not exclude)",
"Keep Versions": "Keep Versions",
"Last File Received": "Last File Received",
"Last File Synced": "Last File Synced",
"Last seen": "Last seen",
"Later": "Later",
"Latest Release": "Latest Release",
"Local Discovery": "Local Discovery",
"Local State": "Local State",
"Maximum Age": "Maximum Age",
"Move to top of queue": "Move to top of queue",
"Multi level wildcard (matches multiple directory levels)": "Multi level wildcard (matches multiple directory levels)",
"Never": "Never",
"New Device": "New Device",
"New Folder": "New Folder",
"No": "No",
"No File Versioning": "No File Versioning",
"Notice": "Notice",
"OK": "OK",
"Offline": "Offline",
"Online": "Online",
"Out Of Sync": "Out of Sync",
"Out of Sync Items": "Out of Sync Items",
"Outgoing Rate Limit (KiB/s)": "Outgoing Rate Limit (KiB/s)",
"Override Changes": "Override Changes",
"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": "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",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Path where versions should be stored (leave empty for the default .stversions folder in the folder).",
"Please wait": "Please wait",
"Preview": "Preview",
"Preview Usage Report": "Preview Usage Report",
"Quick guide to supported patterns": "Quick guide to supported patterns",
"RAM Utilization": "RAM Utilisation",
"Rescan": "Rescan",
"Rescan All": "Rescan All",
"Rescan Interval": "Rescan Interval",
"Restart": "Restart",
"Restart Needed": "Restart Needed",
"Restarting": "Restarting",
"Reused": "Reused",
"Save": "Save",
"Scanning": "Scanning",
"Select the devices to share this folder with.": "Select the devices to share this folder with.",
"Select the folders to share with this device.": "Select the folders to share with this device.",
"Settings": "Settings",
"Share": "Share",
"Share Folder": "Share Folder",
"Share Folders With Device": "Share Folders With Device",
"Share With Devices": "Share With Devices",
"Share this folder?": "Share this folder?",
"Shared With": "Shared With",
"Short identifier for the folder. Must be the same on all cluster devices.": "Short identifier for the folder. Must be the same on all cluster devices.",
"Show ID": "Show ID",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.",
"Shutdown": "Shutdown",
"Shutdown Complete": "Shutdown Complete",
"Simple File Versioning": "Simple File Versioning",
"Single level wildcard (matches within a directory only)": "Single level wildcard (matches within a directory only)",
"Source Code": "Source Code",
"Staggered File Versioning": "Staggered File Versioning",
"Start Browser": "Start Browser",
"Stopped": "Stopped",
"Support": "Support",
"Support / Forum": "Support / Forum",
"Sync Protocol Listen Addresses": "Sync Protocol Listen Addresses",
"Synchronization": "Synchronisation",
"Syncing": "Syncing",
"Syncthing has been shut down.": "Syncthing has been shut down.",
"Syncthing includes the following software or portions thereof:": "Syncthing includes the following software or portions thereof:",
"Syncthing is restarting.": "Syncthing is restarting.",
"Syncthing is upgrading.": "Syncthing is upgrading.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please reload your browser or restart Syncthing if the problem persists.",
"The aggregated statistics are publicly available at {%url%}.": "The aggregated statistics are publicly available at {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.",
"The device ID cannot be blank.": "The device ID cannot be blank.",
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).",
"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.": "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.",
"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.": "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.",
"The folder ID cannot be blank.": "The folder ID cannot be blank.",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.",
"The folder ID must be unique.": "The folder ID must be unique.",
"The folder path cannot be blank.": "The folder path cannot be blank.",
"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.": "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.",
"The maximum age must be a number and cannot be blank.": "The maximum age must be a number and cannot be blank.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "The maximum time to keep a version (in days, set to 0 to keep versions forever).",
"The number of old versions to keep, per file.": "The number of old versions to keep, per file.",
"The number of versions must be a number and cannot be blank.": "The number of versions must be a number and cannot be blank.",
"The rescan interval must be a non-negative number of seconds.": "The rescan interval must be a non-negative number of seconds.",
"The rescan interval must be at least 5 seconds.": "The rescan interval must be at least 5 seconds.",
"Unknown": "Unknown",
"Unshared": "Unshared",
"Unused": "Unused",
"Up to Date": "Up to Date",
"Upgrade To {%version%}": "Upgrade to {{version}}",
"Upgrading": "Upgrading",
"Upload Rate": "Upload Rate",
"Use Compression": "Use Compression",
"Use HTTPS for GUI": "Use HTTPS for GUI",
"Version": "Version",
"Versions Path": "Versions Path",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "When adding a new device, keep in mind that this device must be added on the other side too.",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.",
"Yes": "Yes",
"You must keep at least one version.": "You must keep at least one version.",
"full documentation": "full documentation",
"items": "items",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\"."
}

View File

@@ -7,6 +7,7 @@
"Add new folder?": "Add new folder?",
"Address": "Address",
"Addresses": "Addresses",
"All Data": "All Data",
"Allow Anonymous Usage Reporting?": "Allow Anonymous Usage Reporting?",
"Anonymous Usage Reporting": "Anonymous Usage Reporting",
"Any devices configured on an introducer device will be added to this device as well.": "Any devices configured on an introducer device will be added to this device as well.",
@@ -16,6 +17,7 @@
"Changelog": "Changelog",
"Close": "Close",
"Comment, when used at the start of a line": "Comment, when used at the start of a line",
"Compression": "Compression",
"Compression is recommended in most setups.": "Compression is recommended in most setups.",
"Connection Error": "Connection Error",
"Copied from elsewhere": "Copied from elsewhere",
@@ -71,6 +73,8 @@
"Local Discovery": "Local Discovery",
"Local State": "Local State",
"Maximum Age": "Maximum Age",
"Metadata Only": "Metadata Only",
"Move to top of queue": "Move to top of queue",
"Multi level wildcard (matches multiple directory levels)": "Multi level wildcard (matches multiple directory levels)",
"Never": "Never",
"New Device": "New Device",
@@ -79,6 +83,7 @@
"No File Versioning": "No File Versioning",
"Notice": "Notice",
"OK": "OK",
"Off": "Off",
"Offline": "Offline",
"Online": "Online",
"Out Of Sync": "Out Of Sync",
@@ -93,6 +98,7 @@
"Quick guide to supported patterns": "Quick guide to supported patterns",
"RAM Utilization": "RAM Utilization",
"Rescan": "Rescan",
"Rescan All": "Rescan All",
"Rescan Interval": "Rescan Interval",
"Restart": "Restart",
"Restart Needed": "Restart Needed",

View File

@@ -2,8 +2,8 @@
"API Key": "Clave API",
"About": "Acerca de",
"Add": "Agregar",
"Add Device": "Agregar dispositivo",
"Add Folder": "Agregar repositorio",
"Add Device": "Agregar Dispositivo",
"Add Folder": "Agregar Repositorio",
"Add new folder?": "¿Agregar nuevo repositorio?",
"Address": "Dirección",
"Addresses": "Direcciones",
@@ -12,13 +12,13 @@
"Any devices configured on an introducer device will be added to this device as well.": "Cualquier dispositivo configurado en un dispositivo introductor será también agregado a este dispositivo.",
"Automatic upgrades": "Actualizaciones automáticas",
"Bugs": "Errores",
"CPU Utilization": "Uso de la CPU",
"CPU Utilization": "Uso de CPU",
"Changelog": "Registro de cambios",
"Close": "Cerrar",
"Comment, when used at the start of a line": "Comentario, cuando es utilizado al inicio de una línea.",
"Compression is recommended in most setups.": "La compresión de datos es recomendada para la mayoría de las configuraciones.",
"Connection Error": "Error de conexión",
"Copied from elsewhere": "Copiado de otra parte.",
"Copied from elsewhere": "Copiado desde otra parte.",
"Copied from original": "Copiado del original",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Derechos de autor © 2014 Jakob Borg y los siguientes colaboradores:",
"Delete": "Suprimir",
@@ -71,6 +71,7 @@
"Local Discovery": "Búsqueda en red local",
"Local State": "Estado local",
"Maximum Age": "Edad máxima",
"Move to top of queue": "Mover al principio de la cola.",
"Multi level wildcard (matches multiple directory levels)": "Carácter comodín multinivel (coincide en el directorio y sus subdirectorios)",
"Never": "Nunca",
"New Device": "Nuevo dispositivo",
@@ -93,6 +94,7 @@
"Quick guide to supported patterns": "Guía rápida sobre los patrones soportados",
"RAM Utilization": "Utilización de RAM",
"Rescan": "Reescanear",
"Rescan All": "Reescanear todo",
"Rescan Interval": "Intervalo de reescaneo",
"Restart": "Reiniciar",
"Restart Needed": "Es necesario reiniciar",

View File

@@ -4,12 +4,12 @@
"Add": "Ajouter",
"Add Device": "Ajouter un périphérique",
"Add Folder": "Ajouter un répertoire",
"Add new folder?": "Ajouter un nouveau répertoire ?",
"Add new folder?": "Ajouter un nouveau dossier ?",
"Address": "Adresse",
"Addresses": "Adresses",
"Allow Anonymous Usage Reporting?": "Autoriser le rapport anonyme de statistiques d'utilisation ?",
"Anonymous Usage Reporting": "Rapport anonyme de statistiques d'utilisation",
"Any devices configured on an introducer device will be added to this device as well.": "Tout appareil ajouté depuis un appareil initiateur sera aussi ajouté sur cet appareil.",
"Any devices configured on an introducer device will be added to this device as well.": "Toute machine ajoutée depuis une machine initiatrice sera aussi ajoutée sur cette machine.",
"Automatic upgrades": "Mises à jour automatiques",
"Bugs": "Bugs",
"CPU Utilization": "Utilisation du CPU",
@@ -47,7 +47,7 @@
"Folder ID": "ID du répertoire",
"Folder Master": "Répertoire maître",
"Folder Path": "Chemin du répertoire",
"Folders": "Répertoires",
"Folders": "Dossiers",
"GUI Authentication Password": "Mot de passe d'authentification GUI",
"GUI Authentication User": "Utilisateur autorisé GUI",
"GUI Listen Addresses": "Adresse du GUI",
@@ -71,10 +71,11 @@
"Local Discovery": "Recherche locale",
"Local State": "État local",
"Maximum Age": "Ancienneté maximum",
"Move to top of queue": "Déplacer en haut de la file",
"Multi level wildcard (matches multiple directory levels)": "Astérisque à plusieurs niveaux (correspond aux répertoires et sous-répertoires)",
"Never": "Jamais",
"New Device": "Nouvelle machine",
"New Folder": "Nouveau répertoire",
"New Folder": "Nouveau dossier",
"No": "Non",
"No File Versioning": "Pas de version de fichier",
"Notice": "Notification",
@@ -85,7 +86,7 @@
"Out of Sync Items": "Objets non synchronisés",
"Outgoing Rate Limit (KiB/s)": "Limite du débit sortant (KiB/s)",
"Override Changes": "Écraser les changements",
"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": "Chemin du répertoire sur l'ordinateur local. Il sera créé si il n'existe pas. Le caractère tilde (~) 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": "Chemin du dossier sur l'ordinateur local. Il sera créé si il n'existe pas. Le caractère tilde (~) peut être utilisé comme raccourci vers",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Chemin où les versions doivent être conservées (laisser vide pour le chemin par défaut de .stversions dans le répertoire)",
"Please wait": "Merci de patienter",
"Preview": "Aperçu",
@@ -93,6 +94,7 @@
"Quick guide to supported patterns": "Guide rapide des masques supportés",
"RAM Utilization": "Utilisation de la RAM",
"Rescan": "Rescanner",
"Rescan All": "Re-analyser tout",
"Rescan Interval": "Intervalle de scan",
"Restart": "Redémarrer",
"Restart Needed": "Redémarrage nécessaire",
@@ -101,22 +103,22 @@
"Save": "Sauver",
"Scanning": "En cours de scan",
"Select the devices to share this folder with.": "Sélectionner les machines avec qui partager ce répertoire.",
"Select the folders to share with this device.": "Sélectionner les répertoires à partager avec cette machine.",
"Select the folders to share with this device.": "Sélectionner les dossiers à partager avec cette machine.",
"Settings": "Configuration",
"Share": "Partager",
"Share Folder": "Partager le répertoire",
"Share Folders With Device": "Partager des répertoires avec des machines",
"Share Folder": "Partager le dossier",
"Share Folders With Device": "Partager des dossiers avec des machines",
"Share With Devices": "Partage avec des machines",
"Share this folder?": "Voulez-vous partager ce répertoire ?",
"Share this folder?": "Voulez-vous partager ce dossier ?",
"Shared With": "Partagé avec",
"Short identifier for the folder. Must be the same on all cluster devices.": "Court identifiant du répertoire. Il doit être le même sur l'ensemble des machines du groupe.",
"Short identifier for the folder. Must be the same on all cluster devices.": "Court identifiant du dossier. Il doit être le même sur l'ensemble des machines du groupe.",
"Show ID": "Montrer l'ID",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Affiché à la place de l'ID de la machine dans le groupe. Sera proposé aux autres machines comme nom optionnel par défaut.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Affiché à la place de l'ID de la machine dans le groupe. Si laissé vide, il sera mis à jour par le nom proposé par la machine distante.",
"Shutdown": "Éteindre",
"Shutdown Complete": "Extinction terminée",
"Simple File Versioning": "Suivi simple des versions de fichier",
"Single level wildcard (matches within a directory only)": "Astérisque à un seul niveau (correspond uniquement à lintérieur du répertoire)",
"Single level wildcard (matches within a directory only)": "Astérisque à un seul niveau (correspond uniquement à lintérieur du dossier)",
"Source Code": "Code source",
"Staggered File Versioning": "Versions échelonnées de fichier",
"Start Browser": "Démarrer le navigateur web",
@@ -138,8 +140,8 @@
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "L'ID de l'appareil à renseigner peut être trouvé dans le menu \"Éditer > Montrer l'ID\" des autres nœuds. Les espaces et les tirets sont optionnels (ils seront ignorés).",
"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.": "Le rapport d'utilisation chiffré est envoyé quotidiennement. Il sert à répertorier les plateformes utilisées, la taille des répertoires et les versions de l'application. Si les données rapportées sont modifiées cette boite de dialogue vous redemandera votre confirmation.",
"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.": "L'ID de l'appareil inséré ne semble pas être valide. Il devrait ressembler à une chaîne de 52 ou 56 caractères comprenant des lettres, des chiffres et potentiellement des espaces et des traits d'union.",
"The folder ID cannot be blank.": "L'ID du répertoire ne peut être vide.",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "L'ID du répertoire doit être un identifiant court (64 caractères ou moins) comprenant uniquement des lettres, nombres, points (.), traits d'union (-) et tirets bas (_).",
"The folder ID cannot be blank.": "L'identifiant (ID) du dossier ne peut être vide.",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "L'ID du dossier doit être un identifiant court (64 caractères ou moins) comprenant uniquement des lettres, nombres, points (.), traits d'union (-) et tirets bas (_).",
"The folder ID must be unique.": "L'ID du répertoire doit être unique.",
"The folder path cannot be blank.": "Le chemin du répertoire ne peut pas être vide.",
"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.": "Les intervalles suivant sont utilisés: la première heure une version est conservée chaque 30 secondes, le premier jour une version est conservée chaque heure, les premiers 30 jours une version est conservée chaque jour, jusqu'à la limite d'âge maximum une version est conservée chaque semaine.",
@@ -147,7 +149,7 @@
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Le temps maximum de conservation d'une version (en jours, mettre à 0 pour conserver les versions pour toujours)",
"The number of old versions to keep, per file.": "Le nombre d'anciennes versions à garder, par fichier.",
"The number of versions must be a number and cannot be blank.": "Le nombre de versions doit être numérique, et ne peut pas être vide.",
"The rescan interval must be a non-negative number of seconds.": "L'intervalle de scan ne doit pas être un nombre négatif de secondes.",
"The rescan interval must be a non-negative number of seconds.": "L'intervalle d'analyse ne doit pas être un nombre négatif de secondes.",
"The rescan interval must be at least 5 seconds.": "L'intervalle de scan doit être d'au minimum 5 secondes.",
"Unknown": "Inconnu",
"Unshared": "Non partagé",
@@ -167,5 +169,5 @@
"You must keep at least one version.": "Vous devez garder au minimum une version.",
"full documentation": "documentation complète",
"items": "éléments",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} veut partager le répertoire \"{{folder}}\"."
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} veut partager le dossier \"{{folder}}\"."
}

View File

@@ -1,10 +1,10 @@
{
"API Key": "API kulcs",
"About": "Névjegy",
"Add": "Add",
"Add": "Hozzáadás",
"Add Device": "Eszköz hozzáadása",
"Add Folder": "Mappa hozzáadása",
"Add new folder?": "Add new folder?",
"Add new folder?": " ",
"Address": "Cím",
"Addresses": "Címek",
"Allow Anonymous Usage Reporting?": "Engedélyezed a névtelen felhasználási adatok küldését?",
@@ -13,7 +13,7 @@
"Automatic upgrades": "Automatikus frissítés",
"Bugs": "Hibák",
"CPU Utilization": "Processzor használat",
"Changelog": "Changelog",
"Changelog": "Változások",
"Close": "Bezárás",
"Comment, when used at the start of a line": "Megjegyzés, a sor elején használva",
"Compression is recommended in most setups.": "A tömörítés a a legtöbb esetben ajánlott",
@@ -25,8 +25,8 @@
"Device ID": "Eszköz azonosító",
"Device Identification": "Eszköz azonosító",
"Device Name": "Eszköz neve",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
"Devices": "Devices",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "{{device}} ({{address}}) csatlakozni szeretne.",
"Devices": "Eszközök",
"Disconnected": "Kapcsolat bontva",
"Documentation": "Dokumentáció",
"Download Rate": "Letöltési sebesség",
@@ -56,25 +56,26 @@
"Global Discovery Server": "Globális felfedező szerver",
"Global State": "Globális állapot",
"Idle": "Tétlen",
"Ignore": "Ignore",
"Ignore": "Visszautasítás",
"Ignore Patterns": "Figyelmen kívül hagyás",
"Ignore Permissions": "Jogosultságok figyelmen kívül hagyása",
"Incoming Rate Limit (KiB/s)": "Bejövő sebesség korlát (KIB/mp)",
"Introducer": "Bevezető",
"Inversion of the given condition (i.e. do not exclude)": "A feltétel ellentéte (pl. ki nem hagyás)",
"Keep Versions": "Megtartott verziók",
"Last File Received": "Last File Received",
"Last File Received": "Utolsó beérkezett fájl",
"Last File Synced": "Utolsó szinkronizált fájl",
"Last seen": "Utoljára látva",
"Later": "Later",
"Later": "Később",
"Latest Release": "Utolsó kiadás",
"Local Discovery": "Helyi felfedezés",
"Local State": "Helyi állapot",
"Maximum Age": "Maximális kor",
"Move to top of queue": "Move to top of queue",
"Multi level wildcard (matches multiple directory levels)": "Több szintű helyettesítő karakter (több könyvtár szintre érvényesül)",
"Never": "Soha",
"New Device": "New Device",
"New Folder": "New Folder",
"New Device": "Új eszköz",
"New Folder": "Új mappa",
"No": "Nem",
"No File Versioning": "Nincs fájl verziózás",
"Notice": "Megjegyzés",
@@ -82,7 +83,7 @@
"Offline": "Nincs kapcsolat",
"Online": "Kapcsolódva",
"Out Of Sync": "Nincs szinkronban",
"Out of Sync Items": "Out of Sync Items",
"Out of Sync Items": "Nem szinkronizált elemek",
"Outgoing Rate Limit (KiB/s)": "Kimenő sávszélesség (KiB/mp)",
"Override Changes": "Változtatások felülbírálása",
"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": "A mappa elérési útja az eszközön. Amennyiben nem létezik, a program automatikusan létrehozza. A hullámvonal (~) a következő helyettesítésre használható: ",
@@ -93,6 +94,7 @@
"Quick guide to supported patterns": "Rövid útmutató a használható mintákról",
"RAM Utilization": "Memória használat",
"Rescan": "Átnézés",
"Rescan All": "Rescan All",
"Rescan Interval": "Átnézési intervallum",
"Restart": "Újraindítás",
"Restart Needed": "Újraindítás szükséges",
@@ -103,25 +105,25 @@
"Select the devices to share this folder with.": "Válaszd ki az eszközöket amelyekkel meg szeretnéd osztani a mappát",
"Select the folders to share with this device.": "Válaszd ki a mappákat amiket meg szeretnél osztani ezzel az eszközzel",
"Settings": "Beállítások",
"Share": "Share",
"Share Folder": "Share Folder",
"Share": "Megosztás",
"Share Folder": "Mappa megosztása",
"Share Folders With Device": "Mappák megosztása az eszközzel",
"Share With Devices": "Megosztás más eszközzel",
"Share this folder?": "Share this folder?",
"Share this folder?": "Megosztod ezt a mappát?",
"Shared With": "Megosztva ezekkel:",
"Short identifier for the folder. Must be the same on all cluster devices.": "Rövid azonosító. Minden megosztott eszközön azonosnak kell lennie.",
"Show ID": "Azonosító mutatása",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Az eszköz azonosító helyett jelenik meg. A többi eszközön alapértelmezett névként használható. ",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Az eszköz azonosító helyett jelenik meg. Üresen hagyva az eszköz saját neve lesz használva ",
"Shutdown": "Leállítás",
"Shutdown Complete": "Shutdown Complete",
"Shutdown Complete": "Leállítás",
"Simple File Versioning": "Egyszerű fájl verziókövetés",
"Single level wildcard (matches within a directory only)": "Egyszintű helyettesítő karakter (csak egy mappára érvényes)",
"Source Code": "Forráskód",
"Staggered File Versioning": "Többszintű fájl verziókövetés",
"Start Browser": "Böngésző indítása",
"Stopped": "Leállítva",
"Support": "Support",
"Support": "Támogatás",
"Support / Forum": "Támogatás / Fórum",
"Sync Protocol Listen Addresses": "Szinkronizációs protokoll címe",
"Synchronization": "Szinkronizálás",
@@ -131,7 +133,7 @@
"Syncthing is restarting.": "Syncthing újraindul",
"Syncthing is upgrading.": "Syncthing frissül",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Úgy tűnik, hogy a Syncthing nem működik, vagy valami probléma van az hálózati kapcsolattal. Újra próbálom...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please reload your browser or restart Syncthing if the problem persists.",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "A Syncthing hibát észlelt a kérés teljesítése közben. Töltsd újra a böngészőt vagy indítsd újra a Syncthing-et amennyiben a hiba tartósan fennáll.",
"The aggregated statistics are publicly available at {%url%}.": "Az összevont statisztikák nyilvánosan elérhetők a {{url}} címen.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "A beállítások elmentésre kerültek, de nem lettek aktiválva. Indítsd újra a Syncthing-et, hogy aktiváld őket.",
"The device ID cannot be blank.": "Az eszköz azonosító nem lehet üres.",
@@ -166,6 +168,6 @@
"Yes": "Igen",
"You must keep at least one version.": "Legalább egy verziót meg kell tartanod",
"full documentation": "teljes dokumentáció",
"items": "tételek",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\"."
"items": "elemek",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} meg szeretné osztani a \"{{folder}}\" nevű mappát."
}

View File

@@ -71,6 +71,7 @@
"Local Discovery": "Individuazione Locale",
"Local State": "Stato Locale",
"Maximum Age": "Durata Massima",
"Move to top of queue": "Move to top of queue",
"Multi level wildcard (matches multiple directory levels)": "Metacarattere multi-livello (corrisponde alle cartelle e alle sotto-cartelle)",
"Never": "Mai",
"New Device": "Nuovo Dispositivo",
@@ -93,6 +94,7 @@
"Quick guide to supported patterns": "Guida veloce agli schemi supportati",
"RAM Utilization": "Utilizzo RAM",
"Rescan": "Riscansiona",
"Rescan All": "Rescan All",
"Rescan Interval": "Intervallo Scansione",
"Restart": "Riavvia",
"Restart Needed": "Riavvio Necessario",
@@ -131,7 +133,7 @@
"Syncthing is restarting.": "Riavvio di Syncthing in corso.",
"Syncthing is upgrading.": "Aggiornamento di Syncthing in corso.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing sembra inattivo, oppure c'è un problema con la tua connessione a Internet. Nuovo tentativo…",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please reload your browser or restart Syncthing if the problem persists.",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Sembra che Syncthing non sia stato in grado di processare il tuo comando. Se il problema persiste, prova a ricaricare la pagina nel tuo navigatore, oppure a rilanciare Syncthing.",
"The aggregated statistics are publicly available at {%url%}.": "Le statistiche aggregate sono disponibili pubblicamente su {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "La configurazione è stata salvata ma non attivata. Devi riavviare Syncthing per attivare la nuova configurazione.",
"The device ID cannot be blank.": "L'ID del dispositivo non può essere vuoto.",

View File

@@ -71,6 +71,7 @@
"Local Discovery": "Vietinis matomumas",
"Local State": "Vietinė būsena",
"Maximum Age": "Maksimalus amžius",
"Move to top of queue": "Perkelti į eilės priekį",
"Multi level wildcard (matches multiple directory levels)": "Keletos lygių pakaitos (atitinka keletą direktorijų lygių)",
"Never": "Niekada",
"New Device": "Naujas įrenginys",
@@ -93,6 +94,7 @@
"Quick guide to supported patterns": "Trumpas leistinų šablonų vadovas",
"RAM Utilization": "Atminties naudojimas",
"Rescan": "Nuskaityti iš naujo",
"Rescan All": "Nuskaityti visus aplankus",
"Rescan Interval": "Pertrauka tarp nuskaitymų",
"Restart": "Perleisti",
"Restart Needed": "Reikalingas perleidimas",

View File

@@ -71,6 +71,7 @@
"Local Discovery": "Lokal Søking",
"Local State": "Lokal Tilstand",
"Maximum Age": "Maksimal Levetid",
"Move to top of queue": "Move to top of queue",
"Multi level wildcard (matches multiple directory levels)": "Multinivåsøk (søker på flere mappenivå)",
"Never": "Aldri",
"New Device": "Ny Enhet",
@@ -93,6 +94,7 @@
"Quick guide to supported patterns": "Kjapp innføring i godkjente mønster",
"RAM Utilization": "RAM-utnyttelse",
"Rescan": "Skann På Ny",
"Rescan All": "Rescan All",
"Rescan Interval": "Skanneintervall",
"Restart": "Omstart",
"Restart Needed": "Omstart Kreves",

View File

@@ -13,7 +13,7 @@
"Automatic upgrades": "Automatisch bijwerken",
"Bugs": "Fouten",
"CPU Utilization": "CPU Gebruik",
"Changelog": "Changelog",
"Changelog": "Logboek",
"Close": "Sluiten",
"Comment, when used at the start of a line": "Commentaar, indien gebruikt aan het begin van de lijn",
"Compression is recommended in most setups.": "Gegevenscompressie is aan te raden in de meeste situaties.",
@@ -22,18 +22,18 @@
"Copied from original": "Gekopieerd van het origineel",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Copyright © 2014 Jakob Borg en de onderstaande bijdragers:",
"Delete": "Verwijderen",
"Device ID": "Toestel ID",
"Device Identification": "Toestel identificatie",
"Device Name": "Naam toestel",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Het toestel {{device}} ({{address}}) wenst te verbinden. Dit toestel toevoegen?",
"Devices": "Devices",
"Device ID": "Apparaat ID",
"Device Identification": "Apparaat identificatie",
"Device Name": "Naam apparaat",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Het apparaat {{device}} ({{address}}) wenst te verbinden. Dit apparaat toevoegen?",
"Devices": "Toestellen",
"Disconnected": "Niet Verbonden",
"Documentation": "Documentatie",
"Download Rate": "Downloadsnelheid",
"Downloaded": "Gedownload",
"Downloading": "Bezig met downloaden",
"Edit": "Bewerk",
"Edit Device": "Toestel aanpassen",
"Edit Device": "Apparaat aanpassen",
"Edit Folder": "Folder aanpassen",
"Editing": "Bezig met aanpassen",
"Enable UPnP": "UPnP aanzetten",
@@ -43,7 +43,7 @@
"File Versioning": "Versiebeheer",
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "Bestands permissiebits worden genegeerd wanneer naar veranderingen wordt gekeken. Gebruik dit op FAT bestandsystemen",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.": "Bestanden worden naar de .stversions map verplaatst met een tijdsaanduiding, wanneer ze aangepast of verwijderd worden door syncthing.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Bestanden zijn beschermt tegen aanpassingen gemaakt door andere toestellen maar aanpassingen op dit toestel worden doorgestuurd naar de rest van de cluster.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Bestanden zijn beschermt tegen aanpassingen gemaakt door andere apparaten maar aanpassingen op dit apparaat worden doorgestuurd naar de rest van de cluster.",
"Folder ID": "Folder ID",
"Folder Master": "Hoofdfolder",
"Folder Path": "Locatie folder",
@@ -63,7 +63,7 @@
"Introducer": "Introductietoestel",
"Inversion of the given condition (i.e. do not exclude)": "Inversie van de gegeven voorwaarde (bv. niet uitsluiten)",
"Keep Versions": "Versies behouden",
"Last File Received": "Last File Received",
"Last File Received": "Laaste ontvangen bestand",
"Last File Synced": "Laatste Gesynchroniseerde Bestand",
"Last seen": "Laatst gezien op",
"Later": "Later",
@@ -71,9 +71,10 @@
"Local Discovery": "Lokaal zoeken",
"Local State": "Lokale status",
"Maximum Age": "Maximum leeftijd",
"Move to top of queue": "Move to top of queue",
"Multi level wildcard (matches multiple directory levels)": "Wildcard op meerder niveaus (toepasbaar op meerdere niveaus van folders)",
"Never": "Nooit",
"New Device": "Nieuw Toestel",
"New Device": "Nieuw Apparaat",
"New Folder": "Nieuwe Folder",
"No": "Nee",
"No File Versioning": "Geen versiebeheer",
@@ -82,7 +83,7 @@
"Offline": "Offline",
"Online": "Online",
"Out Of Sync": "Niet gesynchroniseerd",
"Out of Sync Items": "Out of Sync Items",
"Out of Sync Items": "Niet gesynchroniseerde objecten",
"Outgoing Rate Limit (KiB/s)": "Uitgaande snelheidslimiet (KiB/s)",
"Override Changes": "Veranderingen overschrijven",
"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": "Locatie van de folder op de lokale computer. Zal aangemaakt worden wanneer deze niet bestaat. De tilde (~) kan gebruikt in plaats van",
@@ -93,6 +94,7 @@
"Quick guide to supported patterns": "Snelgids voor ondersteunde patronen",
"RAM Utilization": "RAM gebruik",
"Rescan": "Opnieuw scannen",
"Rescan All": "Rescan All",
"Rescan Interval": "Scanfrequentie",
"Restart": "Herstart",
"Restart Needed": "Herstart nodig",
@@ -114,7 +116,7 @@
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Wordt getoond in plaats van de toestel ID in de cluster staat. Wordt doorgegeven aan andere toestellen as een bijkomende standaard toestelnaam.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Wordt getoond in plaats van de toestel ID in de cluster staat. Wanneer leeg wordt deze aangepast met de naam aangekondigd door het toestel.",
"Shutdown": "Sluit af",
"Shutdown Complete": "Shutdown Complete",
"Shutdown Complete": "Afsluiten voltooid",
"Simple File Versioning": "Eenvoudig versiebeheer",
"Single level wildcard (matches within a directory only)": "Wildcard op enkel niveau (toepasbaar binnen een enkele folder)",
"Source Code": "Broncode",

View File

@@ -71,6 +71,7 @@
"Local Discovery": "Lokal Søking",
"Local State": "Lokal Tilstand",
"Maximum Age": "Maksimal Levetid",
"Move to top of queue": "Move to top of queue",
"Multi level wildcard (matches multiple directory levels)": "Multinivåsøk (søkjer på fleire mappenivå)",
"Never": "Aldri",
"New Device": "Ny Eining",
@@ -93,6 +94,7 @@
"Quick guide to supported patterns": "Kjapp innføring i godkjente mønster",
"RAM Utilization": "RAM-utnytting",
"Rescan": "Skann På Ny",
"Rescan All": "Rescan All",
"Rescan Interval": "Skanneintervall",
"Restart": "Omstart",
"Restart Needed": "Omstart Trengs",

View File

@@ -71,6 +71,7 @@
"Local Discovery": "Lokalne odnajdywanie",
"Local State": "Status lokalny",
"Maximum Age": "Maksymalny wiek",
"Move to top of queue": "Move to top of queue",
"Multi level wildcard (matches multiple directory levels)": "Wieloznaczność na poziomie katalogów i plików (uwzględnia nazwy folderów i plików)",
"Never": "Nigdy",
"New Device": "Nowe urządzenie",
@@ -93,6 +94,7 @@
"Quick guide to supported patterns": "Krótki przewodnik po obsługiwanych wzorcach",
"RAM Utilization": "Użycie pamięci RAM",
"Rescan": "Skanuj ponownie",
"Rescan All": "Rescan All",
"Rescan Interval": "Interwał skanowania",
"Restart": "Uruchom ponownie",
"Restart Needed": "Wymagane ponowne uruchomienie",

View File

@@ -0,0 +1,173 @@
{
"API Key": "Chave da API",
"About": "Sobre",
"Add": "Adicionar",
"Add Device": "Adicionar dispositivo",
"Add Folder": "Adicionar pasta",
"Add new folder?": "Adicionar nova pasta?",
"Address": "Endereço",
"Addresses": "Endereços",
"Allow Anonymous Usage Reporting?": "Permitir envio de relatórios anônimos de uso?",
"Anonymous Usage Reporting": "Relatórios anônimos de uso",
"Any devices configured on an introducer device will be added to this device as well.": "Quaisquer dispositivos configurados em um apresentador serão também adicionados a este dispositivo.",
"Automatic upgrades": "Atualizações automáticas",
"Bugs": "Erros",
"CPU Utilization": "Uso de CPU",
"Changelog": "Registro de alterações",
"Close": "Fechar",
"Comment, when used at the start of a line": "Comentário, se usado no início de uma linha",
"Compression is recommended in most setups.": "A compressão é recomendada na maior parte dos casos.",
"Connection Error": "Erro de conexão",
"Copied from elsewhere": "Copiado de outro lugar",
"Copied from original": "Copiado do original",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Direitos reservados © 2014 Jakob Borg e os seguintes contribuidores:",
"Delete": "Apagar",
"Device ID": "ID do dispositivo",
"Device Identification": "Identificação do dispositivo",
"Device Name": "Nome do dispositivo",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Dispositivo {{device}} ({{address}}) quer se conectar. Adicionar novo dispositivo?",
"Devices": "Dispositivos",
"Disconnected": "Desconectado",
"Documentation": "Documentação",
"Download Rate": "Velocidade de recepção",
"Downloaded": "Recebido",
"Downloading": "Recebendo",
"Edit": "Editar",
"Edit Device": "Editar dispositivo",
"Edit Folder": "Editar pasta",
"Editing": "Editando",
"Enable UPnP": "Habilitar UPnP",
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Insira endereços \"ip:porta\" separados por vírgulas ou \"dynamic\" para descobrir automaticamente o endereço.\n",
"Enter ignore patterns, one per line.": "Insira os padrões de exclusão, um por linha.",
"Error": "Erro",
"File Versioning": "Versionamento de arquivos",
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "As permissões de arquivo são ignoradas ao procurar alterações. Use nos sistemas de arquivos FAT.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.": "Os arquivos são movidos para versões marcadas com o tempo numa pasta .stversions, quando substituídos ou apagados pelo syncthing.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Os arquivos estão protegidos contra alterações feitas em outros dispositivos, mas alterações feitas neste dispositivo serão enviadas ao resto do grupo.\n",
"Folder ID": "ID da pasta",
"Folder Master": "Pasta mestre",
"Folder Path": "Caminho da pasta",
"Folders": "Pastas",
"GUI Authentication Password": "Senha para acesso à interface",
"GUI Authentication User": "Nome de usuário para acesso à interface",
"GUI Listen Addresses": "Endereços de escuta da interface",
"Generate": "Gerar",
"Global Discovery": "Descoberta global",
"Global Discovery Server": "Servidor de descoberta global",
"Global State": "Estado global",
"Idle": "Em espera",
"Ignore": "Ignorar",
"Ignore Patterns": "Padrões de exclusão",
"Ignore Permissions": "Ignorar permissões",
"Incoming Rate Limit (KiB/s)": "Limite de velocidade de recepção (KiB/s)",
"Introducer": "Apresentador",
"Inversion of the given condition (i.e. do not exclude)": "Inversão de uma condição (ou seja, não excluir)",
"Keep Versions": "Manter versões",
"Last File Received": "Último arquivo recebido",
"Last File Synced": "Último arquivo sincronizado",
"Last seen": "Visto por último em",
"Later": "Depois",
"Latest Release": "Versão mais recente",
"Local Discovery": "Descoberta local",
"Local State": "Estado local",
"Maximum Age": "Idade máxima",
"Move to top of queue": "Move to top of queue",
"Multi level wildcard (matches multiple directory levels)": "Coringa multi-nível (faz corresponder a vários níveis de pastas)",
"Never": "Nunca",
"New Device": "Novo dispositivo",
"New Folder": "Nova Pasta",
"No": "Não",
"No File Versioning": "Sem versionamento de arquivos",
"Notice": "Aviso",
"OK": "OK",
"Offline": "Desconectado",
"Online": "Conectado",
"Out Of Sync": "Não sincronizado",
"Out of Sync Items": "Itens não sincronizados",
"Outgoing Rate Limit (KiB/s)": "Limite de velocidade de envio (KiB/s)",
"Override Changes": "Sobrescrever mudanças",
"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": "Caminho para a pasta na máquina local. Será criado caso não exista. O caractere til (~) pode ser usado como um atalho para",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Caminho onde as versões serão salvas (deixe vazio para usar a pasta padrão .stversions na pasta).",
"Please wait": "Aguarde",
"Preview": "Visualizar",
"Preview Usage Report": "Visualizar relatório de uso",
"Quick guide to supported patterns": "Guia rápido dos padrões suportados",
"RAM Utilization": "Uso de RAM",
"Rescan": "Verificar agora",
"Rescan All": "Rescan All",
"Rescan Interval": "Intervalo entre verificações",
"Restart": "Reiniciar",
"Restart Needed": "É necessário reiniciar",
"Restarting": "Reiniciando",
"Reused": "Reutilizado",
"Save": "Salvar",
"Scanning": "Verificando",
"Select the devices to share this folder with.": "Selecione os dispositivos com os quais esta pasta será compartilhada.",
"Select the folders to share with this device.": "Selecione as pastas a serem compartilhadas com este dispositivo.",
"Settings": "Configurações",
"Share": "Compartilhar",
"Share Folder": "Compartilhar Pasta",
"Share Folders With Device": "Compartilhar pastas com o dispositivo",
"Share With Devices": "Compartilhar com os dispositivos",
"Share this folder?": "Compartilhar esta pasta?",
"Shared With": "Compartilhada com",
"Short identifier for the folder. Must be the same on all cluster devices.": "Identificador curto para a pasta. Deve ser igual em todos os dispositivos.",
"Show ID": "Mostrar ID",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Mostrado no lugar do ID do dispositivo no indicador de estado do grupo. Será divulgado aos outros dispositivos como um nome pré-definido opcional.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Mostrado no lugar do ID do dispositivo no indicador de estado do grupo. Será actualizado para o nome que o dispositivo divulga, se for deixado em branco.",
"Shutdown": "Desligar",
"Shutdown Complete": "Desligamento completado",
"Simple File Versioning": "Versionamento simples de arquivos",
"Single level wildcard (matches within a directory only)": "Coringa de único nível (faz corresponder apenas dentro de uma pasta)",
"Source Code": "Código-fonte",
"Staggered File Versioning": "Versionamento escalonado de arquivos",
"Start Browser": "Iniciar navegador",
"Stopped": "Parado",
"Support": "Suporte",
"Support / Forum": "Suporte / Fórum",
"Sync Protocol Listen Addresses": "Endereços de escuta do protocolo de sincronização",
"Synchronization": "Sincronização",
"Syncing": "Sincronizando",
"Syncthing has been shut down.": "O Syncthing foi desligado.",
"Syncthing includes the following software or portions thereof:": "O Syncthing inclui os seguintes programas ou partes deles:",
"Syncthing is restarting.": "O Syncthing está sendo reiniciado.",
"Syncthing is upgrading.": "O Syncthing está sendo atualizado.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Parece que o Syncthing está desligado ou há um problema com a sua conexão de internet. Tentando novamente...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Parece que o Syncthing está tendo problemas ao processar sua requisição. Recarregue a página ou reinicie o Syncthing caso o problema persista.",
"The aggregated statistics are publicly available at {%url%}.": "As estatísticas agregadas estão disponíveis publicamente em {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "A configuração foi salva mas ainda não foi ativada. O Syncthing precisa ser reiniciado para a ativação da nova configuração.",
"The device ID cannot be blank.": "O ID de dispositivo não pode estar em branco.",
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "O ID do dispositivo a ser inserido aqui pode ser encontrado no menu \"Editar > Mostrar ID\" do outro dispositivo. Espaços e hífens são opcionais (ignorados).",
"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.": "O relatório criptografado de uso é enviado diariamente. É utilizado para rastrear plataformas comuns, tamanhos de pastas e versões da aplicação. Se o tipo de dados do relatório for alterado, esta janela te notificará novamente.",
"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.": "O ID de dispositivo inserido não parece ser válido. Ele deve ter entre 52 e 56 caracteres e ser composto de letras e números, com espaços e hífens opcionais.",
"The folder ID cannot be blank.": "O ID da pasta não pode estar vazio.",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "O ID da pasta deve ser um identificador curto (com 64 caracteres ou menos), composto somente de letras, números, pontos (.), hífens (-) ou underscores (_).",
"The folder ID must be unique.": "O ID da pasta deve ser único.",
"The folder path cannot be blank.": "O caminho da pasta não pode estar vazio.",
"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.": "São utilizados os seguintes intervalos: na primeira hora é guardada uma versão a cada 30 segundos, no primeiro dia é guardada uma versão a cada hora, nos primeiros 30 dias é guardada uma versão por dia e, até que atinja a idade máxima, é guardada uma versão por semana.",
"The maximum age must be a number and cannot be blank.": "A idade máxima deve ser um valor numérico e não pode estar vazio.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "O número máximo de dias em que uma versão é guardada. (Use 0 para manter para sempre).",
"The number of old versions to keep, per file.": "O número de versões antigas a serem mantidas, por arquivo.",
"The number of versions must be a number and cannot be blank.": "O número de versões deve ser um valor numério e não pode estar em branco.",
"The rescan interval must be a non-negative number of seconds.": "O intervalo entre verificações deve ser um número positivo de segundos.",
"The rescan interval must be at least 5 seconds.": "O intervalo entre verificações deve ser de pelo menos 5 segundos.",
"Unknown": "Desconhecida",
"Unshared": "Não compartilhado",
"Unused": "Não utilizado",
"Up to Date": "Sincronizada",
"Upgrade To {%version%}": "Atualizar para {{version}}",
"Upgrading": "Atualizando",
"Upload Rate": "Velocidade de envio",
"Use Compression": "Usar compressão",
"Use HTTPS for GUI": "Usar HTTPS para a interface web",
"Version": "Versão",
"Versions Path": "Caminho das versões",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "As versões são automaticamente apagadas se elas são mais antigas do que a idade máxima ou excederem o número de arquivos permitido em um intervalo.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Quando estiver adicionando um dispositivo, lembre-se de que este dispositivo deve ser adicionado do outro lado também.",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Quando adicionar uma nova pasta, lembre-se que o ID da pasta é utilizado para ligar pastas entre dispositivos. Ele é sensível às diferenças entre maiúsculas e minúsculas e devem ser iguais em todos os dispositivos.",
"Yes": "Sim",
"You must keep at least one version.": "Você deve manter pelo menos uma versão.",
"full documentation": "documentação completa",
"items": "itens",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} quer compartilhar a pasta \"{{folder}}\"."
}

View File

@@ -71,6 +71,7 @@
"Local Discovery": "Busca local",
"Local State": "Estado local",
"Maximum Age": "Idade máxima",
"Move to top of queue": "Mover para o topo da fila",
"Multi level wildcard (matches multiple directory levels)": "Caractere polivalente multi-nível (faz corresponder a vários níveis de pastas)",
"Never": "Nunca",
"New Device": "Novo dispositivo",
@@ -93,6 +94,7 @@
"Quick guide to supported patterns": "Guia rápido dos padrões suportados",
"RAM Utilization": "Utilização da RAM",
"Rescan": "Verificar agora",
"Rescan All": "Verificar tudo",
"Rescan Interval": "Intervalo entre verificações",
"Restart": "Reiniciar",
"Restart Needed": "É preciso reiniciar",

View File

@@ -38,7 +38,7 @@
"Editing": "Редактирование",
"Enable UPnP": "Включить UPnP",
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": " Введите пары \"IP:PORT\" разделённые запятыми, или слово \"dynamic\" для автоматического обнаружения адреса.",
"Enter ignore patterns, one per line.": "Введите шаблон игнорирования, по-одному на строку.",
"Enter ignore patterns, one per line.": "Введите шаблоны игнорирования, по-одному на строку.",
"Error": "Ошибка",
"File Versioning": "Управление версиями",
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "Права доступа к файлам будут игнорироваться. Используйте на файловых системах типа FAT.",
@@ -71,10 +71,11 @@
"Local Discovery": "Локальное обнаружение",
"Local State": "Локальное состояние",
"Maximum Age": "Максимальный срок",
"Move to top of queue": "Поместить в начало очереди",
"Multi level wildcard (matches multiple directory levels)": "Многоуровневая маска (поиск совпадений во всех подпапках)",
"Never": "Никогда",
"New Device": "Новое устройство",
"New Folder": "Новая напка",
"New Folder": "Новая папка",
"No": "Нет",
"No File Versioning": "Без управления версиями файлов",
"Notice": "Внимание",
@@ -93,6 +94,7 @@
"Quick guide to supported patterns": "Краткое руководство по поддерживаемым шаблонам",
"RAM Utilization": "Использование ОЗУ",
"Rescan": "Пересканирование",
"Rescan All": "Пересканировать все",
"Rescan Interval": "Интервал пересканирования",
"Restart": "Перезапуск",
"Restart Needed": "Требуется перезапуск",
@@ -167,5 +169,5 @@
"You must keep at least one version.": "Вы должны хранить как минимум одну версию.",
"full documentation": "полная документация",
"items": "элементы",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\"."
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} хочет поделиться папкой \"{{folder}}\"."
}

View File

@@ -71,6 +71,7 @@
"Local Discovery": "Lokal uppslagning",
"Local State": "Lokal status",
"Maximum Age": "Högsta åldersgräns",
"Move to top of queue": "Move to top of queue",
"Multi level wildcard (matches multiple directory levels)": "Jokertecken som representerar noll eller fler godtyckliga tecken, även över kataloggränser.",
"Never": "Aldrig",
"New Device": "Ny enhet",
@@ -93,6 +94,7 @@
"Quick guide to supported patterns": "Snabb guide till filmönster som stöds",
"RAM Utilization": "Minnesanvändning",
"Rescan": "Uppdatera",
"Rescan All": "Rescan All",
"Rescan Interval": "Uppdateringsintervall",
"Restart": "Starta om",
"Restart Needed": "Omstart behövs",

View File

@@ -0,0 +1,173 @@
{
"API Key": "API Anahtarı",
"About": "Hakkında",
"Add": "Ekle",
"Add Device": "Cihaz Ekle",
"Add Folder": "Klasör Ekle",
"Add new folder?": "Yeni klasör ekle?",
"Address": "Adres",
"Addresses": "Adresler",
"Allow Anonymous Usage Reporting?": "Anonim kullanım raporlarına izin ver ?",
"Anonymous Usage Reporting": "Anonim Kullanım Raporlama",
"Any devices configured on an introducer device will be added to this device as well.": "Herhangi bir cihaz, tanıtıcı cihaz olarak yapılandırıldığında bu cihaza eklenecektir.",
"Automatic upgrades": "Otomatik yükseltmeler",
"Bugs": "Hatalar",
"CPU Utilization": "İşlemci Kullanımı",
"Changelog": "Değişim Günlüğü",
"Close": "Kapat",
"Comment, when used at the start of a line": "Satır başında kullanıldığında açıklama özelliği taşır",
"Compression is recommended in most setups.": "Sıkıştırma işlemi çoğu kurulum için önerilmektedir.",
"Connection Error": "Bağlantı hatası",
"Copied from elsewhere": "Başka bir yerden kopyalanmış",
"Copied from original": "Aslından kopyalanmış",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Telif hakkı © 2014 Jakob Borg ve aşağıdaki katkıda bulunanlar",
"Delete": "Sil",
"Device ID": "Cihaz ID",
"Device Identification": "Cihaz Kimliği",
"Device Name": "Cihaz Adı",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Cihaz {{device}} ({{address}}) bağlanmak istiyor. Yeni cihaz ekle?",
"Devices": "Cihazlar",
"Disconnected": "Bağlantı Kesik",
"Documentation": "Dokümantasyon",
"Download Rate": "İndirme Hızı",
"Downloaded": "İndirilmiş",
"Downloading": "İndiriliyor",
"Edit": "Seçenekler",
"Edit Device": "Cihaz Düzenle",
"Edit Folder": "Klasör Düzenle",
"Editing": "Düzenleniyor",
"Enable UPnP": "UPnP Etkinleştir",
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "IP adresleri eklemek için virgül ile ayırarak \"ip:port\" yazın, ya da \"dynamic\" yazarak otomatik bulma işlemini seçin.",
"Enter ignore patterns, one per line.": "Yoksayılacak kalıp dizilerini her satıra bir tane olacak şekilde girin.",
"Error": "Hata",
"File Versioning": "Dosya Sürümlendirme",
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "Değişim yoklarken dosya izin bilgilerini ihmal et. FAT dosya sisteminde kullanın.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.": "Dosyalar syncthing tarafından değiştirildiğinde ya da silindiğinde, tarih damgalı sürümleri .stversions dizinine taşınır.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Dosyalar diğer cihazlarda yapılan değişikliklerden korunur, ancak bu cihazdaki değişiklikler kümedeki diğer cihazlara gönderilir.",
"Folder ID": "Klasör ID",
"Folder Master": "Ana Klasör",
"Folder Path": "Klasör Yolu",
"Folders": "Klasörler",
"GUI Authentication Password": "Kullanıcı arayüzü şifresi",
"GUI Authentication User": "Kullanıcı arayüzü kullanıcı ismi",
"GUI Listen Addresses": "Kullanıcı arayüzü bağlantı adresi",
"Generate": "Oluştur",
"Global Discovery": "Küresel Keşif",
"Global Discovery Server": "Küresel Keşif Sunucusu",
"Global State": "Küresel Durum",
"Idle": "Boşta",
"Ignore": "Yoksay",
"Ignore Patterns": "Kalıpları Yoksay",
"Ignore Permissions": "İzinleri yoksay",
"Incoming Rate Limit (KiB/s)": "İndirme Oranı Limiti (KiB/s)",
"Introducer": "Tanıtıcı",
"Inversion of the given condition (i.e. do not exclude)": "Inversion of the given condition (i.e. do not exclude)",
"Keep Versions": "Sürüm tut",
"Last File Received": "Alınan Son Dosya",
"Last File Synced": "Eşzamanlanan Son Dosya",
"Last seen": "Son Görülen",
"Later": "Sonra",
"Latest Release": "Son sürüm",
"Local Discovery": "Yerel bulma",
"Local State": "Yerel Durum",
"Maximum Age": "Azami Süre",
"Move to top of queue": "Move to top of queue",
"Multi level wildcard (matches multiple directory levels)": "Çoklu düzey wildcard (çok sayıda dizin düzeyinde eşleşme)",
"Never": "Asla",
"New Device": "Yeni Cihaz",
"New Folder": "Yeni Klasör",
"No": "Hayır",
"No File Versioning": "Sürümlendirme Yok",
"Notice": "Uyarı",
"OK": "Tamam",
"Offline": "Çevrim dışı",
"Online": "Çevrim içi",
"Out Of Sync": "Senkronize edilmemiş",
"Out of Sync Items": "Eşzamanlanmayan Öğeler",
"Outgoing Rate Limit (KiB/s)": "Yükleme hız sınırı (KB/sn)",
"Override Changes": "Değişiklikleri Geçersiz kıl",
"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": "Yerel bilgisayardaki klasöre ulaşım yolu. Dizin yoksa yaratılacak. (~) karakterinin kısayol olarak kullanılabileceği yol",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Sürümlerin saklanması gereken yol (klasördeki öntanımlı .stversions klasörü için boş bırakın)",
"Please wait": "Lütfen Bekleyin",
"Preview": "Önizleme",
"Preview Usage Report": "Kullanım raporunu gözden geçir",
"Quick guide to supported patterns": "Desteklenen kalıplar için hızlı rehber",
"RAM Utilization": "RAM Kullanımı",
"Rescan": "Tekrar Tara",
"Rescan All": "Rescan All",
"Rescan Interval": "Tarama Aralığı",
"Restart": "Yeniden Başlat",
"Restart Needed": "Yeniden başlatma gereklidir",
"Restarting": "Yeniden başlatılıyor",
"Reused": "Yeniden Kullanılan",
"Save": "Kaydet",
"Scanning": "Taranıyor",
"Select the devices to share this folder with.": "Bu klasörü paylaşacağın cihazları seç.",
"Select the folders to share with this device.": "Bu cihazla paylaşılacak klasörleri seç.",
"Settings": "Ayarlar",
"Share": "Paylaş",
"Share Folder": "Paylaşım Klasörü",
"Share Folders With Device": "Klasörü Cihazla Paylaş",
"Share With Devices": "Cihazlar İle Paylaş",
"Share this folder?": "Bu klasörü paylaşmak istiyor musun?",
"Shared With": "Paylaşılan düğümler",
"Short identifier for the folder. Must be the same on all cluster devices.": "Klasörü için kısa tanımlayıcı. Tüm küme cihazlarda aynı olmalıdır.",
"Show ID": "ID Göster",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Küme durumunda Cihaz ID yerine bunu göster. Varsayılan isim isteğe bağlı olarak diğer cihazlara ilan edilecektir.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Küme durumunda Cihaz ID yerine bunu göster. Eğer düğüm ismi boş bırakılırsa düğüm ismi güncellenip ilan edilecektir.",
"Shutdown": "Kapat",
"Shutdown Complete": "Kapatma İşlemi Tamamlandı",
"Simple File Versioning": "Basit Dosya Sürümlendirme",
"Single level wildcard (matches within a directory only)": "Tekli düzey wildcard (sadece bir dizin içinde eşleşme)",
"Source Code": "Kaynak Kodu",
"Staggered File Versioning": "Aşamalı Dosya Sürümlendirme",
"Start Browser": "Tarayıcıyı Başlat",
"Stopped": "Durduruldu",
"Support": "Destek",
"Support / Forum": "Destek / Forum",
"Sync Protocol Listen Addresses": "Sync Protokol Dinleme Adresleri",
"Synchronization": "Senkronizasyon",
"Syncing": "Senkronize ediliyor",
"Syncthing has been shut down.": "Syncthing durduruldu",
"Syncthing includes the following software or portions thereof:": "Syncthing aşağıdaki yazılımları veya bunların bölümlerini içermektedir:",
"Syncthing is restarting.": "Syncthing yeniden başlatılıyor.",
"Syncthing is upgrading.": "Syncthing yükseltiliyor.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing görünüşe durdu veya internetin bağlantınızda problem var. Tekrar deniyor....",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing isteminizi işleme alırken bir sorunla karşılaştı. Lütfen tarayıcınızı yeniden yükleyin veya sorun devam ediyorsa Syncthing'i yeniden başlatın.",
"The aggregated statistics are publicly available at {%url%}.": "Toplanan halka açık istatistiklere ulaşabileceğiniz adres {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Ayarlar kaydedildi ancak aktifleştirilmedi. Aktifleştirmek için Syncthing yeniden başlatılmalı.",
"The device ID cannot be blank.": "Cihaz ID boş olamaz.",
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "Buraya girilecek cihaz ID'si diğer düğümde \"Düzenle > ID Göster\" menüsünden bulunabilir. Boşluk ve kısa çizginin olup olmaması önemli değildir. (İhmal edilir)",
"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.": "Şifrelenmiş kullanım bilgisi günlük olarak gönderilir. Platform, klasör büyüklüğü ve uygulama sürümü hakkında bilgi toplanır. Toplanan bilgi çeşidi değişecek olursa, sizden tekrar onay istenecek.",
"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.": "Girilen cihaz ID'si geçerli gibi gözükmüyor. 52 ya da 56 karakter uzunluğunda, harf ve rakamlardan oluşmalı. Boşlukların ve kısa çizgilerin olup olmaması önemli değildir.",
"The folder ID cannot be blank.": "Klasör ID boş olamaz.",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "Klasör ID uzun olmamalı (64 karakter ya da daha az). Sadece harf, rakam, nokta (.), kısa çizgi (-) ve alt çizgi (_) kullanabilirsiniz.",
"The folder ID must be unique.": "Klasör ID benzersiz olmalıdır.",
"The folder path cannot be blank.": "Klasör dizini boş bırakılamaz.",
"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.": "Kullanılan zaman aralıkları : ilk bir saat içinde her 30 saniyede, ilk günde her saatte, ilk 30 günde her gün, azami süreye kadar geçen zamanda ise her hafta yeni sürüm değeri oluşturulur.",
"The maximum age must be a number and cannot be blank.": "Azami süre tanımı boş bırakılmamalı ve bir sayı olarak tanımlanmalıdır.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Bir sürümün tutulması için belirlenen azami süre (sürümleri daimi olarak tutabilmek için 0 değeri atayın)",
"The number of old versions to keep, per file.": "Dosya başına saklanacak eski sürüm.",
"The number of versions must be a number and cannot be blank.": "Sürümlerin sayısı sayı olmalı ve boş bırakılamaz.",
"The rescan interval must be a non-negative number of seconds.": "Tarama zaman aralığı, saniye cinsinden negatif olmayan bir sayı olmalıdır.",
"The rescan interval must be at least 5 seconds.": "Tarama aralığı en az 5 saniye olmalıdır.",
"Unknown": "Bilinmiyor",
"Unshared": "Paylaşılmayan",
"Unused": "Kullanılmayan",
"Up to Date": "Güncel",
"Upgrade To {%version%}": "{{version}} sürümüne yükselt",
"Upgrading": "Yükseltiliyor",
"Upload Rate": "Yükleme hızı",
"Use Compression": "Sıkıştırma kullan",
"Use HTTPS for GUI": "GUI için HTTPS kullan",
"Version": "Sürüm",
"Versions Path": "Sürüm Dizini",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Sürümler, tanımlı azami süre veya belirlenen zaman aralığı için izin verilen dosya sayısıılmışsa kendiliğinden silinir.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Yeni bir cihaz eklendiğinde, bu cihazın karşı tarafa da eklenmesi gerektiğini unutmayın.",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Yeni bir klasör eklendiğinde, Klasör ID'nin klasörleri cihazlar arasında bağlantılandırmak için kullanıldığını unutmayın. Klasör ID'ler büyük - küçük harf duyarlıdır ve bütün cihazlarda tamı tamına eşleşmelidir.",
"Yes": "Evet",
"You must keep at least one version.": "En az bir sürümü tutmalısınız.",
"full documentation": "belgelendirme içeriğinin tümü",
"items": "öğeler",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} \"{{folder}}\" klasörünü paylaşmak istiyor."
}

View File

@@ -0,0 +1,173 @@
{
"API Key": "API ключ",
"About": "Про програму",
"Add": "Додати",
"Add Device": "Додати пристрій",
"Add Folder": "Додати директорію",
"Add new folder?": "Додати нову директорію?",
"Address": "Адреса",
"Addresses": "Адреси",
"Allow Anonymous Usage Reporting?": "Дозволити програмі збирати анонімну статистику викроистання?",
"Anonymous Usage Reporting": "Анонімна статистика використання",
"Any devices configured on an introducer device will be added to this device as well.": "Усі пристрої, налаштовані на пристрої-інтродукторі, будуть додані до поточного пристрою.",
"Automatic upgrades": "Автоматичні оновлення",
"Bugs": "Помилки",
"CPU Utilization": "Навантаження CPU",
"Changelog": "Перелік змін",
"Close": "Закрити",
"Comment, when used at the start of a line": "Коментар, якщо використовується на початку рядка",
"Compression is recommended in most setups.": "Стиснення рекомендується у більшості випадків.",
"Connection Error": "Помилка з’єднання",
"Copied from elsewhere": "Скопійовано з іншого місця",
"Copied from original": "Скопійовано з оригіналу",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Copyright © 2014 Jakob Borg та наступні контриб’ютори:",
"Delete": "Видалити",
"Device ID": "ID пристрою",
"Device Identification": "Ідентифікатор пристрою",
"Device Name": "Назва пристрою",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Пристрій {{device}} ({{address}}) намагається під’єднатися. Додати новий пристрій?",
"Devices": "Пристрої",
"Disconnected": "З’єднання відсутнє",
"Documentation": "Документація",
"Download Rate": "Швидкість завантаження",
"Downloaded": "Завантажено",
"Downloading": "Завантаження",
"Edit": "Редагувати",
"Edit Device": "Редагувати пристрій",
"Edit Folder": "Редагувати директорію",
"Editing": "Редагування",
"Enable UPnP": "Увімкнути UPnP",
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Уведіть адреси \"ip:port\" розділені комою, або слово \"dynamic\" для здійснення автоматичного виявлення адреси.",
"Enter ignore patterns, one per line.": "Введіть шаблони ігнорування, по одному на рядок.",
"Error": "Помилка",
"File Versioning": "Керування версіями",
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "Біти прав доступу до файлів будуть проігноровані під час визначення змін. Використовуйте на файлових системах FAT.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.": "Файли будуть поміщатися у директорію .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.": "Файли захищено від змін зроблених на інших пристроях, але зміни зроблені на цьому пристрої будуть надіслані решті кластеру.",
"Folder ID": "ID директорії",
"Folder Master": "Центральна директорія",
"Folder Path": "Шлях до директорії",
"Folders": "Директорії",
"GUI Authentication Password": "Пароль для доступу до панелі управління",
"GUI Authentication User": "Логін користувача для доступу до панелі управління",
"GUI Listen Addresses": "Адреса доступу до панелі управління",
"Generate": "Згенерувати",
"Global Discovery": "Глобальне виявлення",
"Global Discovery Server": "Сервер для глобального виявлення",
"Global State": "Глобальний статус",
"Idle": "Очікування",
"Ignore": "Ігнорувати",
"Ignore Patterns": "Ігнорувати шаблони",
"Ignore Permissions": "Ігнорувати права доступу до файлів",
"Incoming Rate Limit (KiB/s)": "Ліміт швидкості завантаження (КіБ/с)",
"Introducer": "Інтродуктор",
"Inversion of the given condition (i.e. do not exclude)": "Інверсія поточної умови (тобто не виключає)",
"Keep Versions": "Зберігати версії",
"Last File Received": "Останній завантажений файл",
"Last File Synced": "Останній синхронізований файл",
"Last seen": "З’являвся останній раз",
"Later": "Пізніше",
"Latest Release": "Останній реліз",
"Local Discovery": "Локальне виявлення",
"Local State": "Локальний статус",
"Maximum Age": "Максимальний вік",
"Move to top of queue": "Move to top of queue",
"Multi level wildcard (matches multiple directory levels)": "Багаторівнева маска (пошук збігів в усіх піддиректоріях) ",
"Never": "Ніколи",
"New Device": "Новий пристрій",
"New Folder": "Нова директорія",
"No": "Ні",
"No File Versioning": "Версіонування вимкнено",
"Notice": "Повідомлення",
"OK": "OK",
"Offline": "Офлайн",
"Online": "Онлайн",
"Out Of Sync": "Не синхронізовано",
"Out of Sync Items": "Не синхронізовані елементи",
"Outgoing Rate Limit (KiB/s)": "Ліміт швидкості віддачі (КіБ/с)",
"Override Changes": "Перезаписати зміни",
"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": "Шлях до директорії на локальному комп’ютері. Буде створений, якщо такий не існує. Символ тильди (~) може бути використаний як ярлик для",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Шлях, де повинні зберігатися версії (залиште порожнім для зберігання в .stversions в середині директорії)",
"Please wait": "Будь ласка, зачекайте",
"Preview": "Попередній перегляд",
"Preview Usage Report": "Попередній перегляд статистичного звіту",
"Quick guide to supported patterns": "Швидкий посібник по шаблонам, що підтримуються",
"RAM Utilization": "Використання RAM",
"Rescan": "Пересканувати",
"Rescan All": "Rescan All",
"Rescan Interval": "Інтервал для повторного сканування",
"Restart": "Перезапуск",
"Restart Needed": "Необхідний перезапуск",
"Restarting": "Відбувається перезапуск",
"Reused": "Використано вдруге",
"Save": "Зберегти",
"Scanning": "Сканування",
"Select the devices to share this folder with.": "Оберіть пристрої із якими обміняти дану директорію.",
"Select the folders to share with this device.": "Оберіть директорії які обмінювати із цим пристроєм.",
"Settings": "Налаштування",
"Share": "Розповсюдити ",
"Share Folder": "Розповсюдити каталог",
"Share Folders With Device": "Розділити каталог з пристроєм",
"Share With Devices": "Розповсюдити між пристроями",
"Share this folder?": "Розповсюдити цей каталог?",
"Shared With": "Доступно для",
"Short identifier for the folder. Must be the same on all cluster devices.": "Короткий ідентифікатор для директорії. Має бути однаковим на усіх пристроях кластеру.",
"Show ID": "Показати ID",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Показується замість ID пристрою в статусі кластера. Буде розголошено іншим вузлам як опціональне типове ім’я.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Показується замість ID пристрою в статусі кластера. Буде оновлено ім’ям, яке розголошене пристроєм, якщо залишити порожнім.",
"Shutdown": "Вимкнути",
"Shutdown Complete": "Вимикання завершене",
"Simple File Versioning": "Просте версіонування",
"Single level wildcard (matches within a directory only)": "Однорівнева маска (пошук збігів лише в середині директорії) ",
"Source Code": "Сирцевий код",
"Staggered File Versioning": "Поступове версіонування",
"Start Browser": "Запустити браузер",
"Stopped": "Зупинено",
"Support": "Підтримка",
"Support / Forum": "Підтримка / Форум",
"Sync Protocol Listen Addresses": "Адреса панелі управління",
"Synchronization": "Синхронізація",
"Syncing": "Синхронізація",
"Syncthing has been shut down.": "Syncthing вимкнено (закрито).",
"Syncthing includes the following software or portions thereof:": "Syncthing містить наступне програмне забезпечення (або його частини):",
"Syncthing is restarting.": "Syncthing перезавантажується.",
"Syncthing is upgrading.": "Syncthing оновлюється.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Схоже на те, що Syncthing закритий, або виникла проблема із Інтернет-з’єднанням. Проводиться повторна спроба з’єднання…",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Схоже на те, що Syncthing стикнувся з проблемою оброблюючи ваш запит. Будь ласка перезавантажте сторінку в браузері або перезапустіть Syncthing.",
"The aggregated statistics are publicly available at {%url%}.": "Зібрана статистика публічно доступна за посиланням {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Конфігурацію збережено, але не активовано. Необхідно перезапустити Syncthing для того, щоби активувати нову конфігурацію.",
"The device ID cannot be blank.": "ID пристрою не може бути порожнім.",
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "ID пристрою, який необхідно додати. Може бути знайдений у вікні \"Редагувати > Показати ID\" на іншому пристрої. Пробіли та тире опціональні (вони ігноруються програмою).",
"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.": "Зашифрована статистика використання відсилається щоденно. Вона використовується для того, щоб розробники розуміли, на яких платформах працює програма, розміри директорій та версії програми. Якщо набір даних, що збирається зазнає змін, ви обов’язково будете повідомлені через це діалогове вікно.",
"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.": "Введений ID пристрою невалідний. Ідентифікатор має вигляд строки довжиною 52 або 56 символів, що містить цифри та літери, із опціональними пробілами та тире.",
"The folder ID cannot be blank.": "ID директорії не може бути порожнім.",
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "ID директорії повинен бути коротким ідентифікатором (64 символи або менше), що містить лише цифри та літери, знак крапки (.), тире (-) та нижнього підкреслення (_).",
"The folder ID must be unique.": "ID директорії повинен бути унікальним.",
"The folder path cannot be blank.": "Шлях до директорії не може бути порожнім.",
"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.": "Використовуються наступні інтервали: для першої години версія зберігається кожні 30 секунд, для першого дня версія зберігається щогодини, для перших 30 днів версія зберігається кожен день, опісля, до максимального строку, версія зберігається щотижня.",
"The maximum age must be a number and cannot be blank.": "Максимальний термін повинен бути числом та не може бути пустим.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Максимальний термін, щоб зберігати версію (у днях, вствновіть в 0, щоби зберігати версії назавжди).",
"The number of old versions to keep, per file.": "Кількість старих версій, яку необхідно зберігати для кожного файлу.",
"The number of versions must be a number and cannot be blank.": "Кількість версій повинна бути цифрою та не може бути порожньою.",
"The rescan interval must be a non-negative number of seconds.": "Інтервал повторного сканування повинен бути неід’ємною величиною.",
"The rescan interval must be at least 5 seconds.": "Інтервал повторного сканування повинен бути принаймні 5 секунд.",
"Unknown": "Невідомо",
"Unshared": "Не розповсюджується",
"Unused": "Не використовується",
"Up to Date": "Актуальна версія",
"Upgrade To {%version%}": "Оновити до {{version}}",
"Upgrading": "Оновлення",
"Upload Rate": "Швидкість віддачі",
"Use Compression": "Використовувати компресію",
"Use HTTPS for GUI": "Використовувати HTTPS для доступу до панелі управління",
"Version": "Версія",
"Versions Path": "Шлях до версій",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Версії автоматично видаляються, якщо вони старше, ніж максимальний вік, або перевищують допустиму кількість файлів за інтервал.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Коли додаєте новий пристрій, пам’ятайте, що цей пристрій повинен бути доданий і на іншій стороні.",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Коли додаєте нову директорію, пам’ятайте, що ID цієї директорії використовується для того, щоб зв’язувати директорії разом між вузлами. Назви є чутливими до регістра та повинні співпадати точно між усіма вузлами.",
"Yes": "Так",
"You must keep at least one version.": "Ви повинні зберігати щонайменше одну версію.",
"full documentation": "повна документація",
"items": "елементи",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\"."
}

View File

@@ -71,6 +71,7 @@
"Local Discovery": "在局域网上寻找节点",
"Local State": "本地状态",
"Maximum Age": "历史版本最长保留时间",
"Move to top of queue": "移动到队列顶端",
"Multi level wildcard (matches multiple directory levels)": "多级通配符(用以匹配多层文件夹)",
"Never": "从未",
"New Device": "新设备",
@@ -93,6 +94,7 @@
"Quick guide to supported patterns": "支持的通配符的简单教程:",
"RAM Utilization": "内存使用量",
"Rescan": "重新扫描",
"Rescan All": "全部重新扫描",
"Rescan Interval": "扫描间隔",
"Restart": "重启syncthing",
"Restart Needed": "需要重启Syncthing",

View File

@@ -71,6 +71,7 @@
"Local Discovery": "本地探索",
"Local State": "本地狀態",
"Maximum Age": "最長保留時間",
"Move to top of queue": "Move to top of queue",
"Multi level wildcard (matches multiple directory levels)": "多階層萬用字元 (可比對多層資料夾)",
"Never": "從未",
"New Device": "新裝置",
@@ -93,6 +94,7 @@
"Quick guide to supported patterns": "可支援樣式的快速指南",
"RAM Utilization": "記憶體使用率",
"Rescan": "重新掃描",
"Rescan All": "Rescan All",
"Rescan Interval": "重新掃描間隔",
"Restart": "重新啟動",
"Restart Needed": "需要重新啟動",

View File

@@ -1 +1 @@
var validLangs = ["be","bg","ca","cs","de","en","es","fr","hu","it","lt","nb","nl","nn","pl","pt-PT","ru","sv","zh-CN","zh-TW"]
var validLangs = ["be","bg","ca","cs","de","el","en","en-GB","es","fr","hu","it","lt","nb","nl","nn","pl","pt-BR","pt-PT","ru","sv","tr","uk","zh-CN","zh-TW"]

View File

@@ -192,7 +192,7 @@
</span>
</h3>
</div>
<div id="folder-{{$index}}" class="panel-collapse collapse" ng-class="{in: $index === 0}">
<div id="folder-{{$index}}" class="panel-collapse collapse">
<div class="panel-body">
<table class="table table-condensed table-striped">
<tbody>
@@ -225,7 +225,7 @@
</td>
</tr>
<tr ng-if="model[folder.ID].ignorePatterns">
<th><span class="glyphicon glyphicon-minus"></span>&emsp;<span translate>Ignore Patterns</span></th>
<th><span class="glyphicon glyphicon-eye-close"></span>&emsp;<span translate>Ignore Patterns</span></th>
<td class="text-right">
<span translate>Yes</span>
</td>
@@ -273,10 +273,11 @@
</div>
</div>
</div>
<div class="form-group">
<button class="btn btn-sm btn-default pull-right" ng-click="addFolder()"><span class="glyphicon glyphicon-plus"></span>&emsp;<span translate>Add Folder</span></button>
<div class="clearfix"></div>
</div>
<span class="pull-right">
<button class="btn btn-sm btn-default" ng-click="rescanAllFolders()"><span class="glyphicon glyphicon-repeat"></span>&emsp;<span translate>Rescan All</span></button>
<button class="btn btn-sm btn-default" ng-click="addFolder()"><span class="glyphicon glyphicon-plus"></span>&emsp;<span translate>Add Folder</span></button>
</span>
<div class="clearfix"></div>
<hr class="visible-sm"/>
</div>
@@ -318,7 +319,7 @@
<span ng-if="announceServersFailed.length == 0" class="data text-success">
<span>OK</span>
</span>
<span ng-if="announceServersFailed.length != 0" class="data text-danger">
<span ng-if="announceServersFailed.length != 0" class="data" ng-class="{'text-danger': announceServersFailed.length == announceServersTotal}">
<span popover data-trigger="hover" data-placement="bottom" data-content="{{announceServersFailed.join('\n')}}">
{{announceServersTotal-announceServersFailed.length}}/{{announceServersTotal}}
</span>
@@ -369,9 +370,12 @@
<th><span class="glyphicon glyphicon-link"></span>&emsp;<span translate>Address</span></th>
<td class="text-right">{{deviceAddr(deviceCfg)}}</td>
</tr>
<tr ng-if="!deviceCfg.Compression">
<th><span class="glyphicon glyphicon-compressed"></span>&emsp;<span translate>Use Compression</span></th>
<td translate class="text-right">No</td>
<tr ng-if="deviceCfg.Compression != 'metadata'">
<th><span class="glyphicon glyphicon-compressed"></span>&emsp;<span translate>Compression</span></th>
<td class="text-right">
<span ng-if="deviceCfg.Compression == 'always'" translate>All Data</span>
<span ng-if="deviceCfg.Compression == 'never'" translate>Off</span>
</td>
</tr>
<tr ng-if="deviceCfg.Introducer">
<th><span class="glyphicon glyphicon-thumbs-up"></span>&emsp;<span translate>Introducer</span></th>
@@ -501,12 +505,12 @@
<p translate class="help-block">Enter comma separated "ip:port" addresses or "dynamic" to perform automatic discovery of the address.</p>
</div>
<div ng-if="!editingSelf" class="form-group">
<div class="checkbox">
<label>
<input type="checkbox" ng-model="currentDevice.Compression"> <span translate>Use Compression</span>
</label>
<p translate class="help-block">Compression is recommended in most setups.</p>
</div>
<label translate>Compression</label>
<select class="form-control" ng-model="currentDevice.Compression">
<option value="always" translate>All Data</option>
<option value="metadata" translate>Metadata Only</option>
<option value="never" translate>Off</option>
</select>
</div>
<div ng-if="!editingSelf" class="form-group">
<div class="checkbox">
@@ -941,12 +945,14 @@
<li>Alexander Graf</li>
<li>Arthur Axel fREW Schmidt</li>
<li>Audrius Butkevicius</li>
<li>Ben Curthoys</li>
<li>Ben Schulz</li>
<li>Ben Sidhom</li>
<li>Brandon Philips</li>
<li>Brendan Long</li>
<li>Caleb Callaway</li>
<li>Cathryne Linenweaver</li>
<li>Colin Kennedy</li>
<li>Chris Joel</li>
<li>Daniel Martí</li>
<li>Dennis Wilson</li>
@@ -961,6 +967,7 @@
<li>Jochen Voss</li>
<li>Karol Różycki</li>
<li>Lode Hoste</li>
<li>Marc Laporte</li>
<li>Marcin Dziadus</li>
<li>Michael Jephcote</li>
<li>Michael Tilli</li>
@@ -971,6 +978,7 @@
<li>Ryan Sullivan</li>
<li>Stefan Tatschner</li>
<li>Tim Abell</li>
<li>Tobias Nygren</li>
<li>Tomas Cerveny</li>
<li>Tully Robinson</li>
<li>Veeti Paananen</li>

View File

@@ -713,7 +713,7 @@ angular.module('syncthing.core')
.then(function () {
$scope.currentDevice = {
AddressesStr: 'dynamic',
Compression: true,
Compression: 'metadata',
Introducer: false,
selectedFolders: {}
};
@@ -758,7 +758,7 @@ angular.module('syncthing.core')
var deviceCfg = {
DeviceID: device,
AddressesStr: 'dynamic',
Compression: true,
Compression: 'metadata',
Introducer: false,
selectedFolders: {}
};
@@ -1156,6 +1156,10 @@ angular.module('syncthing.core')
$scope.reportPreview = true;
};
$scope.rescanAllFolders = function () {
$http.post(urlbase + "/scan");
};
$scope.rescanFolder = function (folder) {
$http.post(urlbase + "/scan?folder=" + encodeURIComponent(folder));
};

View File

@@ -7,7 +7,7 @@ angular.module('syncthing.core')
if (scope.editingExisting) {
// we shouldn't validate
ctrl.$setValidity('uniqueFolder', true);
} else if (scope.folders[viewValue]) {
} else if (scope.folders.hasOwnProperty(viewValue)) {
// the folder exists already
ctrl.$setValidity('uniqueFolder', false);
} else {

View File

File diff suppressed because one or more lines are too long

View File

@@ -36,7 +36,7 @@ import (
var l = logger.DefaultLogger
const CurrentVersion = 7
const CurrentVersion = 9
type Configuration struct {
Version int `xml:"version,attr"`
@@ -146,12 +146,12 @@ func (c *VersioningConfiguration) UnmarshalXML(d *xml.Decoder, start xml.StartEl
}
type DeviceConfiguration struct {
DeviceID protocol.DeviceID `xml:"id,attr"`
Name string `xml:"name,attr,omitempty"`
Addresses []string `xml:"address,omitempty"`
Compression bool `xml:"compression,attr"`
CertName string `xml:"certName,attr,omitempty"`
Introducer bool `xml:"introducer,attr"`
DeviceID protocol.DeviceID `xml:"id,attr"`
Name string `xml:"name,attr,omitempty"`
Addresses []string `xml:"address,omitempty"`
Compression protocol.Compression `xml:"compression,attr"`
CertName string `xml:"certName,attr,omitempty"`
Introducer bool `xml:"introducer,attr"`
}
type FolderDeviceConfiguration struct {
@@ -163,7 +163,7 @@ type FolderDeviceConfiguration struct {
type OptionsConfiguration struct {
ListenAddress []string `xml:"listenAddress" default:"0.0.0.0:22000"`
GlobalAnnServers []string `xml:"globalAnnounceServer" default:"udp4://announce.syncthing.net:22026"`
GlobalAnnServers []string `xml:"globalAnnounceServer" default:"udp4://announce.syncthing.net:22026, udp6://announce-v6.syncthing.net:22026"`
GlobalAnnEnabled bool `xml:"globalAnnounceEnabled" default:"true"`
LocalAnnEnabled bool `xml:"localAnnounceEnabled" default:"true"`
LocalAnnPort int `xml:"localAnnouncePort" default:"21025"`
@@ -183,6 +183,7 @@ type OptionsConfiguration struct {
CacheIgnoredFiles bool `xml:"cacheIgnoredFiles" default:"true"`
ProgressUpdateIntervalS int `xml:"progressUpdateIntervalS" default:"5"`
SymlinksEnabled bool `xml:"symlinksEnabled" default:"true"`
LimitBandwidthInLan bool `xml:"limitBandwidthInLan" default:"false"`
Deprecated_RescanIntervalS int `xml:"rescanIntervalS,omitempty" json:"-"`
Deprecated_UREnabled bool `xml:"urEnabled,omitempty" json:"-"`
@@ -262,9 +263,13 @@ func (cfg *Configuration) prepare(myID protocol.DeviceID) {
continue
}
if !strings.HasSuffix(folder.Path, string(filepath.Separator)) {
folder.Path += string(filepath.Separator)
}
// The reason it's done like this:
// C: -> C:\ -> C:\ (issue that this is trying to fix)
// C:\somedir -> C:\somedir\ -> C:\somedir
// C:\somedir\ -> C:\somedir\\ -> C:\somedir
// This way in the tests, we get away without OS specific separators
// in the test configs.
folder.Path = filepath.Dir(folder.Path + string(filepath.Separator))
if folder.ID == "" {
folder.ID = "default"
@@ -293,35 +298,31 @@ func (cfg *Configuration) prepare(myID protocol.DeviceID) {
cfg.Options.Deprecated_URDeclined = false
cfg.Options.Deprecated_UREnabled = false
// Upgrade to v1 configuration if appropriate
// Upgrade configuration versions as appropriate
if cfg.Version == 1 {
convertV1V2(cfg)
}
// Upgrade to v3 configuration if appropriate
if cfg.Version == 2 {
convertV2V3(cfg)
}
// Upgrade to v4 configuration if appropriate
if cfg.Version == 3 {
convertV3V4(cfg)
}
// Upgrade to v5 configuration if appropriate
if cfg.Version == 4 {
convertV4V5(cfg)
}
// Upgrade to v6 configuration if appropriate
if cfg.Version == 5 {
convertV5V6(cfg)
}
// Upgrade to v7 configuration if appropriate
if cfg.Version == 6 {
convertV6V7(cfg)
}
if cfg.Version == 7 {
convertV7V8(cfg)
}
if cfg.Version == 8 {
convertV8V9(cfg)
}
// Hash old cleartext passwords
if len(cfg.GUI.Password) > 0 && cfg.GUI.Password[0] != '$' {
@@ -413,6 +414,22 @@ func ChangeRequiresRestart(from, to Configuration) bool {
return false
}
func convertV8V9(cfg *Configuration) {
// Compression is interpreted and serialized differently, but no enforced
// changes. Still need a new version number since the compression stuff
// isn't understandable by earlier versions.
cfg.Version = 9
}
func convertV7V8(cfg *Configuration) {
// Add IPv6 announce server
if len(cfg.Options.GlobalAnnServers) == 1 && cfg.Options.GlobalAnnServers[0] == "udp4://announce.syncthing.net:22026" {
cfg.Options.GlobalAnnServers = append(cfg.Options.GlobalAnnServers, "udp6://announce-v6.syncthing.net:22026")
}
cfg.Version = 8
}
func convertV6V7(cfg *Configuration) {
// Migrate announce server addresses to the new URL based format
for i := range cfg.Options.GlobalAnnServers {
@@ -491,7 +508,7 @@ func convertV2V3(cfg *Configuration) {
// compression on all existing new. New devices will get compression on by
// default by the GUI.
for i := range cfg.Deprecated_Nodes {
cfg.Deprecated_Nodes[i].Compression = true
cfg.Deprecated_Nodes[i].Compression = protocol.CompressMetadata
}
// The global discovery format and port number changed in v0.9. Having the
@@ -591,8 +608,16 @@ func fillNilSlices(data interface{}) error {
switch f.Interface().(type) {
case []string:
if f.IsNil() {
rv := reflect.MakeSlice(reflect.TypeOf([]string{}), 1, 1)
rv.Index(0).SetString(v)
// Treat the default as a comma separated slice
vs := strings.Split(v, ",")
for i := range vs {
vs[i] = strings.TrimSpace(vs[i])
}
rv := reflect.MakeSlice(reflect.TypeOf([]string{}), len(vs), len(vs))
for i, v := range vs {
rv.Index(i).SetString(v)
}
f.Set(rv)
}
}
@@ -612,6 +637,8 @@ func uniqueStrings(ss []string) []string {
us = append(us, k)
}
sort.Strings(us)
return us
}

View File

@@ -37,7 +37,7 @@ func init() {
func TestDefaultValues(t *testing.T) {
expected := OptionsConfiguration{
ListenAddress: []string{"0.0.0.0:22000"},
GlobalAnnServers: []string{"udp4://announce.syncthing.net:22026"},
GlobalAnnServers: []string{"udp4://announce.syncthing.net:22026", "udp6://announce-v6.syncthing.net:22026"},
GlobalAnnEnabled: true,
LocalAnnEnabled: true,
LocalAnnPort: 21025,
@@ -55,6 +55,7 @@ func TestDefaultValues(t *testing.T) {
CacheIgnoredFiles: true,
ProgressUpdateIntervalS: 5,
SymlinksEnabled: true,
LimitBandwidthInLan: false,
}
cfg := New(device1)
@@ -84,7 +85,7 @@ func TestDeviceConfig(t *testing.T) {
expectedFolders := []FolderConfiguration{
{
ID: "test",
Path: "testdata/",
Path: "testdata",
Devices: []FolderDeviceConfiguration{{DeviceID: device1}, {DeviceID: device4}},
ReadOnly: true,
RescanIntervalS: 600,
@@ -98,13 +99,13 @@ func TestDeviceConfig(t *testing.T) {
DeviceID: device1,
Name: "node one",
Addresses: []string{"a"},
Compression: true,
Compression: protocol.CompressMetadata,
},
{
DeviceID: device4,
Name: "node two",
Addresses: []string{"b"},
Compression: true,
Compression: protocol.CompressMetadata,
},
}
expectedDeviceIDs := []protocol.DeviceID{device1, device4}
@@ -158,6 +159,7 @@ func TestOverriddenValues(t *testing.T) {
CacheIgnoredFiles: false,
ProgressUpdateIntervalS: 10,
SymlinksEnabled: false,
LimitBandwidthInLan: true,
}
cfg, err := Load("testdata/overridenvalues.xml", device1)
@@ -174,24 +176,22 @@ func TestDeviceAddressesDynamic(t *testing.T) {
name, _ := os.Hostname()
expected := map[protocol.DeviceID]DeviceConfiguration{
device1: {
DeviceID: device1,
Addresses: []string{"dynamic"},
Compression: true,
DeviceID: device1,
Addresses: []string{"dynamic"},
},
device2: {
DeviceID: device2,
Addresses: []string{"dynamic"},
Compression: true,
DeviceID: device2,
Addresses: []string{"dynamic"},
},
device3: {
DeviceID: device3,
Addresses: []string{"dynamic"},
Compression: true,
DeviceID: device3,
Addresses: []string{"dynamic"},
},
device4: {
DeviceID: device4,
Name: name, // Set when auto created
Addresses: []string{"dynamic"},
DeviceID: device4,
Name: name, // Set when auto created
Addresses: []string{"dynamic"},
Compression: protocol.CompressMetadata,
},
}
@@ -206,6 +206,43 @@ func TestDeviceAddressesDynamic(t *testing.T) {
}
}
func TestDeviceCompression(t *testing.T) {
name, _ := os.Hostname()
expected := map[protocol.DeviceID]DeviceConfiguration{
device1: {
DeviceID: device1,
Addresses: []string{"dynamic"},
Compression: protocol.CompressMetadata,
},
device2: {
DeviceID: device2,
Addresses: []string{"dynamic"},
Compression: protocol.CompressMetadata,
},
device3: {
DeviceID: device3,
Addresses: []string{"dynamic"},
Compression: protocol.CompressNever,
},
device4: {
DeviceID: device4,
Name: name, // Set when auto created
Addresses: []string{"dynamic"},
Compression: protocol.CompressMetadata,
},
}
cfg, err := Load("testdata/devicecompression.xml", device4)
if err != nil {
t.Error(err)
}
actual := cfg.Devices()
if !reflect.DeepEqual(actual, expected) {
t.Errorf("Devices differ;\n E: %#v\n A: %#v", expected, actual)
}
}
func TestDeviceAddressesStatic(t *testing.T) {
name, _ := os.Hostname()
expected := map[protocol.DeviceID]DeviceConfiguration{
@@ -222,9 +259,10 @@ func TestDeviceAddressesStatic(t *testing.T) {
Addresses: []string{"[2001:db8::44]:4444", "192.0.2.4:6090"},
},
device4: {
DeviceID: device4,
Name: name, // Set when auto created
Addresses: []string{"dynamic"},
DeviceID: device4,
Name: name, // Set when auto created
Addresses: []string{"dynamic"},
Compression: protocol.CompressMetadata,
},
}
@@ -269,7 +307,7 @@ func TestIssue1262(t *testing.T) {
}
actual := cfg.Folders()["test"].Path
expected := "e:/"
expected := "e:"
if runtime.GOOS == "windows" {
expected = `e:\`
}

View File

@@ -0,0 +1,8 @@
<configuration version="5">
<device id="AIR6LPZ7K4PTTUXQSMUUCPQ5YWOEDFIIQJUG7772YQXXR5YD6AWQ" compression="true">
</device>
<device id="GYRZZQBIRNPV4T7TC52WEQYJ3TFDQW6MWDFLMU4SSSU6EMFBK2VA" compression="metadata">
</device>
<device id="LGFPDIT7SKNNJVJZA4FC7QNCRKCE753K72BW5QD2FOZ7FRFEP57Q" compression="false">
</device>
</configuration>

View File

@@ -21,5 +21,6 @@
<cacheIgnoredFiles>false</cacheIgnoredFiles>
<progressUpdateIntervalS>10</progressUpdateIntervalS>
<symlinksEnabled>false</symlinksEnabled>
<limitBandwidthInLan>true</limitBandwidthInLan>
</options>
</configuration>

View File

@@ -1,5 +1,5 @@
<configuration version="1">
<repository id="test" directory="testdata/">
<repository id="test" directory="testdata">
<node id="AIR6LPZ7K4PTTUXQSMUUCPQ5YWOEDFIIQJUG7772YQXXR5YD6AWQ" name="node one">
<address>a</address>
</node>

View File

@@ -1,5 +1,5 @@
<configuration version="2">
<repository id="test" directory="testdata/" ro="true">
<repository id="test" directory="testdata" ro="true">
<node id="P56IOI7MZJNU2IQGDREYDM2MGTMGL3BXNPQ6W5BTBBZ4TJXZWICQ"/>
<node id="AIR6LPZ7K4PTTUXQSMUUCPQ5YWOEDFIIQJUG7772YQXXR5YD6AWQ"/>
<node id="C4YBIESWDUAIGU62GOSRXCRAAJDWVE3TKCPMURZE2LH5QHAF576A"/>

View File

@@ -1,5 +1,5 @@
<configuration version="3">
<repository id="test" directory="testdata/" ro="true" ignorePerms="false">
<repository id="test" directory="testdata" ro="true" ignorePerms="false">
<node id="AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR" compression="false"></node>
<node id="P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2" compression="false"></node>
</repository>

View File

@@ -1,5 +1,5 @@
<configuration version="4">
<repository id="test" directory="testdata/" ro="true" ignorePerms="false" rescanIntervalS="600">
<repository id="test" directory="testdata" ro="true" ignorePerms="false" rescanIntervalS="600">
<node id="AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR"></node>
<node id="P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2"></node>
</repository>

View File

@@ -1,5 +1,5 @@
<configuration version="5">
<folder id="test" path="testdata/" ro="true" ignorePerms="false" rescanIntervalS="600">
<folder id="test" path="testdata" ro="true" ignorePerms="false" rescanIntervalS="600">
<device id="AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR"></device>
<device id="P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2"></device>
</folder>

View File

@@ -1,5 +1,5 @@
<configuration version="6">
<folder id="test" path="testdata/" ro="true" ignorePerms="false" rescanIntervalS="600">
<folder id="test" path="testdata" ro="true" ignorePerms="false" rescanIntervalS="600">
<device id="AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR"></device>
<device id="P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2"></device>
</folder>

View File

@@ -1,5 +1,5 @@
<configuration version="7">
<folder id="test" path="testdata/" ro="true" ignorePerms="false" rescanIntervalS="600">
<folder id="test" path="testdata" ro="true" ignorePerms="false" rescanIntervalS="600">
<device id="AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR"></device>
<device id="P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2"></device>
</folder>

12
internal/config/testdata/v8.xml vendored Normal file
View File

@@ -0,0 +1,12 @@
<configuration version="8">
<folder id="test" path="testdata" ro="true" ignorePerms="false" rescanIntervalS="600">
<device id="AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR"></device>
<device id="P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2"></device>
</folder>
<device id="AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR" name="node one" compression="true">
<address>a</address>
</device>
<device id="P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2" name="node two" compression="true">
<address>b</address>
</device>
</configuration>

12
internal/config/testdata/v9.xml vendored Normal file
View File

@@ -0,0 +1,12 @@
<configuration version="9">
<folder id="test" path="testdata" ro="true" ignorePerms="false" rescanIntervalS="600">
<device id="AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR"></device>
<device id="P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2"></device>
</folder>
<device id="AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR" name="node one" compression="metadata">
<address>a</address>
</device>
<device id="P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2" name="node two" compression="metadata">
<address>b</address>
</device>
</configuration>

View File

@@ -199,7 +199,7 @@ func (f *BlockFinder) Fix(folder, file string, index int32, oldHash, newHash []b
// file name (variable size)
func toBlockKey(hash []byte, folder, file string) []byte {
o := make([]byte, 1+64+32+len(file))
o[0] = keyTypeBlock
o[0] = KeyTypeBlock
copy(o[1:], []byte(folder))
copy(o[1+64:], []byte(hash))
copy(o[1+64+32:], []byte(file))
@@ -210,7 +210,7 @@ func fromBlockKey(data []byte) (string, string) {
if len(data) < 1+64+32+1 {
panic("Incorrect key length")
}
if data[0] != keyTypeBlock {
if data[0] != KeyTypeBlock {
panic("Incorrect key type")
}

View File

@@ -50,9 +50,11 @@ func clock(v int64) int64 {
}
const (
keyTypeDevice = iota
keyTypeGlobal
keyTypeBlock
KeyTypeDevice = iota
KeyTypeGlobal
KeyTypeBlock
KeyTypeDeviceStatistic
KeyTypeFolderStatistic
)
type fileVersion struct {
@@ -102,6 +104,9 @@ type dbWriter interface {
Delete([]byte)
}
// Flush batches to disk when they contain this many records.
const batchFlushSize = 64
// deviceKey returns a byte slice encoding the following information:
// keyTypeDevice (1 byte)
// folder (64 bytes)
@@ -109,7 +114,7 @@ type dbWriter interface {
// name (variable size)
func deviceKey(folder, device, file []byte) []byte {
k := make([]byte, 1+64+32+len(file))
k[0] = keyTypeDevice
k[0] = KeyTypeDevice
if len(folder) > 64 {
panic("folder name too long")
}
@@ -142,7 +147,7 @@ func deviceKeyDevice(key []byte) []byte {
// name (variable size)
func globalKey(folder, file []byte) []byte {
k := make([]byte, 1+64+len(file))
k[0] = keyTypeGlobal
k[0] = KeyTypeGlobal
if len(folder) > 64 {
panic("folder name too long")
}
@@ -275,6 +280,21 @@ func ldbGenericReplace(db *leveldb.DB, folder, device []byte, fs []protocol.File
}
moreDb = dbi.Next()
}
// Write out and reuse the batch every few records, to avoid the batch
// growing too large and thus allocating unnecessarily much memory.
if batch.Len() > batchFlushSize {
if debugDB {
l.Debugf("db.Write %p", batch)
}
err = db.Write(batch, nil)
if err != nil {
panic(err)
}
batch.Reset()
}
}
if debugDB {
@@ -393,6 +413,21 @@ func ldbUpdate(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) in
ldbUpdateGlobal(snap, batch, folder, device, name, f.Version)
}
}
// Write out and reuse the batch every few records, to avoid the batch
// growing too large and thus allocating unnecessarily much memory.
if batch.Len() > batchFlushSize {
if debugDB {
l.Debugf("db.Write %p", batch)
}
err = db.Write(batch, nil)
if err != nil {
panic(err)
}
batch.Reset()
}
}
if debugDB {
@@ -674,11 +709,9 @@ func ldbGetGlobal(db *leveldb.DB, folder, file []byte, truncate bool) (FileIntf,
return fi, true
}
func ldbWithGlobal(db *leveldb.DB, folder []byte, truncate bool, fn Iterator) {
func ldbWithGlobal(db *leveldb.DB, folder, prefix []byte, truncate bool, fn Iterator) {
runtime.GC()
start := globalKey(folder, nil)
limit := globalKey(folder, []byte{0xff, 0xff, 0xff, 0xff})
snap, err := db.GetSnapshot()
if err != nil {
panic(err)
@@ -693,7 +726,7 @@ func ldbWithGlobal(db *leveldb.DB, folder []byte, truncate bool, fn Iterator) {
snap.Release()
}()
dbi := snap.NewIterator(&util.Range{Start: start, Limit: limit}, nil)
dbi := snap.NewIterator(util.BytesPrefix(globalKey(folder, prefix)), nil)
defer dbi.Release()
for dbi.Next() {
@@ -868,8 +901,6 @@ outer:
func ldbListFolders(db *leveldb.DB) []string {
runtime.GC()
start := []byte{keyTypeGlobal}
limit := []byte{keyTypeGlobal + 1}
snap, err := db.GetSnapshot()
if err != nil {
panic(err)
@@ -884,7 +915,7 @@ func ldbListFolders(db *leveldb.DB) []string {
snap.Release()
}()
dbi := snap.NewIterator(&util.Range{Start: start, Limit: limit}, nil)
dbi := snap.NewIterator(util.BytesPrefix([]byte{KeyTypeGlobal}), nil)
defer dbi.Release()
folderExists := make(map[string]bool)
@@ -922,9 +953,7 @@ func ldbDropFolder(db *leveldb.DB, folder []byte) {
}()
// Remove all items related to the given folder from the device->file bucket
start := []byte{keyTypeDevice}
limit := []byte{keyTypeDevice + 1}
dbi := snap.NewIterator(&util.Range{Start: start, Limit: limit}, nil)
dbi := snap.NewIterator(util.BytesPrefix([]byte{KeyTypeDevice}), nil)
for dbi.Next() {
itemFolder := deviceKeyFolder(dbi.Key())
if bytes.Compare(folder, itemFolder) == 0 {
@@ -934,9 +963,7 @@ func ldbDropFolder(db *leveldb.DB, folder []byte) {
dbi.Release()
// Remove all items related to the given folder from the global bucket
start = []byte{keyTypeGlobal}
limit = []byte{keyTypeGlobal + 1}
dbi = snap.NewIterator(&util.Range{Start: start, Limit: limit}, nil)
dbi = snap.NewIterator(util.BytesPrefix([]byte{KeyTypeGlobal}), nil)
for dbi.Next() {
itemFolder := globalKeyFolder(dbi.Key())
if bytes.Compare(folder, itemFolder) == 0 {

106
internal/db/namespaced.go Normal file
View File

@@ -0,0 +1,106 @@
// Copyright (C) 2014 The Syncthing Authors.
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>.
package db
import (
"encoding/binary"
"time"
"github.com/syndtr/goleveldb/leveldb"
)
// NamespacedKV is a simple key-value store using a specific namespace within
// a leveldb.
type NamespacedKV struct {
db *leveldb.DB
prefix []byte
}
// NewNamespacedKV returns a new NamespacedKV that lives in the namespace
// specified by the prefix.
func NewNamespacedKV(db *leveldb.DB, prefix string) *NamespacedKV {
return &NamespacedKV{
db: db,
prefix: []byte(prefix),
}
}
// PutInt64 stores a new int64. Any existing value (even if of another type)
// is overwritten.
func (n *NamespacedKV) PutInt64(key string, val int64) {
keyBs := append(n.prefix, []byte(key)...)
var valBs [8]byte
binary.BigEndian.PutUint64(valBs[:], uint64(val))
n.db.Put(keyBs, valBs[:], nil)
}
// Int64 returns the stored value interpreted as an int64 and a boolean that
// is false if no value was stored at the key.
func (n *NamespacedKV) Int64(key string) (int64, bool) {
keyBs := append(n.prefix, []byte(key)...)
valBs, err := n.db.Get(keyBs, nil)
if err != nil {
return 0, false
}
val := binary.BigEndian.Uint64(valBs)
return int64(val), true
}
// PutTime stores a new time.Time. Any existing value (even if of another
// type) is overwritten.
func (n *NamespacedKV) PutTime(key string, val time.Time) {
keyBs := append(n.prefix, []byte(key)...)
valBs, _ := val.MarshalBinary() // never returns an error
n.db.Put(keyBs, valBs, nil)
}
// Time returns the stored value interpreted as a time.Time and a boolean
// that is false if no value was stored at the key.
func (n NamespacedKV) Time(key string) (time.Time, bool) {
var t time.Time
keyBs := append(n.prefix, []byte(key)...)
valBs, err := n.db.Get(keyBs, nil)
if err != nil {
return t, false
}
err = t.UnmarshalBinary(valBs)
return t, err == nil
}
// PutString stores a new string. Any existing value (even if of another type)
// is overwritten.
func (n *NamespacedKV) PutString(key, val string) {
keyBs := append(n.prefix, []byte(key)...)
n.db.Put(keyBs, []byte(val), nil)
}
// String returns the stored value interpreted as a string and a boolean that
// is false if no value was stored at the key.
func (n NamespacedKV) String(key string) (string, bool) {
keyBs := append(n.prefix, []byte(key)...)
valBs, err := n.db.Get(keyBs, nil)
if err != nil {
return "", false
}
return string(valBs), true
}
// Delete deletes the specified key. It is allowed to delete a nonexistent
// key.
func (n NamespacedKV) Delete(key string) {
keyBs := append(n.prefix, []byte(key)...)
n.db.Delete(keyBs, nil)
}

View File

@@ -0,0 +1,101 @@
// Copyright (C) 2014 The Syncthing Authors.
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>.
package db
import (
"testing"
"time"
"github.com/syndtr/goleveldb/leveldb"
"github.com/syndtr/goleveldb/leveldb/storage"
)
func TestNamespacedInt(t *testing.T) {
ldb, err := leveldb.Open(storage.NewMemStorage(), nil)
if err != nil {
t.Fatal(err)
}
n1 := NewNamespacedKV(ldb, "foo")
n2 := NewNamespacedKV(ldb, "bar")
// Key is missing to start with
if v, ok := n1.Int64("test"); v != 0 || ok {
t.Errorf("Incorrect return v %v != 0 || ok %v != false", v, ok)
}
n1.PutInt64("test", 42)
// It should now exist in n1
if v, ok := n1.Int64("test"); v != 42 || !ok {
t.Errorf("Incorrect return v %v != 42 || ok %v != true", v, ok)
}
// ... but not in n2, which is in a different namespace
if v, ok := n2.Int64("test"); v != 0 || ok {
t.Errorf("Incorrect return v %v != 0 || ok %v != false", v, ok)
}
n1.Delete("test")
// It should no longer exist
if v, ok := n1.Int64("test"); v != 0 || ok {
t.Errorf("Incorrect return v %v != 0 || ok %v != false", v, ok)
}
}
func TestNamespacedTime(t *testing.T) {
ldb, err := leveldb.Open(storage.NewMemStorage(), nil)
if err != nil {
t.Fatal(err)
}
n1 := NewNamespacedKV(ldb, "foo")
if v, ok := n1.Time("test"); v != (time.Time{}) || ok {
t.Errorf("Incorrect return v %v != %v || ok %v != false", v, time.Time{}, ok)
}
now := time.Now()
n1.PutTime("test", now)
if v, ok := n1.Time("test"); v != now || !ok {
t.Errorf("Incorrect return v %v != %v || ok %v != true", v, now, ok)
}
}
func TestNamespacedString(t *testing.T) {
ldb, err := leveldb.Open(storage.NewMemStorage(), nil)
if err != nil {
t.Fatal(err)
}
n1 := NewNamespacedKV(ldb, "foo")
if v, ok := n1.String("test"); v != "" || ok {
t.Errorf("Incorrect return v %q != \"\" || ok %v != false", v, ok)
}
n1.PutString("test", "yo")
if v, ok := n1.String("test"); v != "yo" || !ok {
t.Errorf("Incorrect return v %q != \"yo\" || ok %v != true", v, ok)
}
}

View File

@@ -172,14 +172,21 @@ func (s *FileSet) WithGlobal(fn Iterator) {
if debug {
l.Debugf("%s WithGlobal()", s.folder)
}
ldbWithGlobal(s.db, []byte(s.folder), false, nativeFileIterator(fn))
ldbWithGlobal(s.db, []byte(s.folder), nil, false, nativeFileIterator(fn))
}
func (s *FileSet) WithGlobalTruncated(fn Iterator) {
if debug {
l.Debugf("%s WithGlobalTruncated()", s.folder)
}
ldbWithGlobal(s.db, []byte(s.folder), true, nativeFileIterator(fn))
ldbWithGlobal(s.db, []byte(s.folder), nil, true, nativeFileIterator(fn))
}
func (s *FileSet) WithPrefixedGlobalTruncated(prefix string, fn Iterator) {
if debug {
l.Debugf("%s WithPrefixedGlobalTruncated()", s.folder, prefix)
}
ldbWithGlobal(s.db, []byte(s.folder), []byte(osutil.NormalizedFilename(prefix)), true, nativeFileIterator(fn))
}
func (s *FileSet) Get(device protocol.DeviceID, file string) (protocol.FileInfo, bool) {

View File

@@ -13,69 +13,33 @@
// You should have received a copy of the GNU General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>.
//go:generate -command genxdr go run ../../Godeps/_workspace/src/github.com/calmh/xdr/cmd/genxdr/main.go
//go:generate genxdr -o truncated_xdr.go truncated.go
package db
import (
"fmt"
import "github.com/syncthing/protocol"
"github.com/syncthing/protocol"
)
// Used for unmarshalling a FileInfo structure but skipping the block list.
type FileInfoTruncated struct {
Name string // max:8192
Flags uint32
Modified int64
Version int64
LocalVersion int64
NumBlocks int32
protocol.FileInfo
ActualSize int64
}
func ToTruncated(file protocol.FileInfo) FileInfoTruncated {
return FileInfoTruncated{
Name: file.Name,
Flags: file.Flags,
Modified: file.Modified,
Version: file.Version,
LocalVersion: file.LocalVersion,
NumBlocks: int32(len(file.Blocks)),
t := FileInfoTruncated{
FileInfo: file,
ActualSize: file.Size(),
}
t.FileInfo.Blocks = nil
return t
}
func (f FileInfoTruncated) String() string {
return fmt.Sprintf("File{Name:%q, Flags:0%o, Modified:%d, Version:%d, Size:%d, NumBlocks:%d}",
f.Name, f.Flags, f.Modified, f.Version, f.Size(), f.NumBlocks)
func (f *FileInfoTruncated) UnmarshalXDR(bs []byte) error {
err := f.FileInfo.UnmarshalXDR(bs)
f.ActualSize = f.FileInfo.Size()
f.FileInfo.Blocks = nil
return err
}
// Returns a statistical guess on the size, not the exact figure
func (f FileInfoTruncated) Size() int64 {
if f.IsDeleted() || f.IsDirectory() {
return 128
}
return BlocksToSize(int(f.NumBlocks))
}
func (f FileInfoTruncated) IsDeleted() bool {
return f.Flags&protocol.FlagDeleted != 0
}
func (f FileInfoTruncated) IsInvalid() bool {
return f.Flags&protocol.FlagInvalid != 0
}
func (f FileInfoTruncated) IsDirectory() bool {
return f.Flags&protocol.FlagDirectory != 0
}
func (f FileInfoTruncated) IsSymlink() bool {
return f.Flags&protocol.FlagSymlink != 0
}
func (f FileInfoTruncated) HasPermissionBits() bool {
return f.Flags&protocol.FlagNoPermBits == 0
return f.ActualSize
}
func BlocksToSize(num int) int64 {

View File

@@ -1,112 +0,0 @@
// ************************************************************
// This file is automatically generated by genxdr. Do not edit.
// ************************************************************
package db
import (
"bytes"
"io"
"github.com/calmh/xdr"
)
/*
FileInfoTruncated Structure:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Length of Name |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
/ /
\ Name (variable length) \
/ /
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Flags |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Modified (64 bits) +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Version (64 bits) +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| |
+ Local Version (64 bits) +
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Num Blocks |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
struct FileInfoTruncated {
string Name<8192>;
unsigned int Flags;
hyper Modified;
hyper Version;
hyper LocalVersion;
int NumBlocks;
}
*/
func (o FileInfoTruncated) EncodeXDR(w io.Writer) (int, error) {
var xw = xdr.NewWriter(w)
return o.encodeXDR(xw)
}
func (o FileInfoTruncated) MarshalXDR() ([]byte, error) {
return o.AppendXDR(make([]byte, 0, 128))
}
func (o FileInfoTruncated) MustMarshalXDR() []byte {
bs, err := o.MarshalXDR()
if err != nil {
panic(err)
}
return bs
}
func (o FileInfoTruncated) AppendXDR(bs []byte) ([]byte, error) {
var aw = xdr.AppendWriter(bs)
var xw = xdr.NewWriter(&aw)
_, err := o.encodeXDR(xw)
return []byte(aw), err
}
func (o FileInfoTruncated) encodeXDR(xw *xdr.Writer) (int, error) {
if l := len(o.Name); l > 8192 {
return xw.Tot(), xdr.ElementSizeExceeded("Name", l, 8192)
}
xw.WriteString(o.Name)
xw.WriteUint32(o.Flags)
xw.WriteUint64(uint64(o.Modified))
xw.WriteUint64(uint64(o.Version))
xw.WriteUint64(uint64(o.LocalVersion))
xw.WriteUint32(uint32(o.NumBlocks))
return xw.Tot(), xw.Error()
}
func (o *FileInfoTruncated) DecodeXDR(r io.Reader) error {
xr := xdr.NewReader(r)
return o.decodeXDR(xr)
}
func (o *FileInfoTruncated) UnmarshalXDR(bs []byte) error {
var br = bytes.NewReader(bs)
var xr = xdr.NewReader(br)
return o.decodeXDR(xr)
}
func (o *FileInfoTruncated) decodeXDR(xr *xdr.Reader) error {
o.Name = xr.ReadStringMax(8192)
o.Flags = xr.ReadUint32()
o.Modified = int64(xr.ReadUint64())
o.Version = int64(xr.ReadUint64())
o.LocalVersion = int64(xr.ReadUint64())
o.NumBlocks = int32(xr.ReadUint32())
return xr.Error()
}

View File

@@ -94,7 +94,9 @@ func (d *UDPClient) broadcast(pkt []byte) {
conn, err := net.ListenUDP(d.url.Scheme, d.listenAddress)
for err != nil {
l.Warnf("discover %s: broadcast: %v; trying again in %v", d.url, err, d.errorRetryInterval)
if debug {
l.Debugf("discover %s: broadcast listen: %v; trying again in %v", d.url, err, d.errorRetryInterval)
}
select {
case <-d.stop:
return
@@ -106,7 +108,9 @@ func (d *UDPClient) broadcast(pkt []byte) {
remote, err := net.ResolveUDPAddr(d.url.Scheme, d.url.Host)
for err != nil {
l.Warnf("discover %s: broadcast: %v; trying again in %v", d.url, err, d.errorRetryInterval)
if debug {
l.Debugf("discover %s: broadcast resolve: %v; trying again in %v", d.url, err, d.errorRetryInterval)
}
select {
case <-d.stop:
return

View File

@@ -54,15 +54,22 @@ func Convert(pattern string, flags int) (*regexp.Regexp, error) {
pattern = strings.Replace(pattern, "\\?", "[:escapedques:]", -1)
pattern = strings.Replace(pattern, "\\.", "[:escapeddot:]", -1)
}
pattern = strings.Replace(pattern, ".", "\\.", -1)
pattern = strings.Replace(pattern, "+", "\\+", -1)
// Characters that are special in regexps but not in glob, must be
// escaped.
for _, char := range []string{".", "+", "$", "^", "(", ")", "|"} {
pattern = strings.Replace(pattern, char, "\\"+char, -1)
}
pattern = strings.Replace(pattern, "**", "[:doublestar:]", -1)
pattern = strings.Replace(pattern, "*", any+"*", -1)
pattern = strings.Replace(pattern, "[:doublestar:]", ".*", -1)
pattern = strings.Replace(pattern, "?", any, -1)
pattern = strings.Replace(pattern, "[:escapedstar:]", "\\*", -1)
pattern = strings.Replace(pattern, "[:escapedques:]", "\\?", -1)
pattern = strings.Replace(pattern, "[:escapeddot:]", "\\.", -1)
pattern = "^" + pattern + "$"
if flags&FNM_CASEFOLD != 0 {
pattern = "(?i)" + pattern

View File

@@ -62,6 +62,16 @@ var testcases = []testcase{
{"**/foo.txt", "bar/baz/foo.txt", FNM_PATHNAME, true},
{"foo.txt", "foo.TXT", FNM_CASEFOLD, true},
// These characters are literals in glob, but not in regexp.
{"hey$hello", "hey$hello", 0, true},
{"hey^hello", "hey^hello", 0, true},
{"hey{hello", "hey{hello", 0, true},
{"hey}hello", "hey}hello", 0, true},
{"hey(hello", "hey(hello", 0, true},
{"hey)hello", "hey)hello", 0, true},
{"hey|hello", "hey|hello", 0, true},
{"hey|hello", "hey|other", 0, false},
}
func TestMatch(t *testing.T) {

View File

@@ -485,7 +485,13 @@ func (m *Model) Index(deviceID protocol.DeviceID, folder string, fs []protocol.F
for i := 0; i < len(fs); {
lamport.Default.Tick(fs[i].Version)
if symlinkInvalid(fs[i].IsSymlink()) {
if fs[i].Flags&^protocol.FlagsAll != 0 {
if debug {
l.Debugln("dropping update for file with unknown bits set", fs[i])
}
fs[i] = fs[len(fs)-1]
fs = fs[:len(fs)-1]
} else if symlinkInvalid(fs[i].IsSymlink()) {
if debug {
l.Debugln("dropping update for unsupported symlink", fs[i])
}
@@ -528,7 +534,13 @@ func (m *Model) IndexUpdate(deviceID protocol.DeviceID, folder string, fs []prot
for i := 0; i < len(fs); {
lamport.Default.Tick(fs[i].Version)
if symlinkInvalid(fs[i].IsSymlink()) {
if fs[i].Flags&^protocol.FlagsAll != 0 {
if debug {
l.Debugln("dropping update for file with unknown bits set", fs[i])
}
fs[i] = fs[len(fs)-1]
fs = fs[:len(fs)-1]
} else if symlinkInvalid(fs[i].IsSymlink()) {
if debug {
l.Debugln("dropping update for unsupported symlink", fs[i])
}
@@ -1099,7 +1111,7 @@ func (m *Model) AddFolder(cfg config.FolderConfiguration) {
m.fmut.Unlock()
}
func (m *Model) ScanFolders() {
func (m *Model) ScanFolders() map[string]error {
m.fmut.RLock()
var folders = make([]string, 0, len(m.folderCfgs))
for folder := range m.folderCfgs {
@@ -1107,6 +1119,9 @@ func (m *Model) ScanFolders() {
}
m.fmut.RUnlock()
var errors = make(map[string]error, len(m.folderCfgs))
var errorsMut sync.Mutex
var wg sync.WaitGroup
wg.Add(len(folders))
for _, folder := range folders {
@@ -1114,12 +1129,16 @@ func (m *Model) ScanFolders() {
go func() {
err := m.ScanFolder(folder)
if err != nil {
errorsMut.Lock()
errors[folder] = err
errorsMut.Unlock()
m.cfg.InvalidateFolder(folder, err.Error())
}
wg.Done()
}()
}
wg.Wait()
return errors
}
func (m *Model) ScanFolder(folder string) error {
@@ -1127,6 +1146,7 @@ func (m *Model) ScanFolder(folder string) error {
}
func (m *Model) ScanFolderSub(folder, sub string) error {
sub = osutil.NativeFilename(sub)
if p := filepath.Clean(filepath.Join(folder, sub)); !strings.HasPrefix(p, folder) {
return errors.New("invalid subpath")
}
@@ -1143,6 +1163,18 @@ func (m *Model) ScanFolderSub(folder, sub string) error {
_ = ignores.Load(filepath.Join(folderCfg.Path, ".stignore")) // Ignore error, there might not be an .stignore
// Required to make sure that we start indexing at a directory we're already
// aware off.
for sub != "" {
if _, ok = fs.Get(protocol.LocalDeviceID, sub); ok {
break
}
sub = filepath.Dir(sub)
if sub == "." || sub == string(filepath.Separator) {
sub = ""
}
}
w := &scanner.Walker{
Dir: folderCfg.Path,
Sub: sub,
@@ -1223,8 +1255,16 @@ func (m *Model) ScanFolderSub(folder, sub string) error {
"size": f.Size(),
})
batch = append(batch, nf)
} else if _, err := os.Lstat(filepath.Join(folderCfg.Path, f.Name)); err != nil && os.IsNotExist(err) {
// File has been deleted
} else if _, err := os.Lstat(filepath.Join(folderCfg.Path, f.Name)); err != nil {
// File has been deleted.
// We don't specifically verify that the error is
// os.IsNotExist because there is a corner case when a
// directory is suddenly transformed into a file. When that
// happens, files that were in the directory (that is now a
// file) are deleted but will return a confusing error ("not a
// directory") when we try to Lstat() them.
nf := protocol.FileInfo{
Name: f.Name,
Flags: f.Flags | protocol.FlagDeleted,
@@ -1390,6 +1430,69 @@ func (m *Model) RemoteLocalVersion(folder string) int64 {
return ver
}
func (m *Model) GlobalDirectoryTree(folder, prefix string, levels int, dirsonly bool) map[string]interface{} {
m.fmut.RLock()
files, ok := m.folderFiles[folder]
m.fmut.RUnlock()
if !ok {
return nil
}
output := make(map[string]interface{})
sep := string(filepath.Separator)
prefix = osutil.NativeFilename(prefix)
if prefix != "" && !strings.HasSuffix(prefix, sep) {
prefix = prefix + sep
}
files.WithPrefixedGlobalTruncated(prefix, func(fi db.FileIntf) bool {
f := fi.(db.FileInfoTruncated)
if f.IsInvalid() || f.IsDeleted() || f.Name == prefix {
return true
}
f.Name = strings.Replace(f.Name, prefix, "", 1)
var dir, base string
if f.IsDirectory() && !f.IsSymlink() {
dir = f.Name
} else {
dir = filepath.Dir(f.Name)
base = filepath.Base(f.Name)
}
if levels > -1 && strings.Count(f.Name, sep) > levels {
return true
}
last := output
if dir != "." {
for _, path := range strings.Split(dir, sep) {
directory, ok := last[path]
if !ok {
newdir := make(map[string]interface{})
last[path] = newdir
last = newdir
} else {
last = directory.(map[string]interface{})
}
}
}
if !dirsonly && base != "" {
last[base] = []int64{
f.Modified, f.Size(),
}
}
return true
})
return output
}
func (m *Model) availability(folder, file string) []protocol.DeviceID {
// Acquire this lock first, as the value returned from foldersFiles can
// get heavily modified on Close()

View File

@@ -17,8 +17,13 @@ package model
import (
"bytes"
"encoding/json"
"fmt"
"math/rand"
"os"
"path/filepath"
"reflect"
"strconv"
"testing"
"time"
@@ -29,10 +34,31 @@ import (
)
var device1, device2 protocol.DeviceID
var defaultConfig *config.Wrapper
var defaultFolderConfig config.FolderConfiguration
func init() {
device1, _ = protocol.DeviceIDFromString("AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR")
device2, _ = protocol.DeviceIDFromString("GYRZZQB-IRNPV4Z-T7TC52W-EQYJ3TT-FDQW6MW-DFLMU42-SSSU6EM-FBK2VAY")
defaultFolderConfig = config.FolderConfiguration{
ID: "default",
Path: "testdata",
Devices: []config.FolderDeviceConfiguration{
{
DeviceID: device1,
},
},
}
_defaultConfig := config.Configuration{
Folders: []config.FolderConfiguration{defaultFolderConfig},
Devices: []config.DeviceConfiguration{
{
DeviceID: device1,
},
},
}
defaultConfig = config.Wrap("/tmp/test", _defaultConfig)
}
var testDataExpected = map[string]protocol.FileInfo{
@@ -69,10 +95,10 @@ func init() {
func TestRequest(t *testing.T) {
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
m := NewModel(config.Wrap("/tmp/test", config.Configuration{}), "device", "syncthing", "dev", db)
m := NewModel(defaultConfig, "device", "syncthing", "dev", db)
// device1 shares default, but device2 doesn't
m.AddFolder(config.FolderConfiguration{ID: "default", Path: "testdata", Devices: []config.FolderDeviceConfiguration{{DeviceID: device1}}})
m.AddFolder(defaultFolderConfig)
m.ScanFolder("default")
// Existing, shared file
@@ -155,8 +181,8 @@ func genFiles(n int) []protocol.FileInfo {
func BenchmarkIndex10000(b *testing.B) {
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
m := NewModel(nil, "device", "syncthing", "dev", db)
m.AddFolder(config.FolderConfiguration{ID: "default", Path: "testdata"})
m := NewModel(defaultConfig, "device", "syncthing", "dev", db)
m.AddFolder(defaultFolderConfig)
m.ScanFolder("default")
files := genFiles(10000)
@@ -168,8 +194,8 @@ func BenchmarkIndex10000(b *testing.B) {
func BenchmarkIndex00100(b *testing.B) {
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
m := NewModel(nil, "device", "syncthing", "dev", db)
m.AddFolder(config.FolderConfiguration{ID: "default", Path: "testdata"})
m := NewModel(defaultConfig, "device", "syncthing", "dev", db)
m.AddFolder(defaultFolderConfig)
m.ScanFolder("default")
files := genFiles(100)
@@ -181,8 +207,8 @@ func BenchmarkIndex00100(b *testing.B) {
func BenchmarkIndexUpdate10000f10000(b *testing.B) {
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
m := NewModel(nil, "device", "syncthing", "dev", db)
m.AddFolder(config.FolderConfiguration{ID: "default", Path: "testdata"})
m := NewModel(defaultConfig, "device", "syncthing", "dev", db)
m.AddFolder(defaultFolderConfig)
m.ScanFolder("default")
files := genFiles(10000)
m.Index(device1, "default", files)
@@ -195,8 +221,8 @@ func BenchmarkIndexUpdate10000f10000(b *testing.B) {
func BenchmarkIndexUpdate10000f00100(b *testing.B) {
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
m := NewModel(nil, "device", "syncthing", "dev", db)
m.AddFolder(config.FolderConfiguration{ID: "default", Path: "testdata"})
m := NewModel(defaultConfig, "device", "syncthing", "dev", db)
m.AddFolder(defaultFolderConfig)
m.ScanFolder("default")
files := genFiles(10000)
m.Index(device1, "default", files)
@@ -210,8 +236,8 @@ func BenchmarkIndexUpdate10000f00100(b *testing.B) {
func BenchmarkIndexUpdate10000f00001(b *testing.B) {
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
m := NewModel(nil, "device", "syncthing", "dev", db)
m.AddFolder(config.FolderConfiguration{ID: "default", Path: "testdata"})
m := NewModel(defaultConfig, "device", "syncthing", "dev", db)
m.AddFolder(defaultFolderConfig)
m.ScanFolder("default")
files := genFiles(10000)
m.Index(device1, "default", files)
@@ -268,8 +294,8 @@ func (FakeConnection) Statistics() protocol.Statistics {
func BenchmarkRequest(b *testing.B) {
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
m := NewModel(nil, "device", "syncthing", "dev", db)
m.AddFolder(config.FolderConfiguration{ID: "default", Path: "testdata"})
m := NewModel(defaultConfig, "device", "syncthing", "dev", db)
m.AddFolder(defaultFolderConfig)
m.ScanFolder("default")
const n = 1000
@@ -451,12 +477,8 @@ func TestIgnores(t *testing.T) {
}
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
fcfg := config.FolderConfiguration{ID: "default", Path: "testdata"}
cfg := config.Wrap("/tmp", config.Configuration{
Folders: []config.FolderConfiguration{fcfg},
})
m := NewModel(cfg, "device", "syncthing", "dev", db)
m.AddFolder(fcfg)
m := NewModel(defaultConfig, "device", "syncthing", "dev", db)
m.AddFolder(defaultFolderConfig)
expected := []string{
".*",
@@ -525,3 +547,511 @@ func TestIgnores(t *testing.T) {
t.Errorf("Expected no ignores, got: %v", ignores)
}
}
func TestRefuseUnknownBits(t *testing.T) {
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
m := NewModel(defaultConfig, "device", "syncthing", "dev", db)
m.AddFolder(defaultFolderConfig)
m.ScanFolder("default")
m.Index(device1, "default", []protocol.FileInfo{
{
Name: "invalid1",
Flags: (protocol.FlagsAll + 1) &^ protocol.FlagInvalid,
},
{
Name: "invalid2",
Flags: (protocol.FlagsAll + 2) &^ protocol.FlagInvalid,
},
{
Name: "invalid3",
Flags: (1 << 31) &^ protocol.FlagInvalid,
},
{
Name: "valid",
Flags: protocol.FlagsAll &^ (protocol.FlagInvalid | protocol.FlagSymlink),
},
})
for _, name := range []string{"invalid1", "invalid2", "invalid3"} {
f, ok := m.CurrentGlobalFile("default", name)
if ok || f.Name == name {
t.Error("Invalid file found or name match")
}
}
f, ok := m.CurrentGlobalFile("default", "valid")
if !ok || f.Name != "valid" {
t.Error("Valid file not found or name mismatch", ok, f)
}
}
func TestGlobalDirectoryTree(t *testing.T) {
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
m := NewModel(defaultConfig, "device", "syncthing", "dev", db)
m.AddFolder(defaultFolderConfig)
b := func(isfile bool, path ...string) protocol.FileInfo {
var flags uint32 = protocol.FlagDirectory
blocks := []protocol.BlockInfo{}
if isfile {
flags = 0
blocks = []protocol.BlockInfo{{Offset: 0x0, Size: 0xa, Hash: []uint8{0x2f, 0x72, 0xcc, 0x11, 0xa6, 0xfc, 0xd0, 0x27, 0x1e, 0xce, 0xf8, 0xc6, 0x10, 0x56, 0xee, 0x1e, 0xb1, 0x24, 0x3b, 0xe3, 0x80, 0x5b, 0xf9, 0xa9, 0xdf, 0x98, 0xf9, 0x2f, 0x76, 0x36, 0xb0, 0x5c}}}
}
return protocol.FileInfo{
Name: filepath.Join(path...),
Flags: flags,
Modified: 0x666,
Blocks: blocks,
}
}
filedata := []int64{0x666, 0xa}
testdata := []protocol.FileInfo{
b(false, "another"),
b(false, "another", "directory"),
b(true, "another", "directory", "afile"),
b(false, "another", "directory", "with"),
b(false, "another", "directory", "with", "a"),
b(true, "another", "directory", "with", "a", "file"),
b(true, "another", "directory", "with", "file"),
b(true, "another", "file"),
b(false, "other"),
b(false, "other", "rand"),
b(false, "other", "random"),
b(false, "other", "random", "dir"),
b(false, "other", "random", "dirx"),
b(false, "other", "randomx"),
b(false, "some"),
b(false, "some", "directory"),
b(false, "some", "directory", "with"),
b(false, "some", "directory", "with", "a"),
b(true, "some", "directory", "with", "a", "file"),
b(true, "rootfile"),
}
expectedResult := map[string]interface{}{
"another": map[string]interface{}{
"directory": map[string]interface{}{
"afile": filedata,
"with": map[string]interface{}{
"a": map[string]interface{}{
"file": filedata,
},
"file": filedata,
},
},
"file": filedata,
},
"other": map[string]interface{}{
"rand": map[string]interface{}{},
"random": map[string]interface{}{
"dir": map[string]interface{}{},
"dirx": map[string]interface{}{},
},
"randomx": map[string]interface{}{},
},
"some": map[string]interface{}{
"directory": map[string]interface{}{
"with": map[string]interface{}{
"a": map[string]interface{}{
"file": filedata,
},
},
},
},
"rootfile": filedata,
}
mm := func(data interface{}) string {
bytes, err := json.Marshal(data)
if err != nil {
panic(err)
}
return string(bytes)
}
m.Index(device1, "default", testdata)
result := m.GlobalDirectoryTree("default", "", -1, false)
if !reflect.DeepEqual(result, expectedResult) {
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(expectedResult))
}
result = m.GlobalDirectoryTree("default", "another", -1, false)
if !reflect.DeepEqual(result, expectedResult["another"]) {
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(expectedResult["another"]))
}
result = m.GlobalDirectoryTree("default", "", 0, false)
currentResult := map[string]interface{}{
"another": map[string]interface{}{},
"other": map[string]interface{}{},
"some": map[string]interface{}{},
"rootfile": filedata,
}
if !reflect.DeepEqual(result, currentResult) {
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
}
result = m.GlobalDirectoryTree("default", "", 1, false)
currentResult = map[string]interface{}{
"another": map[string]interface{}{
"directory": map[string]interface{}{},
"file": filedata,
},
"other": map[string]interface{}{
"rand": map[string]interface{}{},
"random": map[string]interface{}{},
"randomx": map[string]interface{}{},
},
"some": map[string]interface{}{
"directory": map[string]interface{}{},
},
"rootfile": filedata,
}
if !reflect.DeepEqual(result, currentResult) {
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
}
result = m.GlobalDirectoryTree("default", "", -1, true)
currentResult = map[string]interface{}{
"another": map[string]interface{}{
"directory": map[string]interface{}{
"with": map[string]interface{}{
"a": map[string]interface{}{},
},
},
},
"other": map[string]interface{}{
"rand": map[string]interface{}{},
"random": map[string]interface{}{
"dir": map[string]interface{}{},
"dirx": map[string]interface{}{},
},
"randomx": map[string]interface{}{},
},
"some": map[string]interface{}{
"directory": map[string]interface{}{
"with": map[string]interface{}{
"a": map[string]interface{}{},
},
},
},
}
if !reflect.DeepEqual(result, currentResult) {
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
}
result = m.GlobalDirectoryTree("default", "", 1, true)
currentResult = map[string]interface{}{
"another": map[string]interface{}{
"directory": map[string]interface{}{},
},
"other": map[string]interface{}{
"rand": map[string]interface{}{},
"random": map[string]interface{}{},
"randomx": map[string]interface{}{},
},
"some": map[string]interface{}{
"directory": map[string]interface{}{},
},
}
if !reflect.DeepEqual(result, currentResult) {
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
}
result = m.GlobalDirectoryTree("default", "another", 0, false)
currentResult = map[string]interface{}{
"directory": map[string]interface{}{},
"file": filedata,
}
if !reflect.DeepEqual(result, currentResult) {
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
}
result = m.GlobalDirectoryTree("default", "some/directory", 0, false)
currentResult = map[string]interface{}{
"with": map[string]interface{}{},
}
if !reflect.DeepEqual(result, currentResult) {
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
}
result = m.GlobalDirectoryTree("default", "some/directory", 1, false)
currentResult = map[string]interface{}{
"with": map[string]interface{}{
"a": map[string]interface{}{},
},
}
if !reflect.DeepEqual(result, currentResult) {
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
}
result = m.GlobalDirectoryTree("default", "some/directory", 2, false)
currentResult = map[string]interface{}{
"with": map[string]interface{}{
"a": map[string]interface{}{
"file": filedata,
},
},
}
if !reflect.DeepEqual(result, currentResult) {
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
}
result = m.GlobalDirectoryTree("default", "another", -1, true)
currentResult = map[string]interface{}{
"directory": map[string]interface{}{
"with": map[string]interface{}{
"a": map[string]interface{}{},
},
},
}
if !reflect.DeepEqual(result, currentResult) {
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
}
// No prefix matching!
result = m.GlobalDirectoryTree("default", "som", -1, false)
currentResult = map[string]interface{}{}
if !reflect.DeepEqual(result, currentResult) {
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
}
}
func TestGlobalDirectorySelfFixing(t *testing.T) {
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
m := NewModel(defaultConfig, "device", "syncthing", "dev", db)
m.AddFolder(defaultFolderConfig)
b := func(isfile bool, path ...string) protocol.FileInfo {
var flags uint32 = protocol.FlagDirectory
blocks := []protocol.BlockInfo{}
if isfile {
flags = 0
blocks = []protocol.BlockInfo{{Offset: 0x0, Size: 0xa, Hash: []uint8{0x2f, 0x72, 0xcc, 0x11, 0xa6, 0xfc, 0xd0, 0x27, 0x1e, 0xce, 0xf8, 0xc6, 0x10, 0x56, 0xee, 0x1e, 0xb1, 0x24, 0x3b, 0xe3, 0x80, 0x5b, 0xf9, 0xa9, 0xdf, 0x98, 0xf9, 0x2f, 0x76, 0x36, 0xb0, 0x5c}}}
}
return protocol.FileInfo{
Name: filepath.Join(path...),
Flags: flags,
Modified: 0x666,
Blocks: blocks,
}
}
filedata := []int64{0x666, 0xa}
testdata := []protocol.FileInfo{
b(true, "another", "directory", "afile"),
b(true, "another", "directory", "with", "a", "file"),
b(true, "another", "directory", "with", "file"),
b(false, "other", "random", "dirx"),
b(false, "other", "randomx"),
b(false, "some", "directory", "with", "x"),
b(true, "some", "directory", "with", "a", "file"),
b(false, "this", "is", "a", "deep", "invalid", "directory"),
b(true, "xthis", "is", "a", "deep", "invalid", "file"),
}
expectedResult := map[string]interface{}{
"another": map[string]interface{}{
"directory": map[string]interface{}{
"afile": filedata,
"with": map[string]interface{}{
"a": map[string]interface{}{
"file": filedata,
},
"file": filedata,
},
},
},
"other": map[string]interface{}{
"random": map[string]interface{}{
"dirx": map[string]interface{}{},
},
"randomx": map[string]interface{}{},
},
"some": map[string]interface{}{
"directory": map[string]interface{}{
"with": map[string]interface{}{
"a": map[string]interface{}{
"file": filedata,
},
"x": map[string]interface{}{},
},
},
},
"this": map[string]interface{}{
"is": map[string]interface{}{
"a": map[string]interface{}{
"deep": map[string]interface{}{
"invalid": map[string]interface{}{
"directory": map[string]interface{}{},
},
},
},
},
},
"xthis": map[string]interface{}{
"is": map[string]interface{}{
"a": map[string]interface{}{
"deep": map[string]interface{}{
"invalid": map[string]interface{}{
"file": filedata,
},
},
},
},
},
}
mm := func(data interface{}) string {
bytes, err := json.Marshal(data)
if err != nil {
panic(err)
}
return string(bytes)
}
m.Index(device1, "default", testdata)
result := m.GlobalDirectoryTree("default", "", -1, false)
if !reflect.DeepEqual(result, expectedResult) {
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(expectedResult))
}
result = m.GlobalDirectoryTree("default", "xthis/is/a/deep", -1, false)
currentResult := map[string]interface{}{
"invalid": map[string]interface{}{
"file": filedata,
},
}
if !reflect.DeepEqual(result, currentResult) {
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
}
result = m.GlobalDirectoryTree("default", "xthis/is/a/deep", -1, true)
currentResult = map[string]interface{}{
"invalid": map[string]interface{}{},
}
if !reflect.DeepEqual(result, currentResult) {
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
}
// !!! This is actually BAD, because we don't have enough level allowance
// to accept this file, hence the tree is left unbuilt !!!
result = m.GlobalDirectoryTree("default", "xthis", 1, false)
currentResult = map[string]interface{}{}
if !reflect.DeepEqual(result, currentResult) {
t.Errorf("Does not match:\n%s\n%s", mm(result), mm(currentResult))
}
}
func genDeepFiles(n, d int) []protocol.FileInfo {
rand.Seed(int64(n))
files := make([]protocol.FileInfo, n)
t := time.Now().Unix()
for i := 0; i < n; i++ {
path := ""
for i := 0; i <= d; i++ {
path = filepath.Join(path, strconv.Itoa(rand.Int()))
}
sofar := ""
for _, path := range filepath.SplitList(path) {
sofar = filepath.Join(sofar, path)
files[i] = protocol.FileInfo{
Name: sofar,
}
i++
}
files[i].Modified = t
files[i].Blocks = []protocol.BlockInfo{{0, 100, []byte("some hash bytes")}}
}
return files
}
func BenchmarkTree_10000_50(b *testing.B) {
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
m := NewModel(defaultConfig, "device", "syncthing", "dev", db)
m.AddFolder(defaultFolderConfig)
m.ScanFolder("default")
files := genDeepFiles(10000, 50)
m.Index(device1, "default", files)
b.ResetTimer()
for i := 0; i < b.N; i++ {
m.GlobalDirectoryTree("default", "", -1, false)
}
}
func BenchmarkTree_10000_10(b *testing.B) {
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
m := NewModel(defaultConfig, "device", "syncthing", "dev", db)
m.AddFolder(defaultFolderConfig)
m.ScanFolder("default")
files := genDeepFiles(10000, 10)
m.Index(device1, "default", files)
b.ResetTimer()
for i := 0; i < b.N; i++ {
m.GlobalDirectoryTree("default", "", -1, false)
}
}
func BenchmarkTree_00100_50(b *testing.B) {
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
m := NewModel(defaultConfig, "device", "syncthing", "dev", db)
m.AddFolder(defaultFolderConfig)
m.ScanFolder("default")
files := genDeepFiles(100, 50)
m.Index(device1, "default", files)
b.ResetTimer()
for i := 0; i < b.N; i++ {
m.GlobalDirectoryTree("default", "", -1, false)
}
}
func BenchmarkTree_00100_10(b *testing.B) {
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
m := NewModel(defaultConfig, "device", "syncthing", "dev", db)
m.AddFolder(defaultFolderConfig)
m.ScanFolder("default")
files := genDeepFiles(100, 10)
m.Index(device1, "default", files)
b.ResetTimer()
for i := 0; i < b.N; i++ {
m.GlobalDirectoryTree("default", "", -1, false)
}
}

View File

@@ -614,22 +614,31 @@ func (p *Puller) renameFile(source, target protocol.FileInfo) {
err = osutil.TryRename(from, to)
}
if err != nil {
l.Infof("Puller (folder %q, file %q): rename from %q: %v", p.folder, target.Name, source.Name, err)
return
}
if err == nil {
// The file was renamed, so we have handled both the necessary delete
// of the source and the creation of the target. Fix-up the metadata,
// and update the local index of the target file.
// Fix-up the metadata, and update the local index of the target file
err = p.shortcutFile(target)
if err != nil {
l.Infof("Puller (folder %q, file %q): rename from %q metadata: %v", p.folder, target.Name, source.Name, err)
return
}
p.model.updateLocal(p.folder, source)
// Source file already has the delete bit set.
// Because we got rid of the file (by renaming it), we just need to update
// the index, and we're done with it.
p.model.updateLocal(p.folder, source)
err = p.shortcutFile(target)
if err != nil {
l.Infof("Puller (folder %q, file %q): rename from %q metadata: %v", p.folder, target.Name, source.Name, err)
return
}
} else {
// We failed the rename so we have a source file that we still need to
// get rid of. Attempt to delete it instead so that we make *some*
// progress. The target is unhandled.
err = osutil.InWritableDir(os.Remove, from)
if err != nil {
l.Infof("Puller (folder %q, file %q): delete %q after failed rename: %v", p.folder, target.Name, source.Name, err)
return
}
p.model.updateLocal(p.folder, source)
}
}
// handleFile queues the copies and pulls as necessary for a single new or
@@ -709,13 +718,14 @@ func (p *Puller) handleFile(file protocol.FileInfo, copyChan chan<- copyBlocksSt
}
s := sharedPullerState{
file: file,
folder: p.folder,
tempName: tempName,
realName: realName,
copyTotal: len(blocks),
copyNeeded: len(blocks),
reused: reused,
file: file,
folder: p.folder,
tempName: tempName,
realName: realName,
copyTotal: len(blocks),
copyNeeded: len(blocks),
reused: reused,
ignorePerms: p.ignorePerms,
}
if debug {

View File

@@ -22,7 +22,6 @@ import (
"time"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/internal/config"
"github.com/syncthing/syncthing/internal/scanner"
"github.com/syndtr/goleveldb/leveldb"
@@ -77,8 +76,8 @@ func TestHandleFile(t *testing.T) {
requiredFile.Blocks = blocks[1:]
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
m := NewModel(config.Wrap("/tmp/test", config.Configuration{}), "device", "syncthing", "dev", db)
m.AddFolder(config.FolderConfiguration{ID: "default", Path: "testdata"})
m := NewModel(defaultConfig, "device", "syncthing", "dev", db)
m.AddFolder(defaultFolderConfig)
// Update index
m.updateLocal("default", existingFile)
@@ -131,8 +130,8 @@ func TestHandleFileWithTemp(t *testing.T) {
requiredFile.Blocks = blocks[1:]
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
m := NewModel(config.Wrap("/tmp/test", config.Configuration{}), "device", "syncthing", "dev", db)
m.AddFolder(config.FolderConfiguration{ID: "default", Path: "testdata"})
m := NewModel(defaultConfig, "device", "syncthing", "dev", db)
m.AddFolder(defaultFolderConfig)
// Update index
m.updateLocal("default", existingFile)
@@ -190,12 +189,9 @@ func TestCopierFinder(t *testing.T) {
requiredFile.Blocks = blocks[1:]
requiredFile.Name = "file2"
fcfg := config.FolderConfiguration{ID: "default", Path: "testdata"}
cfg := config.Configuration{Folders: []config.FolderConfiguration{fcfg}}
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
m := NewModel(config.Wrap("/tmp/test", cfg), "device", "syncthing", "dev", db)
m.AddFolder(fcfg)
m := NewModel(defaultConfig, "device", "syncthing", "dev", db)
m.AddFolder(defaultFolderConfig)
// Update index
m.updateLocal("default", existingFile)
@@ -268,12 +264,9 @@ func TestCopierCleanup(t *testing.T) {
return true
}
fcfg := config.FolderConfiguration{ID: "default", Path: "testdata"}
cfg := config.Configuration{Folders: []config.FolderConfiguration{fcfg}}
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
m := NewModel(config.Wrap("/tmp/test", cfg), "device", "syncthing", "dev", db)
m.AddFolder(fcfg)
m := NewModel(defaultConfig, "device", "syncthing", "dev", db)
m.AddFolder(defaultFolderConfig)
// Create a file
file := protocol.FileInfo{
@@ -320,12 +313,9 @@ func TestCopierCleanup(t *testing.T) {
// Make sure that the copier routine hashes the content when asked, and pulls
// if it fails to find the block.
func TestLastResortPulling(t *testing.T) {
fcfg := config.FolderConfiguration{ID: "default", Path: "testdata"}
cfg := config.Configuration{Folders: []config.FolderConfiguration{fcfg}}
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
m := NewModel(config.Wrap("/tmp/test", cfg), "device", "syncthing", "dev", db)
m.AddFolder(fcfg)
m := NewModel(defaultConfig, "device", "syncthing", "dev", db)
m.AddFolder(defaultFolderConfig)
// Add a file to index (with the incorrect block representation, as content
// doesn't actually match the block list)
@@ -396,11 +386,11 @@ func TestDeregisterOnFailInCopy(t *testing.T) {
defer os.Remove("testdata/" + defTempNamer.TempName("filex"))
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
cw := config.Wrap("/tmp/test", config.Configuration{})
m := NewModel(cw, "device", "syncthing", "dev", db)
m.AddFolder(config.FolderConfiguration{ID: "default", Path: "testdata"})
emitter := NewProgressEmitter(cw)
m := NewModel(defaultConfig, "device", "syncthing", "dev", db)
m.AddFolder(defaultFolderConfig)
emitter := NewProgressEmitter(defaultConfig)
go emitter.Serve()
p := Puller{
@@ -484,11 +474,10 @@ func TestDeregisterOnFailInPull(t *testing.T) {
defer os.Remove("testdata/" + defTempNamer.TempName("filex"))
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
cw := config.Wrap("/tmp/test", config.Configuration{})
m := NewModel(cw, "device", "syncthing", "dev", db)
m.AddFolder(config.FolderConfiguration{ID: "default", Path: "testdata"})
m := NewModel(defaultConfig, "device", "syncthing", "dev", db)
m.AddFolder(defaultFolderConfig)
emitter := NewProgressEmitter(cw)
emitter := NewProgressEmitter(defaultConfig)
go emitter.Serve()
p := Puller{

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