Compare commits

..
Author SHA1 Message Date
Simon FreiandJakob Borg bea6ecaf35 lib/scanner, lib/model: Actually assign version when un-ignoring (fixes #4841) (#4842)
This fixes a mistake introduced in #4750 and #4776 and is relevant to v0.14.46-rc1
2018-03-27 16:25:36 -04:00
Jakob BorgandGitHub 470ef87dd5 vendor: Don't panic in FS watcher on old FreeBSD (fixes #4806) (#4809)
This adds a recover step to the notify package to avoid the panic. We
should get something like this upstreamed.
2018-03-13 13:31:26 +01:00
Simon FreiandJakob Borg b31bad1c4d lib/model: Remove unused shouldIgnore function (#4805) 2018-03-12 20:16:40 +01:00
Simon FreiandJakob Borg 8b4346c3ec lib/scanner, lib/fs: Don't create file infos with abs paths (fixes #4799) (#4800) 2018-03-12 13:18:59 +01:00
Simon FreiandJakob Borg 2bdb37d412 cmd/syncthing: More information in help about -logfile option (#4796) 2018-03-12 13:17:12 +01:00
Jakob BorgandGitHub 1471c15b29 cmd/syncthing, lib/db: Be nicer about dropping deltas on upgrade (#4798)
When dropping delta index IDs due to upgrade, only drop our local one.
Previously, when dropping all of them, we would trigger a full send in
both directions on first connect after upgrade. Then the other side
would upgrade, doing the same thing. Net effect is full index data gets
sent twice in both directions.

With this change we just drop our local ID, meaning we will send our
full index on first connect after upgrade. When the other side upgrades,
they will do the same. This is a bit less cruel.
2018-03-10 11:42:01 +01:00
Jakob Borg 4b1782cf6d gui, man: Update docs & translations 2018-03-07 07:45:16 +01:00
Jakob Borg 71fab4d250 cmd/stdiscosrv: Record time of failed lookup
So that we can eventually garbage collect keys that noone is asking
about any more.
2018-03-06 16:15:29 +01:00
Jakob Borg 22ebc80329 cmd/stdiscosrv: Expose process metrics
Like this:

    $ curl -s http://localhost:9098/metrics | egrep '^syncthing_discovery_process'
    syncthing_discovery_process_cpu_seconds_total 12.92
    syncthing_discovery_process_max_fds 10240
    syncthing_discovery_process_open_fds 51
    syncthing_discovery_process_resident_memory_bytes 1.3674496e+08
    syncthing_discovery_process_start_time_seconds 1.52034731837e+09
    syncthing_discovery_process_virtual_memory_bytes 1.40324864e+08
2018-03-06 15:43:36 +01:00
Jakob Borg 74b820f287 test: Update conflict tests 2018-03-04 14:52:09 +01:00
Simon FreiandJakob Borg 3949b750f5 gui: In remote need use index and auto-expand if only one folder (fixes #4759) (#4781) 2018-03-01 16:21:35 +01:00
Jakob Borg a26778fa9b gui, man: Update docs & translations 2018-02-28 07:45:16 +01:00
Jakob BorgandAudrius Butkevicius d4b7be009c cmd/syncthing: Reset delta indexes on upgrade 2018-02-26 22:22:19 +00:00
Audrius ButkeviciusandJakob Borg 2751be57dc lib/connections: Fix relay connections when two devices use the same relay (fixes #4778) (#4779) 2018-02-25 16:12:46 +01:00
Audrius ButkeviciusandSimon Frei 8df90bb475 lib/scanner: Track modified by in symlinks (#4777) 2018-02-25 14:51:37 +01:00
Simon FreiandJakob Borg 36251b86f7 lib/model: Mark deleted file as conflicting when un-ignoring (#4776)
This completes #4750 as a followup to #4765.
2018-02-25 13:03:55 +01:00
Jakob BorgandGitHub 42cc64e2ed lib/config, lib/model: Auto adjust pullers based on desired amount of pending data (#4748)
This makes the number of pullers vary with the desired amount of outstanding requests instead of being a fixed number.
2018-02-25 10:14:02 +01:00
Simon FreiandJakob Borg 158859a1e2 lib: Handle metadata changes for send-only folders (fixes #4616, fixes #4627) (#4750)
Unignored files are marked as conflicting while scanning, which is then resolved
in the subsequent pull. Automatically reconciles needed items on send-only
folders, if they do not actually differ except for internal metadata.
2018-02-25 09:39:00 +01:00
Simon FreiandJakob Borg 5822222c74 lib/model: More precise deletion detection (fixes #2571, fixes #4573) (#4765) 2018-02-25 09:27:54 +01:00
Jakob BorgandAudrius Butkevicius e99be02055 lib/model: Don't panic if block size in index is larger than protocol block size
This doesn't happen today, but it might in the future if the block size
were increased or made variable and we were talking to a client from the
future.
2018-02-24 07:34:23 -05:00
Matic PotočnikandJakob Borg 1901a5a9f4 all: Fix typos (#4772)
Skip-check: authors
2018-02-24 08:51:29 +01:00
Jakob BorgandAudrius Butkevicius a27032f09e cmd/strelaysrv: Don't patch the default HTTP client (fixes #4745) 2018-02-21 09:56:04 -05:00
Jakob BorgandGitHub 5e041dca9f cmd/strelaypoolsrv: Return better error codes and messages (#4770)
The current 500 "test failed" looks and sounds like a problem in the
relay pool server, while it actually indicates a problem on the
announcing side. Instead use 400 "connection test failed" to indicate
that the request was bad and what was the test.
2018-02-21 12:53:49 +01:00
Jakob Borg c9ec6159e8 lib/fs, vendor: s/zillode/Zillode/ 2018-02-21 08:27:33 +01:00
Jakob Borg 5b17aae1b2 cmd/syncthing: Fix help text for STRECHECKDBEVERY (fixes #4764) 2018-02-21 08:26:57 +01:00
Jakob Borg 5931231a32 gui, man: Update docs & translations 2018-02-21 07:45:16 +01:00
Simon FreiandJakob Borg 6802505dda lib/fs: Fix MkdirAll failure due to \\?\ (fixes #4762) (#4763) 2018-02-16 15:19:20 +01:00
Jakob BorgandGitHub 7a92f6c6b1 lib/db: Don't panic on negative counts (#4761)
* lib/db: Don't panic on negative counts (fixes #4659)

So, negative counts should never happen and hence the original idea to
panic. However, this sucks as the panic will happen in a folder runner,
be automatically swallowed by suture, and the runner gets restarted but
now we are in a bad state. (Related: #4758)

At the time of writing the global list is somewhat in flux (we've
changed how ignored files are handled, invalid bits, etc.) and I think
that can cause unusual conditions here. Hence just fixing up the numbers
instead until the next full recount.
2018-02-14 11:25:34 +01:00
Simon FreiandJakob Borg 68c1b2dd47 all: Revert simultaneously walk fs and db on scan (fixes #4756) (#4757)
This reverts commit 6d3f9d5154.
2018-02-14 08:59:46 +01:00
Jakob Borg b57c9b6af5 gui, man: Update docs & translations 2018-02-14 07:45:17 +01:00
137 changed files with 2026 additions and 1298 deletions
+1
View File
@@ -195,6 +195,7 @@ func (s *apiSrv) handleGET(ctx context.Context, w http.ResponseWriter, req *http
if misses%notFoundMissesWriteInterval == 0 {
rec.Misses = misses
rec.Missed = time.Now().UnixNano()
rec.Addresses = nil
// rec.Seen retained from get
s.db.put(key, rec)
+43 -14
View File
@@ -36,6 +36,7 @@ type DatabaseRecord struct {
Addresses []DatabaseAddress `protobuf:"bytes,1,rep,name=addresses" json:"addresses"`
Misses int32 `protobuf:"varint,2,opt,name=misses,proto3" json:"misses,omitempty"`
Seen int64 `protobuf:"varint,3,opt,name=seen,proto3" json:"seen,omitempty"`
Missed int64 `protobuf:"varint,4,opt,name=missed,proto3" json:"missed,omitempty"`
}
func (m *DatabaseRecord) Reset() { *m = DatabaseRecord{} }
@@ -106,6 +107,11 @@ func (m *DatabaseRecord) MarshalTo(dAtA []byte) (int, error) {
i++
i = encodeVarintDatabase(dAtA, i, uint64(m.Seen))
}
if m.Missed != 0 {
dAtA[i] = 0x20
i++
i = encodeVarintDatabase(dAtA, i, uint64(m.Missed))
}
return i, nil
}
@@ -221,6 +227,9 @@ func (m *DatabaseRecord) Size() (n int) {
if m.Seen != 0 {
n += 1 + sovDatabase(uint64(m.Seen))
}
if m.Missed != 0 {
n += 1 + sovDatabase(uint64(m.Missed))
}
return n
}
@@ -367,6 +376,25 @@ func (m *DatabaseRecord) Unmarshal(dAtA []byte) error {
break
}
}
case 4:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Missed", wireType)
}
m.Missed = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowDatabase
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.Missed |= (int64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
default:
iNdEx = preIndex
skippy, err := skipDatabase(dAtA[iNdEx:])
@@ -723,21 +751,22 @@ var (
func init() { proto.RegisterFile("database.proto", fileDescriptorDatabase) }
var fileDescriptorDatabase = []byte{
// 254 bytes of a gzipped FileDescriptorProto
// 264 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x4b, 0x49, 0x2c, 0x49,
0x4c, 0x4a, 0x2c, 0x4e, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0xc9, 0x4d, 0xcc, 0xcc,
0x93, 0xd2, 0x4d, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x4f, 0xcf, 0x4f,
0xcf, 0xd7, 0x07, 0x4b, 0x26, 0x95, 0xa6, 0x81, 0x79, 0x60, 0x0e, 0x98, 0x05, 0xd1, 0xa4, 0x54,
0xce, 0xc5, 0xe7, 0x02, 0x35, 0x26, 0x28, 0x35, 0x39, 0xbf, 0x28, 0x45, 0xc8, 0x92, 0x8b, 0x33,
0x31, 0x25, 0xa5, 0x28, 0xb5, 0xb8, 0x38, 0xb5, 0x58, 0x82, 0x51, 0x81, 0x59, 0x83, 0xdb, 0x48,
0x54, 0x0f, 0x64, 0xb4, 0x1e, 0x4c, 0xa1, 0x23, 0x44, 0xda, 0x89, 0xe5, 0xc4, 0x3d, 0x79, 0x86,
0x20, 0x84, 0x6a, 0x21, 0x31, 0x2e, 0xb6, 0xdc, 0x4c, 0xb0, 0x3e, 0x26, 0x05, 0x46, 0x0d, 0xd6,
0x20, 0x28, 0x4f, 0x48, 0x88, 0x8b, 0xa5, 0x38, 0x35, 0x35, 0x4f, 0x82, 0x59, 0x81, 0x51, 0x83,
0x39, 0x08, 0xcc, 0x56, 0x2a, 0xe1, 0x12, 0x0c, 0x4a, 0x2d, 0xc8, 0xc9, 0x4c, 0x4e, 0x2c, 0xc9,
0xcc, 0xcf, 0x83, 0xda, 0x2d, 0xc0, 0xc5, 0x9c, 0x9d, 0x5a, 0x29, 0xc1, 0xa8, 0xc0, 0xa8, 0xc1,
0x19, 0x04, 0x62, 0xa2, 0xba, 0x86, 0x89, 0x24, 0xd7, 0x60, 0xb3, 0xd5, 0x95, 0x8b, 0x1f, 0x4d,
0x9f, 0x90, 0x04, 0x17, 0x3b, 0x54, 0x0f, 0xd4, 0x5e, 0x18, 0x17, 0x24, 0x93, 0x5a, 0x51, 0x90,
0x59, 0x04, 0xf5, 0x0f, 0x73, 0x10, 0x8c, 0xeb, 0x24, 0x70, 0xe2, 0xa1, 0x1c, 0xc3, 0x89, 0x47,
0x72, 0x8c, 0x17, 0x1e, 0xc9, 0x31, 0x3e, 0x78, 0x24, 0xc7, 0x98, 0xc4, 0x06, 0x0e, 0x4e, 0x63,
0x40, 0x00, 0x00, 0x00, 0xff, 0xff, 0x9e, 0x45, 0x60, 0x7e, 0x95, 0x01, 0x00, 0x00,
0xcf, 0xd7, 0x07, 0x4b, 0x26, 0x95, 0xa6, 0x81, 0x79, 0x60, 0x0e, 0x98, 0x05, 0xd1, 0xa4, 0xd4,
0xcf, 0xc8, 0xc5, 0xe7, 0x02, 0x35, 0x27, 0x28, 0x35, 0x39, 0xbf, 0x28, 0x45, 0xc8, 0x92, 0x8b,
0x33, 0x31, 0x25, 0xa5, 0x28, 0xb5, 0xb8, 0x38, 0xb5, 0x58, 0x82, 0x51, 0x81, 0x59, 0x83, 0xdb,
0x48, 0x54, 0x0f, 0x64, 0xb6, 0x1e, 0x4c, 0xa1, 0x23, 0x44, 0xda, 0x89, 0xe5, 0xc4, 0x3d, 0x79,
0x86, 0x20, 0x84, 0x6a, 0x21, 0x31, 0x2e, 0xb6, 0xdc, 0x4c, 0xb0, 0x3e, 0x26, 0x05, 0x46, 0x0d,
0xd6, 0x20, 0x28, 0x4f, 0x48, 0x88, 0x8b, 0xa5, 0x38, 0x35, 0x35, 0x4f, 0x82, 0x59, 0x81, 0x51,
0x83, 0x39, 0x08, 0xcc, 0x86, 0xab, 0x4d, 0x91, 0x60, 0x01, 0x8b, 0x42, 0x79, 0x4a, 0x25, 0x5c,
0x82, 0x41, 0xa9, 0x05, 0x39, 0x99, 0xc9, 0x89, 0x25, 0x99, 0xf9, 0x79, 0x50, 0x37, 0x09, 0x70,
0x31, 0x67, 0xa7, 0x56, 0x4a, 0x30, 0x2a, 0x30, 0x6a, 0x70, 0x06, 0x81, 0x98, 0xa8, 0xae, 0x64,
0x22, 0xc9, 0x95, 0x58, 0x5c, 0xa3, 0xe4, 0xca, 0xc5, 0x8f, 0xa6, 0x4f, 0x48, 0x82, 0x8b, 0x1d,
0xaa, 0x07, 0x6a, 0x2f, 0x8c, 0x0b, 0x92, 0x49, 0xad, 0x28, 0xc8, 0x2c, 0x82, 0xfa, 0x93, 0x39,
0x08, 0xc6, 0x75, 0x12, 0x38, 0xf1, 0x50, 0x8e, 0xe1, 0xc4, 0x23, 0x39, 0xc6, 0x0b, 0x8f, 0xe4,
0x18, 0x1f, 0x3c, 0x92, 0x63, 0x4c, 0x62, 0x03, 0x87, 0xb3, 0x31, 0x20, 0x00, 0x00, 0xff, 0xff,
0x6a, 0x22, 0xa2, 0x85, 0xae, 0x01, 0x00, 0x00,
}
+4 -1
View File
@@ -14,10 +14,13 @@ option (gogoproto.goproto_getters_all) = false;
message DatabaseRecord {
repeated DatabaseAddress addresses = 1 [(gogoproto.nullable) = false];
int32 misses = 2; // Number of lookups without hits
int32 misses = 2; // Number of lookups* without hits
int64 seen = 3; // Unix nanos, last device announce
int64 missed = 4; // Unix nanos, last* failed lookup
}
// *) Not every lookup results in a write, so may not be completely accurate
message ReplicationRecord {
string key = 1;
repeated DatabaseAddress addresses = 2 [(gogoproto.nullable) = false];
+1 -1
View File
@@ -154,7 +154,7 @@ func TestDatabaseGetSet(t *testing.T) {
}
if len(rec.Addresses) != 1 {
t.Log(rec.Addresses)
t.Fatal("should have one addres")
t.Fatal("should have one address")
}
if rec.Misses != 0 {
t.Log(rec.Misses)
+1 -1
View File
@@ -149,7 +149,7 @@ func (m replicationMultiplexer) send(key string, ps []DatabaseAddress, seen int6
}
}
// replicationListener acceptes incoming connections and reads replication
// replicationListener accepts incoming connections and reads replication
// items from them. Incoming items are applied to the KV store.
type replicationListener struct {
addr string
+4
View File
@@ -7,6 +7,8 @@
package main
import (
"os"
"github.com/prometheus/client_golang/prometheus"
)
@@ -105,4 +107,6 @@ func init() {
replicationSendsTotal, replicationRecvsTotal,
databaseKeys, databaseStatisticsSeconds,
databaseOperations, databaseOperationSeconds)
prometheus.MustRegister(prometheus.NewProcessCollector(os.Getpid(), "syncthing_discovery"))
}
+8 -4
View File
@@ -90,6 +90,10 @@ var (
evictionTimers = make(map[string]*time.Timer)
)
const (
httpStatusEnhanceYourCalm = 429
)
func main() {
flag.StringVar(&listen, "listen", listen, "Listen address")
flag.StringVar(&dir, "keys", dir, "Directory where http-cert.pem and http-key.pem is stored for TLS listening")
@@ -344,7 +348,7 @@ func handlePostRequest(w http.ResponseWriter, r *http.Request) {
if debug {
log.Println("Asked to add a relay", newRelay, "which exists in permanent list")
}
http.Error(w, "Invalid request", 500)
http.Error(w, "Invalid request", http.StatusBadRequest)
return
}
}
@@ -355,7 +359,7 @@ func handlePostRequest(w http.ResponseWriter, r *http.Request) {
case requests <- request{newRelay, uri, reschan}:
result := <-reschan
if result.err != nil {
http.Error(w, result.err.Error(), 500)
http.Error(w, result.err.Error(), http.StatusBadRequest)
return
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
@@ -367,7 +371,7 @@ func handlePostRequest(w http.ResponseWriter, r *http.Request) {
if debug {
log.Println("Dropping request")
}
w.WriteHeader(429)
w.WriteHeader(httpStatusEnhanceYourCalm)
}
}
@@ -380,7 +384,7 @@ func requestProcessor() {
if debug {
log.Println("Test for relay", request.relay, "failed")
}
request.result <- result{fmt.Errorf("test failed"), 0}
request.result <- result{fmt.Errorf("connection test failed"), 0}
continue
}
+12 -6
View File
@@ -86,6 +86,12 @@ var (
pprofEnabled bool
)
// httpClient is the HTTP client we use for outbound requests. It has a
// timeout and may get further options set during initialization.
var httpClient = &http.Client{
Timeout: 30 * time.Second,
}
func main() {
log.SetFlags(log.Lshortfile | log.LstdFlags)
@@ -129,14 +135,14 @@ func main() {
if err != nil {
log.Fatal(err)
}
if laddr.IP != nil && !laddr.IP.IsUnspecified() {
// We bind to a specific address. Our outgoing HTTP requests should
// also come from that address.
laddr.Port = 0
transport, ok := http.DefaultTransport.(*http.Transport)
if ok {
transport.Dial = (&net.Dialer{
Timeout: 30 * time.Second,
LocalAddr: laddr,
}).Dial
boundDialer := &net.Dialer{LocalAddr: laddr}
httpClient.Transport = &http.Transport{
DialContext: boundDialer.DialContext,
}
}
+1 -2
View File
@@ -7,7 +7,6 @@ import (
"encoding/json"
"io/ioutil"
"log"
"net/http"
"net/url"
"time"
)
@@ -27,7 +26,7 @@ func poolHandler(pool string, uri *url.URL, mapping mapping) {
uriCopy.String(),
})
resp, err := http.Post(pool, "application/json", &b)
resp, err := httpClient.Post(pool, "application/json", &b)
if err != nil {
log.Println("Error joining pool", pool, err)
} else if resp.StatusCode == 500 {
+28 -23
View File
@@ -197,7 +197,7 @@ are mostly useful for developers. Use with care.
"minio" for the github.com/minio/sha256-simd implementation,
and blank (the default) for auto detection.
STDBCHECKEVERY Set to a time interval to override the default database
STRECHECKDBEVERY Set to a time interval to override the default database
check interval of 30 days (720h). The interval understands
"h", "m" and "s" abbreviations for hours minutes and seconds.
Valid values are like "720h", "30s", etc.
@@ -303,7 +303,7 @@ func parseCommandLineOptions() RuntimeOptions {
flag.BoolVar(&options.verbose, "verbose", false, "Print verbose log output")
flag.BoolVar(&options.paused, "paused", false, "Start with all devices and folders paused")
flag.BoolVar(&options.unpaused, "unpaused", false, "Start with all devices and folders unpaused")
flag.StringVar(&options.logFile, "logfile", options.logFile, "Log file name (use \"-\" for stdout)")
flag.StringVar(&options.logFile, "logfile", options.logFile, "Log file name (still always logs to stdout). Cannot be used together with -no-restart/STNORESTART environment variable.")
flag.StringVar(&options.auditFile, "auditfile", options.auditFile, "Specify audit file (use \"-\" for stdout, \"--\" for stderr)")
if runtime.GOOS == "windows" {
// Allow user to hide the console window
@@ -720,14 +720,14 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
dbFile := locations[locDatabase]
ldb, err := db.Open(dbFile)
if err != nil {
l.Fatalln("Cannot open database:", err, "- Is another copy of Syncthing already running?")
}
if runtimeOptions.resetDeltaIdxs {
l.Infoln("Reinitializing delta index IDs")
ldb.DropDeltaIndexIDs()
ldb.DropLocalDeltaIndexIDs()
ldb.DropRemoteDeltaIndexIDs()
}
protectedFiles := []string{
@@ -746,28 +746,33 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
}
}
if cfg.RawCopy().OriginalVersion == 15 {
// The config version 15->16 migration is about handling ignores and
// delta indexes and requires that we drop existing indexes that
// have been incorrectly ignore filtered.
ldb.DropDeltaIndexIDs()
}
if cfg.RawCopy().OriginalVersion < 19 {
// Converts old symlink types to new in the entire database.
ldb.ConvertSymlinkTypes()
}
if cfg.RawCopy().OriginalVersion < 26 {
// Adds invalid (ignored) files to global list of files
changed := 0
for folderID, folderCfg := range folders {
changed += ldb.AddInvalidToGlobal([]byte(folderID), protocol.LocalDeviceID[:])
for _, deviceCfg := range folderCfg.Devices {
changed += ldb.AddInvalidToGlobal([]byte(folderID), deviceCfg.DeviceID[:])
}
// Grab the previously running version string from the database.
miscDB := db.NewNamespacedKV(ldb, string(db.KeyTypeMiscData))
prevVersion, _ := miscDB.String("prevVersion")
// Strip away prerelease/beta stuff and just compare the release
// numbers. 0.14.44 to 0.14.45-banana is an upgrade, 0.14.45-banana to
// 0.14.45-pineapple is not.
prevParts := strings.Split(prevVersion, "-")
curParts := strings.Split(Version, "-")
if prevParts[0] != curParts[0] {
if prevVersion != "" {
l.Infoln("Detected upgrade from", prevVersion, "to", Version)
}
l.Infof("Database update: Added %d ignored files to the global list", changed)
// Drop delta indexes in case we've changed random stuff we
// shouldn't have. We will resend our index on next connect.
ldb.DropLocalDeltaIndexIDs()
// Remember the new version.
miscDB.PutString("prevVersion", Version)
}
// Potential database transitions
ldb.UpdateSchema()
m := model.NewModel(cfg, myID, "syncthing", Version, ldb, protectedFiles)
if t := os.Getenv("STDEADLOCKTIMEOUT"); t != "" {
+12 -6
View File
@@ -22,6 +22,7 @@
"Allowed Networks": "Разрешени мрежи",
"Alphabetic": "Азбучен ред",
"An external command handles the versioning. It has to remove the file from the shared folder.": "Друга команда се занимава с версиите. Тази команда трябва да премахне файла от синхронизираната папка.",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "Външна команда се занимава с версиите. Тази команда трябва да премахне файла от синхронизираната папка. Ако пътят до това приложение използва интервали, то той трябва да бъде заграден в кавички.",
"An external command handles the versioning. It has to remove the file from the synced folder.": "Друга команда се занимава с версиите. Тази команда трябва да премахни файла от синхронизираната папка.",
"Anonymous Usage Reporting": "Анонимен доклад",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Форматът на анонимния доклад е променен. Желаете ли да преминете към новия формат?",
@@ -33,7 +34,7 @@
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Автоматичното обновяване вече предлага избор между стабилни версии и кандидат версии.",
"Automatic upgrades": "Автоматично обновяване",
"Automatically create or share folders that this device advertises at the default path.": "Автоматично създаване или споделяне на папки, които това устройство предлага в пътя по подразбиране.",
"Available debug logging facilities:": "Available debug logging facilities:",
"Available debug logging facilities:": "Дебъгинг функционалност на разположение:",
"Be careful!": "Внимание!",
"Bugs": "Бъгове",
"CPU Utilization": "Използван процесор",
@@ -65,7 +66,9 @@
"Device that last modified the item": "Устройство, което последно промени обекта",
"Devices": "Устройства",
"Disabled": "Деактивирано",
"Disabled periodic scanning": "Disabled periodic scanning",
"Disabled periodic scanning and disabled watching for changes": "Периодичните сканирвания и наблюденията за промяна са деактивирани.",
"Disabled periodic scanning and enabled watching for changes": "Периодичните сканирвания са деактивирани , а наблюденията за промяна са активирани.",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Периодичните сканирвания са деактивирани и задаването на наблюдение за промени е неуспешно, ще опита пак след 1мин:",
"Disconnected": "Не е свързано",
"Discovered": "Открит",
"Discovery": "Откриване",
@@ -83,6 +86,7 @@
"Editing {%path%}.": "Промяна на {{path}}.",
"Enable NAT traversal": "Разреши NAT traversal",
"Enable Relaying": "Разреши препращане",
"Enabled": "Активирано",
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Въведете не отрицателно число (пр. \"2.35\") и изберете единица.\nПроцентите са като част от размера на цялото дисково пространство.",
"Enter a non-privileged port number (1024 - 65535).": "Въведете непривилегирован номер на порт (1024-65535).",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Въведете адреси разделени със запетая (\"tcp://ip:port\", \"tcp://host:port\") или \"dynamic\", за да автоматично откриване на наличните адреси.",
@@ -90,7 +94,7 @@
"Error": "Грешка",
"External File Versioning": "Външно управление на версиите",
"Failed Items": "Неуспешни",
"Failed to setup, retrying": "Failed to setup, retrying",
"Failed to setup, retrying": "Неуспешно конфигуриране, правне на повторен опит",
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Неуспешна връзка към IPv6 сървъри може да се очаква ако няма IPv6 свързаност.",
"File Pull Order": "Ред на сваляне",
"File Versioning": "Версии на файловете",
@@ -185,7 +189,9 @@
"Pause": "Пауза",
"Pause All": "Пауза на висчко",
"Paused": "На пауза",
"Periodic scan every": "Periodic scan every",
"Periodic scanning at given interval and disabled watching for changes": "Периодичните сканирвания на определен интервал са активирани, а наблюденията за промени са деактивирани",
"Periodic scanning at given interval and enabled watching for changes": "Периодичните сканирвания на определен интервал и наблюденията за промени са активирани",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Периодичните сканирвания са деактивирани и задаването на наблюдение за промени е неуспешно, ще опита всяка следваща минута:",
"Please consult the release notes before performing a major upgrade.": "Моля прочети бележките по обновяването преди да започнеш.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Моля задайте потребителско име и парола за потребителския интерфейс в секцията Настройки.",
"Please wait": "Моля изчакай",
@@ -208,7 +214,7 @@
"Rescan": "Сканирай",
"Rescan All": "Обнови всички",
"Rescan Interval": "Интервал за повторно сканиране",
"Rescans": "Rescans",
"Rescans": "Повторни сканирвания",
"Restart": "Рестартирай",
"Restart Needed": "Изисква се рестартиране",
"Restarting": "Рестартиране",
@@ -217,7 +223,7 @@
"Resume": "Пусни",
"Resume All": "Пускане на всичко",
"Reused": "Повторно използван",
"Running": "Running",
"Running": "Изпълнява се",
"Save": "Запази",
"Scan Time Remaining": "Оставащо време за сканиране",
"Scanning": "Сканиране",
@@ -22,6 +22,7 @@
"Allowed Networks": "Xarxes permeses",
"Alphabetic": "Alfabètic",
"An external command handles the versioning. It has to remove the file from the shared folder.": "Un command extern maneja les versions. Té que eliminar el fitxer de la carpeta compartida.",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.",
"An external command handles the versioning. It has to remove the file from the synced folder.": "Un comando extern controla el versionat. És necessari eliminar el fitxer de la carpeta sincronitzada.",
"Anonymous Usage Reporting": "Informe d'ús anònim",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Anonymous usage report format has changed. Would you like to move to the new format?",
@@ -65,7 +66,9 @@
"Device that last modified the item": "Device that last modified the item",
"Devices": "Dispositius",
"Disabled": "Disabled",
"Disabled periodic scanning": "Disabled periodic scanning",
"Disabled periodic scanning and disabled watching for changes": "Disabled periodic scanning and disabled watching for changes",
"Disabled periodic scanning and enabled watching for changes": "Disabled periodic scanning and enabled watching for changes",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:",
"Disconnected": "Desconnectat",
"Discovered": "Descobert",
"Discovery": "Descobriment",
@@ -83,6 +86,7 @@
"Editing {%path%}.": "Editant {{path}}.",
"Enable NAT traversal": "Permetre NAT transversal",
"Enable Relaying": "Permetre Transmissions",
"Enabled": "Enabled",
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Introdueix un nombre no negatiu (per exemple, \"2.35\") i selecciona una unitat. Els percentatges són com a part del tamany total del disc.",
"Enter a non-privileged port number (1024 - 65535).": "Introdueix un nombre de port sense privilegis (1024-65535).",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Introdueix adreces separades per coma (\"tcp://ip:port\", \"tcp://host:port\") o \"dynamic\" per a realitzar el descobriment automàtic de l'adreça.",
@@ -185,7 +189,9 @@
"Pause": "Pausa",
"Pause All": "Pausa Tot",
"Paused": "Pausat",
"Periodic scan every": "Periodic scan every",
"Periodic scanning at given interval and disabled watching for changes": "Periodic scanning at given interval and disabled watching for changes",
"Periodic scanning at given interval and enabled watching for changes": "Periodic scanning at given interval and enabled watching for changes",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:",
"Please consult the release notes before performing a major upgrade.": "Per favor, consultar les notes de la versió abans de fer una actualització important.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Per favor, estableix un usuari i password per a l'Interfície Gràfica d'Usuari en el menú d'Adjustos.",
"Please wait": "Per favor, espere",
+9 -3
View File
@@ -22,6 +22,7 @@
"Allowed Networks": "Povolené sítě",
"Alphabetic": "Abecedně",
"An external command handles the versioning. It has to remove the file from the shared folder.": "Verzování obstarává externí příkaz. Musí odstranit soubor ze sdíleného adresáře.",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "Verzování obstarává externí skript. Musí odstranit soubor ze sdíleného adresáře. Pokud cesta ke skriptu obsahuje mezeru, měla by být v uvozovkách.",
"An external command handles the versioning. It has to remove the file from the synced folder.": "Verzování obstarává externí příkaz. Musí odstranit soubor ze sdíleného adresáře.",
"Anonymous Usage Reporting": "Anonymní hlášení o používání",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Formát anonymního hlášení o používání byl změněn. Chcete přejít na nový formát?",
@@ -65,7 +66,9 @@
"Device that last modified the item": "Poslední přístroj, který modifikoval položku",
"Devices": "Přístroje",
"Disabled": "Vypnuto",
"Disabled periodic scanning": "Periodické skenování zakázáno",
"Disabled periodic scanning and disabled watching for changes": "Periodické skenování i sledování změn vypnuto",
"Disabled periodic scanning and enabled watching for changes": "Periodické skenování vypnuto; sledování změn zapnuto",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Periodické skenování vypnuto; nastavení sledování změn selhalo, nový pokud každou 1m:",
"Disconnected": "Odpojen",
"Discovered": "Nalezeno",
"Discovery": "Oznamování",
@@ -83,6 +86,7 @@
"Editing {%path%}.": "Editace {{path}}.",
"Enable NAT traversal": "Povolit NAT přenos",
"Enable Relaying": "Povolit přenašeče",
"Enabled": "Zapnuto",
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Zadajte kladné číslo (např. \"2.35\") a zvolte jednotku. Percenta znamenají část celkové velikosti disku.",
"Enter a non-privileged port number (1024 - 65535).": "Zadejte číslo neprivilegovaného portu (1024-65535).",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Zadejte adresy oddělené čárkou (\"tcp://ip:port\", \"tcp://host:port\") nebo \"dynamic\" pro automatické zjišťování adres.",
@@ -90,7 +94,7 @@
"Error": "Chyba",
"External File Versioning": "Externí verzování souborů",
"Failed Items": "Selhalo",
"Failed to setup, retrying": "Nastavování selhalo, zkouším znova",
"Failed to setup, retrying": "Nastavování selhalo, zkouším znovu",
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Je v pořádku, když připojení k IPv6 serverům selže, pokud není k dispozici IPv6 konektivita.",
"File Pull Order": "Pořadí stahování souborů",
"File Versioning": "Verzování souborů",
@@ -185,7 +189,9 @@
"Pause": "Pozastavit",
"Pause All": "Pozastavit vše",
"Paused": "Pozastaveno",
"Periodic scan every": "Periodické skenování každých",
"Periodic scanning at given interval and disabled watching for changes": "Periodické skenování podle zadaného intervalu; sledování změn vypnuto",
"Periodic scanning at given interval and enabled watching for changes": "Periodické skenování podle zadaného intervalu; sledování změn zapnuto",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Periodické skenování podle zadaného intervalu; nastavení sledování změn selhalo, nový pokud každou 1m:",
"Please consult the release notes before performing a major upgrade.": "Před spuštěním důležité aktualizace si nejdříve přečtěte poznámky k vydání nové verze.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Zadejte prosím přihlašovací jméno a heslo pro GUI v dialogu nastavení.",
"Please wait": "Chvíli strpení",
+8 -2
View File
@@ -22,6 +22,7 @@
"Allowed Networks": "Tilladte netværk",
"Alphabetic": "Alfabetisk",
"An external command handles the versioning. It has to remove the file from the shared folder.": "En ekstern kommando styrer versioneringen. Den skal fjerne filen fra den delte mappe.",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.",
"An external command handles the versioning. It has to remove the file from the synced folder.": " ",
"Anonymous Usage Reporting": "Anonym brugerstatistik",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Anonymous usage report format has changed. Would you like to move to the new format?",
@@ -65,7 +66,9 @@
"Device that last modified the item": "Device that last modified the item",
"Devices": "Enheder",
"Disabled": "Disabled",
"Disabled periodic scanning": "Disabled periodic scanning",
"Disabled periodic scanning and disabled watching for changes": "Disabled periodic scanning and disabled watching for changes",
"Disabled periodic scanning and enabled watching for changes": "Disabled periodic scanning and enabled watching for changes",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:",
"Disconnected": "Ikke tilsluttet",
"Discovered": "Opdaget",
"Discovery": "Opslag",
@@ -83,6 +86,7 @@
"Editing {%path%}.": "Redigerer {{path}}.",
"Enable NAT traversal": "Aktiver NAT",
"Enable Relaying": "Aktiver Relaying",
"Enabled": "Enabled",
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.",
"Enter a non-privileged port number (1024 - 65535).": "Enter a non-privileged port number (1024 - 65535).",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Angiv kommaseparerede adresser (\"tcp://ip:port\", \"tcp://host:port\") eller \"dynamic\" for at benytte automatisk opdagelse af adressen.",
@@ -185,7 +189,9 @@
"Pause": "Pause",
"Pause All": "Sæt alt på pause",
"Paused": "Pauset",
"Periodic scan every": "Periodic scan every",
"Periodic scanning at given interval and disabled watching for changes": "Periodic scanning at given interval and disabled watching for changes",
"Periodic scanning at given interval and enabled watching for changes": "Periodic scanning at given interval and enabled watching for changes",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:",
"Please consult the release notes before performing a major upgrade.": "Tjek venligst udgivelsesnoterne før opgradering til en ny version.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Sæt vensligt en GUI bruger og kodeord i opsætningsdialogen.",
"Please wait": "Vent venligst",
+8 -2
View File
@@ -22,6 +22,7 @@
"Allowed Networks": "Erlaubte Netzwerke",
"Alphabetic": "Alphabetisch",
"An external command handles the versioning. It has to remove the file from the shared folder.": "Ein externer Befehl führt die Versionierung durch. Er muss die Datei aus dem geteilten Ordner entfernen.",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.",
"An external command handles the versioning. It has to remove the file from the synced folder.": "Ein externer Programmaufruf handhabt die Versionierung. Es muss die Datei aus dem zu synchronisierendem Ordner entfernen.",
"Anonymous Usage Reporting": "Anonymer Nutzungsbericht",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Das Format des anonymen Nutzungsberichts hat sich geändert. Möchten Sie auf das neue Format umsteigen?",
@@ -65,7 +66,9 @@
"Device that last modified the item": "Gerät, das das Element zuletzt geändert hat",
"Devices": "Geräte",
"Disabled": "Deaktiviert",
"Disabled periodic scanning": "Disabled periodic scanning",
"Disabled periodic scanning and disabled watching for changes": "Disabled periodic scanning and disabled watching for changes",
"Disabled periodic scanning and enabled watching for changes": "Disabled periodic scanning and enabled watching for changes",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:",
"Disconnected": "Getrennt",
"Discovered": "Ermittelt",
"Discovery": "Gerätesuche",
@@ -83,6 +86,7 @@
"Editing {%path%}.": "Bearbeite {{path}}.",
"Enable NAT traversal": "NAT-Durchdringung aktivieren",
"Enable Relaying": "Weiterleitung aktivieren",
"Enabled": "Enabled",
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Geben Sie eine positive Zahl ein (z.B. \"2.35\") und wählen Sie eine Einheit. Prozentsätze sind Teil der gesamten Festplattengröße.",
"Enter a non-privileged port number (1024 - 65535).": "Geben Sie eine nichtprivilegierte Portnummer ein (1024 - 65535).",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Kommagetrennte Adressen (\"tcp://ip:port\", \"tcp://host:port\") oder \"dynamic\" eingeben, um die Adresse automatisch zu ermitteln.",
@@ -185,7 +189,9 @@
"Pause": "Pause",
"Pause All": "Alles pausieren",
"Paused": "Pausiert",
"Periodic scan every": "Periodic scan every",
"Periodic scanning at given interval and disabled watching for changes": "Periodic scanning at given interval and disabled watching for changes",
"Periodic scanning at given interval and enabled watching for changes": "Periodic scanning at given interval and enabled watching for changes",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:",
"Please consult the release notes before performing a major upgrade.": "Bitte lesen Sie die Veröffentlichungshinweise bevor Sie eine Hauptversionsaktualisierung installieren.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Bitte lege einen Benutzer und ein Passwort für die Benutzeroberfläche in den Einstellungen fest.",
"Please wait": "Bitte warten",
+13 -7
View File
@@ -22,6 +22,7 @@
"Allowed Networks": "Επιτρεπόμενα δίκτυα",
"Alphabetic": "Αλφαβητικά",
"An external command handles the versioning. It has to remove the file from the shared folder.": "Μια εξωτερική εντολή χειρίζεται την τήρηση εκδόσεων και αναλαμβάνει να αφαιρέσει το αρχείο από τον συγχρονισμένο φάκελο.",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "Μια εξωτερική εντολή χειρίζεται την τήρηση εκδόσεων και αναλαμβάνει να αφαιρέσει το αρχείο από τον συγχρονισμένο φάκελο. Αν η διαδρομή προς την εφαρμογή περιέχει διαστήματα, πρέπει να εσωκλείεται σε εισαγωγικά. ",
"An external command handles the versioning. It has to remove the file from the synced folder.": "Μια εξωτερική εντολή χειρίζεται την διαχείριση εκδόσεων. Χρειάζεται να αφαιρέσει το αρχείο από το φάκελο συγχρονισμένων.",
"Anonymous Usage Reporting": "Ανώνυμα στοιχεία χρήσης",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Η μορφή της αναφοράς ανώνυμων στοιχείων χρήσης έχει αλλάξει. Επιθυμείτε να μεταβείτε στη νέα μορφή;",
@@ -33,7 +34,7 @@
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Για τις αυτόματες αναβαθμίσεις μπορείτε πλέον να επιλέξετε μεταξύ σταθερών εκδόσεων και υποψήφιων εκδόσεων.",
"Automatic upgrades": "Αυτόματη αναβάθμιση",
"Automatically create or share folders that this device advertises at the default path.": "Αυτόματη δημιουργία ή κοινή χρήση φακέλων τους οποίους ανακοινώνει αυτή η συσκευή στην προκαθορισμένη διαδρομή.",
"Available debug logging facilities:": "Available debug logging facilities:",
"Available debug logging facilities:": "Διαθέσιμες επιλογές μηνυμάτων αποσφαλμάτωσης:",
"Be careful!": "Με προσοχή!",
"Bugs": "Bugs",
"CPU Utilization": "Επιβάρυνση του επεξεργαστή",
@@ -65,7 +66,9 @@
"Device that last modified the item": "Συσκευή από την οποία πραγματοποιήθηκε η τελευταία τροποποίηση του στοιχείου",
"Devices": "Συσκευές",
"Disabled": "Απενεργοποιημένη",
"Disabled periodic scanning": "Disabled periodic scanning",
"Disabled periodic scanning and disabled watching for changes": "Έχουν απενεργοποιηθεί η τακτική σάρωση και η επιτήρηση αλλαγών",
"Disabled periodic scanning and enabled watching for changes": "Έχει απενεργοποιηθεί η τακτική σάρωση και ενεργοποιηθεί η επιτήρηση αλλαγών",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Έχει απενεργοποιηθεί η τακτική σάρωση και απέτυχε η ενεργοποίηση επιτήρησης αλλαγών. Γίνεται νέα προσπάθεια κάθε 1m:",
"Disconnected": "Αποσυνδεδεμένη",
"Discovered": "Βάσει ανεύρεσης",
"Discovery": "Ανεύρεση συσκευών",
@@ -83,6 +86,7 @@
"Editing {%path%}.": "Επεξεργασία του {{path}}.",
"Enable NAT traversal": "Ενεργοποίηση διάσχισης NAT",
"Enable Relaying": "Ενεργοποίηση αναμετάδοσης",
"Enabled": "Ενεργοποιημένη",
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Εισάγετε έναν μη αρνητικό αριθμό (π.χ. «2.35») και επιλέξτε μια μονάδα μέτρησης. Τα ποσοστά ισχύουν ως προς το συνολικό μέγεθος του δίσκου.",
"Enter a non-privileged port number (1024 - 65535).": "Εισάγετε τον αριθμό μιας μη δεσμευμένης θύρας (1024 - 65535).",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Εισάγετε τις διευθύνσεις χωρισμένες με κόμμα (\"tcp://ip:port\", \"tcp://host:port\") ή γράψτε \"dynamic\" για την αυτόματη ανεύρεση τους.",
@@ -90,7 +94,7 @@
"Error": "Σφάλμα",
"External File Versioning": "Εξωτερική τήρηση εκδόσεων",
"Failed Items": "Αρχεία που απέτυχαν",
"Failed to setup, retrying": "Failed to setup, retrying",
"Failed to setup, retrying": "Αποτυχία ενεργοποίησης, γίνεται νέα προσπάθεια",
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Είναι φυσιολογική η αποτυχία σύνδεσης σε εξυπηρετητές IPv6 όταν δεν υπάρχει συνδεσιμότητα IPv6.",
"File Pull Order": "Σειρά με την οποία θα κατεβαίνουν τα αρχεία",
"File Versioning": "Τήρηση εκδόσεων αρχείων",
@@ -185,7 +189,9 @@
"Pause": "Παύση",
"Pause All": "Παύση όλων",
"Paused": "Σε παύση",
"Periodic scan every": "Periodic scan every",
"Periodic scanning at given interval and disabled watching for changes": "Τακτική σάρωση ανά καθορισμένο διάστημα και απενεργοποίηση επιτήρησης αλλαγών",
"Periodic scanning at given interval and enabled watching for changes": "Τακτική σάρωση ανά καθορισμένο διάστημα και ενεργοποίηση επιτήρησης αλλαγών",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Τακτική σάρωση ανά καθορισμένο διάστημα και αποτυχία ενεργοποίησης επιτήρησης αλλαγών. Γίνεται νέα προσπάθεια κάθε 1m:",
"Please consult the release notes before performing a major upgrade.": "Παρακαλούμε, πριν από την εκτέλεση μιας σημαντικής αναβάθμισης, να συμβουλευτείς το σημείωμα που τη συνοδεύει. ",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Παρακαλώ όρισε στις ρυθμίσεις έναν χρήστη και έναν κωδικό πρόσβασης για τη διεπαφή.",
"Please wait": "Παρακαλώ περιμένετε",
@@ -208,7 +214,7 @@
"Rescan": "Έλεγξε για αλλαγές",
"Rescan All": "Έλεγξέ τα όλα για αλλαγές",
"Rescan Interval": "Κάθε πότε θα ελέγχεται για αλλαγές ",
"Rescans": "Rescans",
"Rescans": "Επανασαρώσεις",
"Restart": "Επανεκκίνηση",
"Restart Needed": "Απαιτείται επανεκκίνηση",
"Restarting": "Επανεκκίνηση",
@@ -217,7 +223,7 @@
"Resume": "Συνέχεια",
"Resume All": "Συνέχιση όλων",
"Reused": "Χρησιμοποιήθηκε ξανά",
"Running": "Running",
"Running": "Εκτελείται",
"Save": "Αποθήκευση",
"Scan Time Remaining": "Εναπομείναντας χρόνος για τον έλεγχο ",
"Scanning": "Έλεγχος για αλλαγές",
@@ -304,7 +310,7 @@
"Unknown": "Άγνωστο",
"Unshared": "Δε μοιράζεται",
"Unused": "Δε χρησιμοποιείται",
"Up to Date": "Ενημερωμένος",
"Up to Date": "Ενημερωμένη",
"Updated": "Ενημερωμένο",
"Upgrade": "Αναβάθμιση",
"Upgrade To {%version%}": "Αναβάθμιση στην έκδοση {{version}}",
+8 -2
View File
@@ -22,6 +22,7 @@
"Allowed Networks": "Allowed Networks",
"Alphabetic": "Alphabetic",
"An external command handles the versioning. It has to remove the file from the shared folder.": "An external command handles the versioning. It has to remove the file from the shared folder.",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.",
"An external command handles the versioning. It has to remove the file from the synced folder.": "An external command handles the versioning. It has to remove the file from the synced folder.",
"Anonymous Usage Reporting": "Anonymous Usage Reporting",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Anonymous usage report format has changed. Would you like to move to the new format?",
@@ -65,7 +66,9 @@
"Device that last modified the item": "Device that last modified the item",
"Devices": "Devices",
"Disabled": "Disabled",
"Disabled periodic scanning": "Disabled periodic scanning",
"Disabled periodic scanning and disabled watching for changes": "Disabled periodic scanning and disabled watching for changes",
"Disabled periodic scanning and enabled watching for changes": "Disabled periodic scanning and enabled watching for changes",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:",
"Disconnected": "Disconnected",
"Discovered": "Discovered",
"Discovery": "Discovery",
@@ -83,6 +86,7 @@
"Editing {%path%}.": "Editing {{path}}.",
"Enable NAT traversal": "Enable NAT traversal",
"Enable Relaying": "Enable Relaying",
"Enabled": "Enabled",
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.",
"Enter a non-privileged port number (1024 - 65535).": "Enter a non-privileged port number (1024 - 65535).",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.",
@@ -185,7 +189,9 @@
"Pause": "Pause",
"Pause All": "Pause All",
"Paused": "Paused",
"Periodic scan every": "Periodic scan every",
"Periodic scanning at given interval and disabled watching for changes": "Periodic scanning at given interval and disabled watching for changes",
"Periodic scanning at given interval and enabled watching for changes": "Periodic scanning at given interval and enabled watching for changes",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:",
"Please consult the release notes before performing a major upgrade.": "Please consult the release notes before performing a major upgrade.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Please set a GUI Authentication User and Password in the Settings dialogue.",
"Please wait": "Please wait",
+1
View File
@@ -22,6 +22,7 @@
"Allowed Networks": "Allowed Networks",
"Alphabetic": "Alphabetic",
"An external command handles the versioning. It has to remove the file from the shared folder.": "An external command handles the versioning. It has to remove the file from the shared folder.",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.",
"An external command handles the versioning. It has to remove the file from the synced folder.": "An external command handles the versioning. It has to remove the file from the synced folder.",
"Anonymous Usage Reporting": "Anonymous Usage Reporting",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Anonymous usage report format has changed. Would you like to move to the new format?",
+8 -2
View File
@@ -22,6 +22,7 @@
"Allowed Networks": "Permesitaj Retoj",
"Alphabetic": "Alfabeta",
"An external command handles the versioning. It has to remove the file from the shared folder.": "Ekstera komando manipulas la version. Ĝi devas forigi la dosieron el la partigita dosierujo.",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.",
"An external command handles the versioning. It has to remove the file from the synced folder.": "Ekstera komando manipulas la version. Ĝi devas forigi la dosieron el la sinkronigita dosierujo.",
"Anonymous Usage Reporting": "Anonima Raporto de Uzado",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Anonymous usage report format has changed. Would you like to move to the new format?",
@@ -65,7 +66,9 @@
"Device that last modified the item": "Device that last modified the item",
"Devices": "Aparatoj",
"Disabled": "Disabled",
"Disabled periodic scanning": "Disabled periodic scanning",
"Disabled periodic scanning and disabled watching for changes": "Disabled periodic scanning and disabled watching for changes",
"Disabled periodic scanning and enabled watching for changes": "Disabled periodic scanning and enabled watching for changes",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:",
"Disconnected": "Malkonektita",
"Discovered": "Malkovrita",
"Discovery": "Malkovro",
@@ -83,6 +86,7 @@
"Editing {%path%}.": "Redaktado {{path}}.",
"Enable NAT traversal": "Ŝaltu trairan NAT",
"Enable Relaying": "Ŝaltu Relajsadon",
"Enabled": "Enabled",
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.",
"Enter a non-privileged port number (1024 - 65535).": "Enter a non-privileged port number (1024 - 65535).",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Enigi adresojn dividitajn per komoj (\"tcp://ip:port\", \"tcp://host:port\") aŭ \"dynamic\" por elfari aŭtomatan malkovradon de la adreso.",
@@ -185,7 +189,9 @@
"Pause": "Paŭzu",
"Pause All": "Paŭzu Ĉion",
"Paused": "Paŭzita",
"Periodic scan every": "Periodic scan every",
"Periodic scanning at given interval and disabled watching for changes": "Periodic scanning at given interval and disabled watching for changes",
"Periodic scanning at given interval and enabled watching for changes": "Periodic scanning at given interval and enabled watching for changes",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:",
"Please consult the release notes before performing a major upgrade.": "Bonvolu konsulti la eldonitajn notojn antaŭ elfari ĉefan ĝisdatigon.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Bonvolu agordi GUI Authentication Uzanto kaj Pasvorto en la agordoj dialogo.",
"Please wait": "Bonvolu atendi",
+8 -2
View File
@@ -22,6 +22,7 @@
"Allowed Networks": "Redes permitidas",
"Alphabetic": "Alfabético",
"An external command handles the versioning. It has to remove the file from the shared folder.": "Un comando externo gestiona las versiones. Tiene que eliminar el fichero de la carpeta compartida.",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.",
"An external command handles the versioning. It has to remove the file from the synced folder.": "Un comando externo controla la versión. El fichero debe ser eliminado de la carpeta sincronizada.",
"Anonymous Usage Reporting": "Informe anónimo de uso",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Anonymous usage report format has changed. Would you like to move to the new format?",
@@ -65,7 +66,9 @@
"Device that last modified the item": "Device that last modified the item",
"Devices": "Dispositivos",
"Disabled": "Disabled",
"Disabled periodic scanning": "Disabled periodic scanning",
"Disabled periodic scanning and disabled watching for changes": "Disabled periodic scanning and disabled watching for changes",
"Disabled periodic scanning and enabled watching for changes": "Disabled periodic scanning and enabled watching for changes",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:",
"Disconnected": "Desconectado",
"Discovered": "Descubierto",
"Discovery": "Descubrimiento",
@@ -83,6 +86,7 @@
"Editing {%path%}.": "Editando {{path}}.",
"Enable NAT traversal": "Permitir NAT transversal",
"Enable Relaying": "Habilitar Retransmisión",
"Enabled": "Enabled",
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Introduce un número no negativo (por ejemplo, \"2.35\") y selecciona una unidad. Los porcentajes son como parte del tamaño total del disco.",
"Enter a non-privileged port number (1024 - 65535).": "Introduce un puerto sin privilegios (1024 - 65535).",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Introduzca las direcciones, separadas por comas (\"tcp://ip:port\", \"tcp://host:port\"), o \"dynamic\" para llevar a cabo el descubrimiento automático de la dirección.",
@@ -185,7 +189,9 @@
"Pause": "Pausar",
"Pause All": "Pausar todo",
"Paused": "Pausado",
"Periodic scan every": "Periodic scan every",
"Periodic scanning at given interval and disabled watching for changes": "Periodic scanning at given interval and disabled watching for changes",
"Periodic scanning at given interval and enabled watching for changes": "Periodic scanning at given interval and enabled watching for changes",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:",
"Please consult the release notes before performing a major upgrade.": "Por favor, consultar las notas de la versión antes de realizar una actualización importante.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Por favor, introduzca un Usuario y Contraseña para la Autenticación de la Interfaz de Usuario en el panel de Ajustes.",
"Please wait": "Por favor, espere",
+8 -2
View File
@@ -22,6 +22,7 @@
"Allowed Networks": "Redes permitidas",
"Alphabetic": "Alfabético",
"An external command handles the versioning. It has to remove the file from the shared folder.": "Un comando externo gestiona las versiones. Tiene que eliminar el fichero de la carpeta compartida.",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.",
"An external command handles the versioning. It has to remove the file from the synced folder.": "Un comando externo controla la versión. El fichero debe ser eliminado de la carpeta sincronizada.",
"Anonymous Usage Reporting": "Informe anónimo de uso",
"Anonymous usage report format has changed. Would you like to move to the new format?": "El formato del informe de uso anónimo a cambiado. ¿Desearía usar el nuevo formato?",
@@ -65,7 +66,9 @@
"Device that last modified the item": "Dispositivo que modificó por última vez el ítem",
"Devices": "Dispositivos",
"Disabled": "Deshabilitado",
"Disabled periodic scanning": "Disabled periodic scanning",
"Disabled periodic scanning and disabled watching for changes": "Disabled periodic scanning and disabled watching for changes",
"Disabled periodic scanning and enabled watching for changes": "Disabled periodic scanning and enabled watching for changes",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:",
"Disconnected": "Desconectado",
"Discovered": "Descubierto",
"Discovery": "Descubrimiento",
@@ -83,6 +86,7 @@
"Editing {%path%}.": "Editando {{path}}.",
"Enable NAT traversal": "Permitir NAT transversal",
"Enable Relaying": "Habilitar Retransmisión",
"Enabled": "Enabled",
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Introduce un número no negativo (por ejemplo, \"2.35\") y selecciona una unidad. Los porcentajes son como parte del tamaño total del disco.",
"Enter a non-privileged port number (1024 - 65535).": "Introduce un puerto sin privilegios (1024 - 65535).",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Introduzca las direcciones, separadas por comas (\"tcp://ip:port\", \"tcp://host:port\"), o \"dynamic\" para llevar a cabo el descubrimiento automático de la dirección.",
@@ -185,7 +189,9 @@
"Pause": "Pausar",
"Pause All": "Pausar todo",
"Paused": "Pausado",
"Periodic scan every": "Periodic scan every",
"Periodic scanning at given interval and disabled watching for changes": "Periodic scanning at given interval and disabled watching for changes",
"Periodic scanning at given interval and enabled watching for changes": "Periodic scanning at given interval and enabled watching for changes",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:",
"Please consult the release notes before performing a major upgrade.": "Por favor, consultar las notas de la versión antes de realizar una actualización importante.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Por favor, introduzca un Usuario y Contraseña para la Autenticación de la Interfaz de Usuario en el panel de Ajustes.",
"Please wait": "Por favor, espere",
+8 -2
View File
@@ -22,6 +22,7 @@
"Allowed Networks": "Sare baimenduak",
"Alphabetic": "Alfabetikoa",
"An external command handles the versioning. It has to remove the file from the shared folder.": "Kanpoko kontrolagailu batek fitxategien bertsioak kudeatzen ditu. Fitxategiak kendu behar ditu errepertorio sinkronizatuan.",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.",
"An external command handles the versioning. It has to remove the file from the synced folder.": "Kanpoko kontrolagailu batek fitxeroen bertsioak erabiltzen ditu. Fitxeroak errepertorio sinkronizatutik desagertaraztea berari doakio.",
"Anonymous Usage Reporting": "Izenik gabeko erabiltze erreportak",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Anonymous usage report format has changed. Would you like to move to the new format?",
@@ -65,7 +66,9 @@
"Device that last modified the item": "Device that last modified the item",
"Devices": "Tresnak",
"Disabled": "Disabled",
"Disabled periodic scanning": "Disabled periodic scanning",
"Disabled periodic scanning and disabled watching for changes": "Disabled periodic scanning and disabled watching for changes",
"Disabled periodic scanning and enabled watching for changes": "Disabled periodic scanning and enabled watching for changes",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:",
"Disconnected": "Deskonektatua",
"Discovered": "Agertua",
"Discovery": "Agertzea",
@@ -83,6 +86,7 @@
"Editing {%path%}.": "Muntatzea {{path}}",
"Enable NAT traversal": "NAT translazioa aktibatu",
"Enable Relaying": "Aldizkatzea posible",
"Enabled": "Enabled",
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Negatiboa ez den zenbaki bat hauta ezazu (\"2.35\" adib.) bai eta unitate bat. Disko osoaren ehuneko espazioa",
"Enter a non-privileged port number (1024 - 65535).": "Abantailatua ez den portu zenbalki bat sar ezazu (1024 - 65535)",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": " (\"tcp://ip:ataka\", \"tcp://izena:ataka\") zuzenbideak sar, krakotx batez separatuak edo bestenaz \"dynamic\", zuzenbidearen xekatze automatikoa aktibatzeko",
@@ -185,7 +189,9 @@
"Pause": "Pausa",
"Pause All": "Geldi dena",
"Paused": "Gelditua",
"Periodic scan every": "Periodic scan every",
"Periodic scanning at given interval and disabled watching for changes": "Periodic scanning at given interval and disabled watching for changes",
"Periodic scanning at given interval and enabled watching for changes": "Periodic scanning at given interval and enabled watching for changes",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:",
"Please consult the release notes before performing a major upgrade.": "Aktualizatze garrantzitsu bat egin baino lehen, bertsioaren oharrak begira itzazu.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Konfigurazio leihoan asma itzazu erabiltzale izen bat eta pasahitz bat",
"Please wait": "Pazientzia pixka bat, otoi",
+8 -2
View File
@@ -22,6 +22,7 @@
"Allowed Networks": "Réseaux autorisés",
"Alphabetic": "Alphabétique",
"An external command handles the versioning. It has to remove the file from the shared folder.": "Une commande externe gère les versions de fichiers. Il lui incombe de supprimer les fichiers dans le répertoire synchronisé.",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.",
"An external command handles the versioning. It has to remove the file from the synced folder.": "Une commande externe gère les versions de fichiers. Il lui incombe de supprimer les fichiers dans le répertoire synchronisé.",
"Anonymous Usage Reporting": "Rapport anonyme de statistiques d'utilisation",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Anonymous usage report format has changed. Would you like to move to the new format?",
@@ -65,7 +66,9 @@
"Device that last modified the item": "Device that last modified the item",
"Devices": "Appareil",
"Disabled": "Disabled",
"Disabled periodic scanning": "Disabled periodic scanning",
"Disabled periodic scanning and disabled watching for changes": "Disabled periodic scanning and disabled watching for changes",
"Disabled periodic scanning and enabled watching for changes": "Disabled periodic scanning and enabled watching for changes",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:",
"Disconnected": "Déconnecté",
"Discovered": "Découvert",
"Discovery": "Découverte",
@@ -83,6 +86,7 @@
"Editing {%path%}.": "Modification de {{path}}.",
"Enable NAT traversal": "Activer transfert d'adresses NAT",
"Enable Relaying": "Activer le relayage",
"Enabled": "Enabled",
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Nombre positif (p.ex, \"2.35\") et unité. Pourcentage de l'espace disque total.",
"Enter a non-privileged port number (1024 - 65535).": "Enter a non-privileged port number (1024 - 65535).",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Entrer les adresses (\"tcp://ip:port\" ou \"tcp://hôte:port\") séparées par une virgule, ou \"dynamic\" afin d'activer la recherche automatique de l'adresse.",
@@ -185,7 +189,9 @@
"Pause": "Pause",
"Pause All": "Tout suspendre",
"Paused": "En pause",
"Periodic scan every": "Periodic scan every",
"Periodic scanning at given interval and disabled watching for changes": "Periodic scanning at given interval and disabled watching for changes",
"Periodic scanning at given interval and enabled watching for changes": "Periodic scanning at given interval and enabled watching for changes",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:",
"Please consult the release notes before performing a major upgrade.": "Veuillez consulter les notes de version avant de réaliser une mise à jour majeure.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Veuillez définir un nom d'utilisateur et un mot de passe dans les réglages.",
"Please wait": "Merci de patienter",
+20 -14
View File
@@ -8,8 +8,8 @@
"Actions": "Actions",
"Add": "Ajouter",
"Add Device": "Ajouter l'appareil",
"Add Folder": "Ajouter un partage",
"Add Remote Device": "Ajouter un appareil",
"Add Folder": "Ajouter un partage...",
"Add Remote Device": "Ajouter un appareil...",
"Add devices from the introducer to our device list, for mutually shared folders.": "ATTENTION !!! Lui permettre d'ajouter et enlever des membres à toutes mes listes de membres des partages dont il fait (ou fera !) partie (ceci permet de créer automatiquement toutes les liaisons point à point possibles en complétant mes listes par les siennes, meilleur débit de réception par cumul des débits d'envoi, indépendance vis à vis de l'introducteur, etc).",
"Add new folder?": "Ajouter ce partage ?",
"Address": "Adresse",
@@ -21,7 +21,8 @@
"Allow Anonymous Usage Reporting?": "Autoriser l'envoi de statistiques d'utilisation anonymisées ?",
"Allowed Networks": "Réseaux autorisés",
"Alphabetic": "Alphabétique",
"An external command handles the versioning. It has to remove the file from the shared folder.": "Une commande externe gère les versions de fichiers. Il lui incombe de supprimer les fichiers dans le répertoire synchronisé.",
"An external command handles the versioning. It has to remove the file from the shared folder.": "Une commande externe gère les versions de fichiers. Il lui incombe de supprimer les fichiers du répertoire partagé.",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "Une commande externe gère les versions de fichiers. Il lui incombe de supprimer les fichiers du répertoire partagé. Si le chemin contient des espaces, il doit être spécifié entre guillemets.",
"An external command handles the versioning. It has to remove the file from the synced folder.": "Une commande externe gère les versions de fichiers. Il lui incombe de supprimer les fichiers dans le répertoire synchronisé.",
"Anonymous Usage Reporting": "Rapport anonyme de statistiques d'utilisation",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Le format du rapport anonyme d'utilisation a changé. Voulez-vous passer au nouveau format ?",
@@ -55,7 +56,7 @@
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Création de masques d'exclusion, remplacement du fichier existant : {{path}}.",
"Danger!": "Attention !",
"Debugging Facilities": "Outils de débogage",
"Default Folder Path": "Chemin par défaut des partages",
"Default Folder Path": "Chemin parent par défaut pour les nouveaux partages",
"Deleted": "Supprimé",
"Device": "Appareil",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "\"{{name}}\" ({{device}}), appareil actuellement à {{address}}, demande à se connecter.\nAcceptez-vous de l'ajouter à votre liste d'appareils connus ?",
@@ -65,7 +66,9 @@
"Device that last modified the item": "Dernier appareil modificateur",
"Devices": "Appareils",
"Disabled": "Désactivé",
"Disabled periodic scanning": "Dynamique",
"Disabled periodic scanning and disabled watching for changes": "Analyse périodique et surveillance des changements désactivées.",
"Disabled periodic scanning and enabled watching for changes": "Analyse périodique désactivée et surveillance des changements activée.",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Analyse périodique désactivée et échec d'activation de la surveillance des changements. Nouvel essai toutes les 1mn :",
"Disconnected": "Déconnecté",
"Discovered": "Découvert",
"Discovery": "Découverte",
@@ -76,20 +79,21 @@
"Download Rate": "Débit de réception",
"Downloaded": "Reçu",
"Downloading": "Réception",
"Edit": "Gérer",
"Edit": "Gérer...",
"Edit Device": "Gérer l'appareil",
"Edit Folder": "Gérer le partage",
"Editing": "Modifications",
"Editing {%path%}.": "Modification de {{path}}.",
"Enable NAT traversal": "Activer la translation d'adresses (NAT)",
"Enable Relaying": "Relayage possible",
"Enabled": "Activée",
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Nombre positif (p.ex, \"2.35\") et unité. Pourcentage de l'espace disque total.",
"Enter a non-privileged port number (1024 - 65535).": "Entrez un n° de port non-privilégié (1024 - 65535)",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Entrer les adresses (\"tcp://ip:port\" ou \"tcp://hôte:port\") séparées par une virgule, ou \"dynamic\" afin d'activer la recherche automatique de l'adresse.",
"Enter ignore patterns, one per line.": "Entrez les masques d'exclusion, un par ligne.",
"Error": "Erreur",
"External File Versioning": "Gestion externe des versions de fichiers",
"Failed Items": "Fichiers en échec",
"Failed Items": "Éléments en échec",
"Failed to setup, retrying": "Échec, nouvel essai",
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "La connexion aux serveurs en IPv6 va échouer s'il n'y a pas de connectivité IPv6.",
"File Pull Order": "Ordre de récupération des fichiers",
@@ -124,7 +128,7 @@
"Help": "Aide",
"Home page": "Page d'accueil",
"Ignore": "Ignorer",
"Ignore Patterns": "Exclusions ...",
"Ignore Patterns": "Exclusions...",
"Ignore Permissions": "Ignorer les permissions",
"Incoming Rate Limit (KiB/s)": "Limite du débit de réception (Kio/s)",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Une configuration incorrecte peut créer des dommages dans vos répertoires et mettre Syncthing hors-service.",
@@ -169,7 +173,7 @@
"Normal": "Normal",
"Notice": "Notification",
"OK": "OK",
"Off": "Désactivé(e)",
"Off": "Désactivée",
"Oldest First": "Les plus anciens en premier",
"Optional descriptive label for the folder. Can be different on each device.": "Nom local, convivial et optionnel du partage, à votre guise. il peut être différent sur chaque appareil. Par notification initiale, il sera proposé tel quel aux nouveaux participants.\nAstuce : comme il est modifiable ultérieurement, pensez à indiquer un nom parlant pour les invités, puis renommez-le quand ils l'auront accepté (exemple d'un partage à deux membres où l'initiateur commence par donner son propre nom au partage, puis le renomme plus tard au nom du partenaire quand celui-ci l'a enregistré). Évitez les erreurs d'orthographe car ce nom servira aussi de base au chemin proposé en création (local et distant) et ce chemin est difficilement modifiable.",
"Options": "Options",
@@ -185,7 +189,9 @@
"Pause": "Pause",
"Pause All": "Tout suspendre",
"Paused": "En pause",
"Periodic scan every": "Intervalle",
"Periodic scanning at given interval and disabled watching for changes": "Analyse périodique à intervalle défini et surveillance des changements désactivée.",
"Periodic scanning at given interval and enabled watching for changes": "Analyse périodique à intervalle défini et surveillance des changements activée.",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Analyse périodique à intervalle défini et échec d'activation de la surveillance des changements. Nouvel essai toutes les 1mn :",
"Please consult the release notes before performing a major upgrade.": "Veuillez consulter les notes de version avant de réaliser une mise à jour majeure.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Veuillez définir un nom d'utilisateur et un mot de passe dans la fenêtre de Configuration.",
"Please wait": "Merci de patienter",
@@ -196,19 +202,19 @@
"Quick guide to supported patterns": "Guide rapide des masques compatibles ci-dessous",
"RAM Utilization": "Utilisation de la RAM",
"Random": "Aléatoire",
"Recent Changes": "Changements récents",
"Recent Changes": "Changements récents...",
"Reduced by ignore patterns": "(Limité par des masques d'exclusion)",
"Release Notes": "Notes de version",
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Les versions préliminaires contiennent les dernières fonctionnalités et derniers correctifs. Elles sont identiques aux traditionnelles mises à jour bimensuelles.",
"Remote Devices": "Autres appareils",
"Remove": "Supprimer",
"Remove": "Supprimer...",
"Remove Device": "Supprimer l'appareil",
"Remove Folder": "Supprimer le partage",
"Required identifier for the folder. Must be the same on all cluster devices.": "Identifiant du partage. Doit être le même sur tous les appareils concernés (généré aléatoirement, mais modifiable à la création).",
"Rescan": "Réanalyser",
"Rescan All": "Tout réanalyser",
"Rescan Interval": "Intervalle d'analyse",
"Rescans": "Surveillance",
"Rescans": "Analyses/Surveillance",
"Restart": "Redémarrer",
"Restart Needed": "Redémarrage nécessaire",
"Restarting": "Redémarrage en cours",
@@ -314,7 +320,7 @@
"Usage reporting is always enabled for candidate releases.": "Les statistiques d'utilisation sont toujours envoyées pour les versions préliminaires.",
"Use HTTPS for GUI": "Utiliser l'HTTPS pour le GUI",
"Version": "Version",
"Versions": "Versions",
"Versions": "Restauration...",
"Versions Path": "Emplacement des versions",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Les plus anciennes versions seront supprimées automatiquement quand elles dépassent la durée maximum de conservation ou si leur nombre (par fichier) est supérieur à la limite prédéfinie pour l'intervalle.",
"Warning, this path is a parent directory of an existing folder \"{%otherFolder%}\".": "Attention, ce chemin est un répertoire parent d'au moins un partage existant (par exemple \"{{otherFolder}}\"). Si c'est bien ce que vous souhaitez, vous devriez créer un nouveau sous-répertoire, sinon ceci peut causer des problèmes tels que duplications et/ou suppressions intempestives de fichiers.",
+11 -5
View File
@@ -22,6 +22,7 @@
"Allowed Networks": "Tasteane Netwurken",
"Alphabetic": "Alfabetysk",
"An external command handles the versioning. It has to remove the file from the shared folder.": "In ekstern kommando soarget foar it ferzjebehear. It moat de triem út de dielde map fuortsmite.",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "In ekstern kommando soarget foar it ferzjebehear. It moat de triem út de dielde map fuortsmite. As it paad nei de applikaasje romtes hat, moat it tusken oanheltekens sette wurden.",
"An external command handles the versioning. It has to remove the file from the synced folder.": "In ekstern kommando soarget foar it ferzjebehear. It moat de triem út de syngronisearre map fuortsmite.",
"Anonymous Usage Reporting": "Anonym brûkensrapportaazje",
"Anonymous usage report format has changed. Would you like to move to the new format?": "It formaat fan de rapportaazje fan anonime gebrûksynformaasje is feroare. Wolle jo op dit nije formaat oerstappe?",
@@ -65,7 +66,9 @@
"Device that last modified the item": "Apparaat dat dit item it lêst oanpast hat",
"Devices": "Apparaten",
"Disabled": "Utskeakele",
"Disabled periodic scanning": "Disabled periodic scanning",
"Disabled periodic scanning and disabled watching for changes": "Periodic scanning útskeakele en feroarings wurde net mear yn'e gaten hâlden.",
"Disabled periodic scanning and enabled watching for changes": "Periodic scanning útskeakele en feroarings wurde yn'e gaten hâlden.",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Periodic scanning útskeakele en it ynskeakeljen fan it yn'e gaten hâlden fan feroarings is mislearre, wurd eltse 1m opnij besocht:",
"Disconnected": "Ferbining ferbrutsen",
"Discovered": "Untdekt",
"Discovery": "Untdekking",
@@ -83,6 +86,7 @@
"Editing {%path%}.": "{{path}} wurd bewurke.",
"Enable NAT traversal": "NAT-trochkruse ynskeakelje",
"Enable Relaying": "Trochjaan tastean",
"Enabled": "Ynskeakele",
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Fier in net-negatyf nûmer yn (bygelyks \"2.35\") en selektearje in ienheid. Percentages stean foar it part fan de totale skiifromte.",
"Enter a non-privileged port number (1024 - 65535).": "Fier in net-befoarrjochte poart-nûmer yn (1024 - 65535).",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Fier troch komma's skieden (\"tcp://ip:port\", \"tcp://host:port\") adressen yn of \"dynamic\" om automatyske ûntdekking fan it adres út te fieren.",
@@ -90,7 +94,7 @@
"Error": "Flater",
"External File Versioning": "Ekstern ferzjebehear foar triemen",
"Failed Items": "Mislearre items",
"Failed to setup, retrying": "Failed to setup, retrying",
"Failed to setup, retrying": "Ynskeakeljen mislearre, wurd no opnij besocht",
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": " Mislearjen fan it ferbinen mei IPv6-tsjinners wurd ferwachte as der gjin stipe foar IPv6-ferbinings is.",
"File Pull Order": "Triemlûkfolchoarder",
"File Versioning": "Triemferzjebehear",
@@ -185,7 +189,9 @@
"Pause": "Skoftsje",
"Pause All": "Alles skoftsje",
"Paused": "Skoftet",
"Periodic scan every": "Periodic scan every",
"Periodic scanning at given interval and disabled watching for changes": "Periodic scanning op opjûn ynterfal en feroarings wurde net yn'e gaten hâlden.",
"Periodic scanning at given interval and enabled watching for changes": "Periodic scanning op opjûn ynterfal en feroarings wurde yn'e gaten hâlden.",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Periodic scanning op opjûn ynterfal en it ynskeakeljen fan it yn'e gaten hâlden fan feroarings is mislearre, wurd eltse 1m opnij besocht:",
"Please consult the release notes before performing a major upgrade.": "Foardat jo in wichtige fernijing ynstallearre, graach earst de fernijingsoantekenings lêze.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Graach foar GUI-ferifikaasje in brûkers-ID en wachtwurd ynstelle yn it ynstellingsdialooch.",
"Please wait": "In amerijke",
@@ -208,7 +214,7 @@
"Rescan": "Sken opnij",
"Rescan All": "Sken alles opnij",
"Rescan Interval": "Wersken ynterval",
"Rescans": "Rescans",
"Rescans": "Werscans",
"Restart": "Werstarte",
"Restart Needed": "Werstart nedich",
"Restarting": "Oan it werstarten",
@@ -217,7 +223,7 @@
"Resume": "Trochgean",
"Resume All": "Alles trochgean litte",
"Reused": "Opnij brûkt",
"Running": "Running",
"Running": "Rint",
"Save": "Bewarje",
"Scan Time Remaining": "Oerbleaune skentiid",
"Scanning": "Oan it skennen",
+8 -2
View File
@@ -22,6 +22,7 @@
"Allowed Networks": "Engedélyezett hálózatok",
"Alphabetic": "ABC sorrendben",
"An external command handles the versioning. It has to remove the file from the shared folder.": "Külső program kezeli a fájlverzió-követést. Az távolítja el a fájlt a megosztott mappából.",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "Külső program kezeli a fájlverzió-követést. Az távolítja el a fájlt a megosztott mappából. Ha az alkalmazás útvonala szóközöket tartalmaz, zárójelezni szükséges az útvonalat.",
"An external command handles the versioning. It has to remove the file from the synced folder.": "Külső program kezeli a fájlverzió-követést. Az távolítja el a fájlt a szinkronizált mappából.",
"Anonymous Usage Reporting": "Névtelen felhasználási adatok küldése",
"Anonymous usage report format has changed. Would you like to move to the new format?": "A névtelen használati jelentés formátuma megváltozott. Szeretnél áttérni az új formátumra?",
@@ -65,7 +66,9 @@
"Device that last modified the item": "Az eszköz, amely utoljára módosította az elemet",
"Devices": "Eszközök",
"Disabled": "Letiltva",
"Disabled periodic scanning": "Periodikus átnézés letiltva",
"Disabled periodic scanning and disabled watching for changes": "A periodikus átnézés és a változások keresése letiltva",
"Disabled periodic scanning and enabled watching for changes": "A periodikus átnézés letiltva a változások keresése engedélyezve",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "A periodikus átnézés letiltva és a változások keresésének beállítása sikertelen, 1 percenként újrapróbálkozás:",
"Disconnected": "Kapcsolat bontva",
"Discovered": "Felfedezett",
"Discovery": "Felfedezés",
@@ -83,6 +86,7 @@
"Editing {%path%}.": "{{path}} szerkesztése.",
"Enable NAT traversal": "NAT bejárás engedélyezése",
"Enable Relaying": "Közvetítés engedélyezése",
"Enabled": "Engedélyezve",
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Adj meg egy nem-negatív számot (pl. \"2.35\") és válassz egy mértékegységet. A százalékok a teljes lemezméretre vonatkoznak.",
"Enter a non-privileged port number (1024 - 65535).": "Adj meg egy nem privilegizált port számot (1024 - 65535).",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Vesszővel elválasztva több cím is bevihető (\"tcp://ip:port\", \"tcp://host:port\"), az automatikus felderítéshez a 'dynamic' kulcsszó használatos. ",
@@ -185,7 +189,9 @@
"Pause": "Szünet",
"Pause All": "Mindent szüneteltet",
"Paused": "Szünetel",
"Periodic scan every": "Periodikus átnézés gyakorisága",
"Periodic scanning at given interval and disabled watching for changes": "Periodikus átnézés a megadott időközönként és a változások keresése letiltva",
"Periodic scanning at given interval and enabled watching for changes": "Periodikus átnézés a megadott időközönként és a változások keresése engedélyezve",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Periodikus átnézés a megadott időközönként és a változások keresésének beállítása sikertelen, 1 percenként újrapróbálkozás:",
"Please consult the release notes before performing a major upgrade.": "Nagyobb frissítés előtt ellenőrizni kell a kiadási megjegyzéseket.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Be kell állítani a grafikus felület felhasználónevét és jelszavát a Beállítások párbeszédablakban.",
"Please wait": "Türelem",
+11 -5
View File
@@ -22,6 +22,7 @@
"Allowed Networks": "Reti Consentite.",
"Alphabetic": "Alfabetico",
"An external command handles the versioning. It has to remove the file from the shared folder.": "Il controllo versione è gestito da un comando esterno. Quest'ultimo deve rimuovere il file dalla cartella condivisa.",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "Il controllo versione è gestito da un comando esterno. Quest'ultimo deve rimuovere il file dalla cartella condivisa. Se il percorso dell'applicazione contiene spazi, deve essere indicato tra virgolette.",
"An external command handles the versioning. It has to remove the file from the synced folder.": "Il controllo versione è gestito da un comando esterno. Quest'ultimo deve rimuovere il file dalla cartella sincronizzata.",
"Anonymous Usage Reporting": "Statistiche Anonime di Utilizzo",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Il formato delle statistiche anonime di utilizzo è cambiato. Vuoi passare al nuovo formato?",
@@ -65,7 +66,9 @@
"Device that last modified the item": "Dispositivo che ha modificato l'elemento per ultimo",
"Devices": "Dispositivi",
"Disabled": "Disabilitato",
"Disabled periodic scanning": "Disabled periodic scanning",
"Disabled periodic scanning and disabled watching for changes": "Scansione periodica disabilitata e verifica cambiamenti disabilitata",
"Disabled periodic scanning and enabled watching for changes": "Scansione periodica disabilitata e verifica cambiamenti abilitata",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Scansione periodica disabilitata e configurazione fallita della verifica cambiamenti, nuovo tentativo ogni 1m:",
"Disconnected": "Disconnesso",
"Discovered": "Individuato",
"Discovery": "Individuazione",
@@ -83,6 +86,7 @@
"Editing {%path%}.": "Modifica di {{path}}.",
"Enable NAT traversal": "Abilita NAT traversal",
"Enable Relaying": "Abilita Reindirizzamento",
"Enabled": "Abilitato",
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Inserisci un numero non negativo (ad esempio \"2.35\") e seleziona un'unità. Le percentuali sono parte della dimensione totale del disco.",
"Enter a non-privileged port number (1024 - 65535).": "Inserisci un numero di porta non-privilegiata (1024 - 65535).",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Inserisci indirizzi separati da virgola (\"tcp://ip:porta\", \"tcp://host:porta\") oppure \"dynamic\" per effettuare il rilevamento automatico dell'indirizzo.",
@@ -90,7 +94,7 @@
"Error": "Errore",
"External File Versioning": "Controllo Versione Esterno",
"Failed Items": "Elementi Errati",
"Failed to setup, retrying": "Failed to setup, retrying",
"Failed to setup, retrying": "Configurazione fallita, riprovo",
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "La connessione a server IPv6 fallisce se non c'è connettività IPv6.",
"File Pull Order": "Ordine Prelievo File",
"File Versioning": "Controllo Versione File",
@@ -185,7 +189,9 @@
"Pause": "Pausa",
"Pause All": "Pausa Tutti",
"Paused": "In Pausa",
"Periodic scan every": "Periodic scan every",
"Periodic scanning at given interval and disabled watching for changes": "Scansione periodica a intervalli determinati e verifica cambiamenti disabilitata",
"Periodic scanning at given interval and enabled watching for changes": "Scansione periodica a intervalli determinati e verifica cambiamenti abilitata",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Scansione periodica a intervalli determinati e configurazione fallita della verifica cambiamenti, nuovo tentativo ogni 1m:",
"Please consult the release notes before performing a major upgrade.": "Si prega di consultare le note di rilascio prima di eseguire un aggiornamento principale.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Per favore impostare Utente e Password dell'Interfaccia Grafica nelle Impostazioni.",
"Please wait": "Attendere prego",
@@ -208,7 +214,7 @@
"Rescan": "Riscansiona",
"Rescan All": "Riscansiona Tutto",
"Rescan Interval": "Intervallo Scansione",
"Rescans": "Rescans",
"Rescans": "Ri-scansione",
"Restart": "Riavvia",
"Restart Needed": "Riavvio Necessario",
"Restarting": "Riavvio",
@@ -217,7 +223,7 @@
"Resume": "Riprendi",
"Resume All": "Riprendi Tutti",
"Reused": "Riutilizzato",
"Running": "Running",
"Running": "In esecuzione",
"Save": "Salva",
"Scan Time Remaining": "Tempo di Scansione Rimanente",
"Scanning": "Scansione in corso",
+8 -2
View File
@@ -22,6 +22,7 @@
"Allowed Networks": "許可されているネットワーク",
"Alphabetic": "アルファベット順",
"An external command handles the versioning. It has to remove the file from the shared folder.": "外部コマンドでバージョン管理を行います。ここで指定するコマンドは、共有フォルダーからファイルを削除するものでなくてはなりません。",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.",
"An external command handles the versioning. It has to remove the file from the synced folder.": "外部コマンドにバージョンを管理させます。ここで指定するコマンドは、同期フォルダーからファイルを削除するものでなくてはなりません。",
"Anonymous Usage Reporting": "匿名での使用状況レポート",
"Anonymous usage report format has changed. Would you like to move to the new format?": "匿名での使用状況レポートのフォーマットが変わりました。新形式でのレポートに移行しますか?",
@@ -65,7 +66,9 @@
"Device that last modified the item": "Device that last modified the item",
"Devices": "デバイス",
"Disabled": "無効",
"Disabled periodic scanning": "Disabled periodic scanning",
"Disabled periodic scanning and disabled watching for changes": "Disabled periodic scanning and disabled watching for changes",
"Disabled periodic scanning and enabled watching for changes": "Disabled periodic scanning and enabled watching for changes",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:",
"Disconnected": "切断中",
"Discovered": "探索結果",
"Discovery": "探索サーバー",
@@ -83,6 +86,7 @@
"Editing {%path%}.": "{{path}} を編集中",
"Enable NAT traversal": "NATトラバーサルを有効にする",
"Enable Relaying": "中継サーバー経由の通信を有効にする",
"Enabled": "Enabled",
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "0以上の数 (例: 2.35) を入力し、単位を選択してください。パーセントはディスク容量全体に対する割合です。",
"Enter a non-privileged port number (1024 - 65535).": "非特権ポート番号 (1024 - 65535) を入力してください。",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "アドレスを指定する場合は「tcp://IPアドレス:ポート, tcp://ホスト名:ポート」のようにコンマで区切って入力してください。自動探索を行う場合は「dynamic」と入力してください。",
@@ -185,7 +189,9 @@
"Pause": "一時停止",
"Pause All": "すべて一時停止",
"Paused": "一時停止中",
"Periodic scan every": "Periodic scan every",
"Periodic scanning at given interval and disabled watching for changes": "Periodic scanning at given interval and disabled watching for changes",
"Periodic scanning at given interval and enabled watching for changes": "Periodic scanning at given interval and enabled watching for changes",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:",
"Please consult the release notes before performing a major upgrade.": "メジャーアップグレードを行う前にリリースノートを参照してください。",
"Please set a GUI Authentication User and Password in the Settings dialog.": "設定ダイアログから、GUI認証ユーザー名とパスワードを設定してください。",
"Please wait": "お待ちください",
+8 -2
View File
@@ -22,6 +22,7 @@
"Allowed Networks": "허가된 네트워크",
"Alphabetic": "알파벳순",
"An external command handles the versioning. It has to remove the file from the shared folder.": "외부 커맨드가 파일 버전을 관리합니다. 공유된 폴더에서 파일을 삭제해야 합니다.",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.",
"An external command handles the versioning. It has to remove the file from the synced folder.": "외부 커맨드가 파일 버전을 관리합니다. 동기화된 폴더에서 파일을 삭제해야 합니다.",
"Anonymous Usage Reporting": "익명 사용 보고서",
"Anonymous usage report format has changed. Would you like to move to the new format?": "익명 사용 리포트의 형식이 변경되었습니다. 새 형식으로 이동 하시겠습니까?",
@@ -65,7 +66,9 @@
"Device that last modified the item": "Device that last modified the item",
"Devices": "기기",
"Disabled": "비활성화",
"Disabled periodic scanning": "Disabled periodic scanning",
"Disabled periodic scanning and disabled watching for changes": "Disabled periodic scanning and disabled watching for changes",
"Disabled periodic scanning and enabled watching for changes": "Disabled periodic scanning and enabled watching for changes",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:",
"Disconnected": "연결 끊김",
"Discovered": "탐색됨",
"Discovery": "탐색",
@@ -83,6 +86,7 @@
"Editing {%path%}.": "{{path}} 수정하기.",
"Enable NAT traversal": "NAT traversal 활성화",
"Enable Relaying": "Relaying 활성화",
"Enabled": "Enabled",
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "음수가 아닌 수 (예, \"2.35\") 를 입력 후 단위를 선택하세요. 백분율은 총 디스크 크기의 일부입니다.",
"Enter a non-privileged port number (1024 - 65535).": "비 특권 포트 번호를 입력하세요 (1024 - 65535).",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "주소 자동 검색을 하기 위해서는 \"ip:port\" 형식의 주소들을 쉼표로 구분해서 입력하거나 \"dynamic\"을 입력하세요.",
@@ -185,7 +189,9 @@
"Pause": "일시 중지",
"Pause All": "모두 일시 중지",
"Paused": "일시 중지됨",
"Periodic scan every": "Periodic scan every",
"Periodic scanning at given interval and disabled watching for changes": "Periodic scanning at given interval and disabled watching for changes",
"Periodic scanning at given interval and enabled watching for changes": "Periodic scanning at given interval and enabled watching for changes",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:",
"Please consult the release notes before performing a major upgrade.": "메이저 업데이트를 하기 전에 먼저 릴리즈 노트를 살펴보세요.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "설정에서 GUI 인증용 User와 암호를 입력해주세요.",
"Please wait": "기다려 주십시오",
+8 -2
View File
@@ -22,6 +22,7 @@
"Allowed Networks": "Leidžiami tinklai",
"Alphabetic": "Abėcėlės tvarka",
"An external command handles the versioning. It has to remove the file from the shared folder.": "Išorinė komanda apdoroja versijų valdymą. Ji turi pašalinti failą iš bendrinamo aplanko.",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "Išorinė komanda apdoroja versijų valdymą. Ji turi pašalinti failą iš bendrinamo aplanko. Jei kelyje į programą yra tarpų, jie turėtų būti imami į kabutes.",
"An external command handles the versioning. It has to remove the file from the synced folder.": "Išorinė komanda apdoroja versijų valdymą. Ji turi pašalinti failą iš sinchronizuoto aplanko.",
"Anonymous Usage Reporting": "Anoniminė naudojimo ataskaita",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Anoniminės naudojimo ataskaitos formatas pasikeitė. Ar norėtumėte pereiti prie naujojo formato?",
@@ -65,7 +66,9 @@
"Device that last modified the item": "Įrenginys, kuris paskutinis modifikavo elementą",
"Devices": "Įrenginiai",
"Disabled": "Išjungta",
"Disabled periodic scanning": "Periodinis nuskaitymas išjungtas",
"Disabled periodic scanning and disabled watching for changes": "Išjungtas periodinis nuskaitymas ir išjungtas pakeitimų stebėjimas",
"Disabled periodic scanning and enabled watching for changes": "Išjungtas periodinis nuskaitymas ir įjungtas pakeitimų stebėjimas",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Išjungtas periodinis nuskaitymas ir nepavykęs nustatyti pakeitimų stebėjimas, bandoma iš naujo kas 1 min.:",
"Disconnected": "Atsijungęs",
"Discovered": "Atrastas",
"Discovery": "Lokacija",
@@ -83,6 +86,7 @@
"Editing {%path%}.": "Redaguojama {{path}}.",
"Enable NAT traversal": "Leisti kirsti NAT",
"Enable Relaying": "Įjungti retransliavimą",
"Enabled": "Įjungta",
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Įveskite neneigiamąjį skaičių (pvz., \"2.35\") ir pasirinkite įtaisą. Procentai yra skaičiuojami kaip viso disko dydžio dalis.",
"Enter a non-privileged port number (1024 - 65535).": "Įveskite neprivilegijuoto prievado numerį (1024 - 65535).",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Įveskite kableliais atskirtus (\"tcp://ip:prievadas\", \"tcp://serveris:prievadas\") adresus arba \"dynamic\", kad atliktumėte automatinį adresų aptikimą.",
@@ -185,7 +189,9 @@
"Pause": "Pristabdyti",
"Pause All": "Pristabdyti visus",
"Paused": "Pristabdyta",
"Periodic scan every": "Periodinis nuskaitymas kas",
"Periodic scanning at given interval and disabled watching for changes": "Periodinis nuskaitymas nurodytu intervalu ir išjungtas pakeitimų stebėjimas",
"Periodic scanning at given interval and enabled watching for changes": "Periodinis nuskaitymas nurodytu intervalu ir įjungtas pakeitimų stebėjimas",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Periodinis nuskaitymas nurodytu intervalu ir nepavykęs nustatyti pakeitimų stebėjimas, bandoma iš naujo kas 1 min.:",
"Please consult the release notes before performing a major upgrade.": "Peržvelkite laidos informaciją prieš atlikdami stambų atnaujinimą.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Prašome nustatymų dialoge nustatyti valdymo skydelio vartotojo vardą ir slaptažodį.",
"Please wait": "Prašome palaukti",
+8 -2
View File
@@ -22,6 +22,7 @@
"Allowed Networks": "Tillatte nettverk",
"Alphabetic": "Alfabetisk",
"An external command handles the versioning. It has to remove the file from the shared folder.": "En ekstern kommando håndterer versjonkontrollen. Den må fjerne filen fra den delte mappa.",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.",
"An external command handles the versioning. It has to remove the file from the synced folder.": "En ekstern kommando håndterer versjonkontrollen. Den må fjerne filen fra den synkroniserte mappa.",
"Anonymous Usage Reporting": "Anonym innsamling av brukerdata",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Det anonyme bruksrapportformatet har endret seg. Ønsker du å gå over til det nye formatet?",
@@ -65,7 +66,9 @@
"Device that last modified the item": "Enheten som sist endret elementet",
"Devices": "Enheter",
"Disabled": "Avskrudd",
"Disabled periodic scanning": "Disabled periodic scanning",
"Disabled periodic scanning and disabled watching for changes": "Disabled periodic scanning and disabled watching for changes",
"Disabled periodic scanning and enabled watching for changes": "Disabled periodic scanning and enabled watching for changes",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:",
"Disconnected": "Frakoblet",
"Discovered": "Oppdaget",
"Discovery": "Oppslag",
@@ -83,6 +86,7 @@
"Editing {%path%}.": "Redigerer {{path}}.",
"Enable NAT traversal": "Slå på NAT-traversering",
"Enable Relaying": "Aktiver reléforsendelse",
"Enabled": "Enabled",
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Skriv inn et ikke-negativt nummer (f.eks. \"2.35\") og velg en enhet. Prosenter er deler av total diskstørrelse.",
"Enter a non-privileged port number (1024 - 65535).": "Skriv inn et ikke-priviligert portnummer (1024-65535).",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Skriv inn kommaseparerte (\"tcp://ip:port\", \"tcp://host:port\") adresser, eller ordet \"dynamic\" for å gjøre automatisk oppslag i adressen.",
@@ -185,7 +189,9 @@
"Pause": "Oppholde",
"Pause All": "Sett alt på pause",
"Paused": "Oppholdt",
"Periodic scan every": "Periodic scan every",
"Periodic scanning at given interval and disabled watching for changes": "Periodic scanning at given interval and disabled watching for changes",
"Periodic scanning at given interval and enabled watching for changes": "Periodic scanning at given interval and enabled watching for changes",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:",
"Please consult the release notes before performing a major upgrade.": "Sjekk utgivelsesnotatene før en storoppgradering utføres.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Vennligst angi bruker og passord for GUI-autentisering i innstillingsvinduet.",
"Please wait": "Vent",
+8 -2
View File
@@ -22,6 +22,7 @@
"Allowed Networks": "Toegestane netwerken",
"Alphabetic": "Alfabetisch",
"An external command handles the versioning. It has to remove the file from the shared folder.": "Een externe opdracht regelt het versiebeheer. Dit moet het bestand verwijderen uit de gedeelde map.",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "Een externe opdracht regelt het versiebeheer. Dit moet het bestand verwijderen uit de gedeelde map. Als het pad naar de toepassing spaties bevat, moet dit tussen aanhalingstekens geplaatst worden.",
"An external command handles the versioning. It has to remove the file from the synced folder.": "Een extern commando regelt het versiebeheer. Dit moet het bestand verwijderen van de gesynchroniseerde map.",
"Anonymous Usage Reporting": "Anonieme gebruikersstatistieken",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Het formaat voor anonieme gebruikersrapporten is gewijzigd. Wilt u naar het nieuwe formaat overschakelen?",
@@ -65,7 +66,9 @@
"Device that last modified the item": "Apparaat dat het item laatst gewijzigd heeft",
"Devices": "Apparaten",
"Disabled": "Uitgeschakeld",
"Disabled periodic scanning": "Periodiek scannen uitgeschakeld",
"Disabled periodic scanning and disabled watching for changes": "Disabled periodic scanning and disabled watching for changes",
"Disabled periodic scanning and enabled watching for changes": "Disabled periodic scanning and enabled watching for changes",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:",
"Disconnected": "Niet verbonden",
"Discovered": "Ontdekt",
"Discovery": "Ontdekken",
@@ -83,6 +86,7 @@
"Editing {%path%}.": "Bezig met bewerken van {{path}}.",
"Enable NAT traversal": "NAT traversal inschakelen",
"Enable Relaying": "Doorsturen inschakelen",
"Enabled": "Ingeschakeld",
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Voer een positief nummer in (bijv. \"2.35\") en selecteer een eenheid. Percentages zijn een onderdeel van de totale schijfgrootte.",
"Enter a non-privileged port number (1024 - 65535).": "Voer een niet-geprivilegieerd poortnummer in (1024 - 65535).",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Voer door komma's gescheiden (\"tcp://ip:port\", \"tcp://host:port\") adressen in of voer \"dynamisch\" in om automatische ontdekking van het adres uit te voeren.",
@@ -185,7 +189,9 @@
"Pause": "Pauze",
"Pause All": "Alles pauzeren",
"Paused": "Gepauseerd",
"Periodic scan every": "Periodiek scannen elke",
"Periodic scanning at given interval and disabled watching for changes": "Periodic scanning at given interval and disabled watching for changes",
"Periodic scanning at given interval and enabled watching for changes": "Periodic scanning at given interval and enabled watching for changes",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:",
"Please consult the release notes before performing a major upgrade.": "Lees eerst de release notes voordat u een grote update uitvoert.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Stel een gebruikersnaam en wachtwoord in bij 'Instellingen'.",
"Please wait": "Even geduld",
+8 -2
View File
@@ -22,6 +22,7 @@
"Allowed Networks": "Dozwolone sieci",
"Alphabetic": "Alfabetycznie",
"An external command handles the versioning. It has to remove the file from the shared folder.": "Zewnętrzna komenda odpowiedzialna za wersjonowanie. Musi usunąć plik ze współdzielonego folderu.",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.",
"An external command handles the versioning. It has to remove the file from the synced folder.": "Zewnętrzna komenda odpowiedzialna za wersjonowanie. Musi usuwać ten plik z synchronizowanego folderu.",
"Anonymous Usage Reporting": "Anonimowe statystyki użycia",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Format anonimowego raportu zużycia uległ zmianie.\nCzy chcesz przejść na nowy format?",
@@ -65,7 +66,9 @@
"Device that last modified the item": "Device that last modified the item",
"Devices": "Urządzenia",
"Disabled": "Wyłączone",
"Disabled periodic scanning": "Disabled periodic scanning",
"Disabled periodic scanning and disabled watching for changes": "Disabled periodic scanning and disabled watching for changes",
"Disabled periodic scanning and enabled watching for changes": "Disabled periodic scanning and enabled watching for changes",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:",
"Disconnected": "Rozłączony",
"Discovered": "Odkryte",
"Discovery": "Odnajdywanie",
@@ -83,6 +86,7 @@
"Editing {%path%}.": "Edytowanie {{path}}.",
"Enable NAT traversal": "Włącz trawersowanie NAT",
"Enable Relaying": "Włącz przekazywanie",
"Enabled": "Enabled",
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Wpisz nieujemną liczbę (np. \"2.35\") oraz wybierz jednostkę. Wartość procentowa odnosi się do rozmiaru całego dysku.",
"Enter a non-privileged port number (1024 - 65535).": "Wpisz nieuprzywilejowany numer portu (1024-65535).",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Wpisz oddzielone przecinkiem adresy (\"tcp://ip:port\", \"tcp://host:port\") lub \"dynamic\" by przeprowadzić automatyczne odnalezienie adresu.",
@@ -185,7 +189,9 @@
"Pause": "Zatrzymaj",
"Pause All": "Zatrzymaj wszystkie",
"Paused": "Zatrzymany",
"Periodic scan every": "Periodic scan every",
"Periodic scanning at given interval and disabled watching for changes": "Periodic scanning at given interval and disabled watching for changes",
"Periodic scanning at given interval and enabled watching for changes": "Periodic scanning at given interval and enabled watching for changes",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:",
"Please consult the release notes before performing a major upgrade.": "Zaleca się przeanalizowanie \"release notes\" przed przeprowadzeniem znaczącej aktualizacji.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Ustaw proszę użytkownika i hasło dostępowe do GUI w Ustawieniach",
"Please wait": "Proszę czekać",
+45 -39
View File
@@ -1,5 +1,5 @@
{
"A device with that ID is already added.": "Um dispositivo com esta ID já foi adicionado.",
"A device with that ID is already added.": "Um dispositivo com este ID já foi adicionado.",
"A negative number of days doesn't make sense.": "Um número negativo de dias não faz sentido.",
"A new major version may not be compatible with previous versions.": "Uma nova versão principal pode não ser compatível com versões anteriores.",
"API Key": "Chave da API",
@@ -22,18 +22,19 @@
"Allowed Networks": "Redes permitidas",
"Alphabetic": "Alfabética",
"An external command handles the versioning. It has to remove the file from the shared folder.": "Um comando externo cuida do versionamento. Ele tem que remover o arquivo da pasta compartilhada.",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.",
"An external command handles the versioning. It has to remove the file from the synced folder.": "Um programa externo controla o versionamento. Ele tem que remover o arquivo da pasta sincronizada.",
"Anonymous Usage Reporting": "Relatórios anônimos de uso",
"Anonymous usage report format has changed. Would you like to move to the new format?": "O formato do relatório anônimo de uso mudou. Gostaria de usar o formato novo?",
"Any devices configured on an introducer device will be added to this device as well.": "Quaisquer dispositivos configurados em um apresentador também serão adicionados a este dispositivo.",
"Are you sure you want to remove device {%name%}?": "Are you sure you want to remove device {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Are you sure you want to remove folder {{label}}?",
"Are you sure you want to restore {%count%} files?": "Are you sure you want to restore {{count}} files?",
"Auto Accept": "Auto Accept",
"Are you sure you want to remove device {%name%}?": "Tem certeza de que deseja remover o dispositivo {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Tem certeza de que deseja remover a pasta {{name}}?",
"Are you sure you want to restore {%count%} files?": "Tem certeza de que deseja restaurar {{count}} arquivo(s)?",
"Auto Accept": "Aceitar automaticamente",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "A atualização automática agora oferece a escolha entre versões estáveis e candidatas ao lançamento.",
"Automatic upgrades": "Atualizações automáticas",
"Automatically create or share folders that this device advertises at the default path.": "Automatically create or share folders that this device advertises at the default path.",
"Available debug logging facilities:": "Available debug logging facilities:",
"Automatically create or share folders that this device advertises at the default path.": "Criar ou compartilhar automaticamente pastas que este dispositivo anuncia no caminho padrão.",
"Available debug logging facilities:": "Facilidades de depuração disponíveis:",
"Be careful!": "Tenha cuidado!",
"Bugs": "Erros",
"CPU Utilization": "Uso de CPU",
@@ -47,31 +48,33 @@
"Configured": "Configurado",
"Connection Error": "Erro de conexão",
"Connection Type": "Tipo da conexão",
"Connections": "Connections",
"Connections": "Conexões",
"Copied from elsewhere": "Copiado de outro lugar",
"Copied from original": "Copiado do original",
"Copyright © 2014-2016 the following Contributors:": "Direitos reservados © 2014-2016 aos seguintes colaboradores:",
"Copyright © 2014-2017 the following Contributors:": "Copyright © 2014-2017 dos seguintes Colaboradores:",
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Criando filtros, sobrescrevendo o arquivo {{path}}.",
"Danger!": "Perigo!",
"Debugging Facilities": "Debugging Facilities",
"Default Folder Path": "Default Folder Path",
"Debugging Facilities": "Facilidades de depuração",
"Default Folder Path": "Caminho padrão da pasta",
"Deleted": "Apagado",
"Device": "Dispositivo",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Dispositivo \"{{name}}\" ({{device}} em {{address}}) quer se conectar. Adicionar novo dispositivo?",
"Device ID": "ID do dispositivo",
"Device Identification": "Identificação do dispositivo",
"Device Name": "Nome do dispositivo",
"Device that last modified the item": "Device that last modified the item",
"Device that last modified the item": "Dispositivo que modificou o item pela última vez",
"Devices": "Dispositivos",
"Disabled": "Desabilitado",
"Disabled periodic scanning": "Disabled periodic scanning",
"Disabled periodic scanning and disabled watching for changes": "Verificação periódica e verificação automática de mudanças desabilitadas",
"Disabled periodic scanning and enabled watching for changes": "Verificação periódica desabilitada. Verificação automática de mudanças habilitada.",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Verificação periódica desabilitada. Não foi possível configurar a verificação automática de mudanças, tentando novamente a cada 1 minuto:",
"Disconnected": "Desconectado",
"Discovered": "Descoberto",
"Discovery": "Descoberta",
"Discovery Failures": "Falhas na descoberta",
"Do not restore": "Do not restore",
"Do not restore all": "Do not restore all",
"Do not restore": "Não restaurar",
"Do not restore all": "Não restaurar nenhum",
"Documentation": "Documentação",
"Download Rate": "Velocidade de recepção",
"Downloaded": "Recebido",
@@ -83,6 +86,7 @@
"Editing {%path%}.": "Editando {{path}}.",
"Enable NAT traversal": "Habilitar NAT",
"Enable Relaying": "Habilitar retransmissão",
"Enabled": "Habilitado",
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Insira um número não negativo (por exemplo, 2.35) e escolha uma unidade. Porcentagens são como parte do tamanho total do disco.",
"Enter a non-privileged port number (1024 - 65535).": "Insira um número de porta não privilegiada (1024-65535).",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Insira endereços (\"tcp://ip:porta\", \"tcp://host:porta\") separados por vírgula ou \"dynamic\" para executar a descoberta automática do endereço.",
@@ -90,7 +94,7 @@
"Error": "Erro",
"External File Versioning": "Externo",
"Failed Items": "Itens com falha",
"Failed to setup, retrying": "Failed to setup, retrying",
"Failed to setup, retrying": "Não foi possível configurar, tentando novamente",
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Falhas na conexão a servidores IPv6 são esperadas caso não haja conectividade IPv6.",
"File Pull Order": "Ordem de retirada do arquivo",
"File Versioning": "Versionamento de arquivos",
@@ -101,8 +105,8 @@
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Os arquivos são renomeados com suas datas na pasta .stversions quando substituídos ou removidos 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 dos dispositivos.",
"Filesystem Notifications": "Notificação do Sistema de Arquivos",
"Filter by date": "Filter by date",
"Filter by name": "Filter by name",
"Filter by date": "Filtrar por data",
"Filter by name": "Filtrar por nome",
"Folder": "Pasta",
"Folder ID": "ID da pasta",
"Folder Label": "Rótulo da pasta",
@@ -115,7 +119,7 @@
"GUI Listen Address": "Endereço de escuta da interface web",
"GUI Listen Addresses": "Endereços de escuta da interface",
"GUI Theme": "Tema da interface",
"General": "General",
"General": "Geral",
"Generate": "Gerar",
"Global Changes": "Alterações globais",
"Global Discovery": "Descoberta global",
@@ -140,16 +144,16 @@
"Latest Change": "Última mudança",
"Learn more": "Saiba mais",
"Listeners": "Escutadores",
"Loading data...": "Loading data...",
"Loading...": "Loading...",
"Loading data...": "Carregando dados...",
"Loading...": "Carregando",
"Local Discovery": "Descoberta local",
"Local State": "Estado local",
"Local State (Total)": "Estado local (total)",
"Log": "Log",
"Log tailing paused. Click here to continue.": "Log tailing paused. Click here to continue.",
"Log tailing paused. Click here to continue.": "Observação do log pausada. Clique aqui para continuar.",
"Logs": "Logs",
"Major Upgrade": "Atualização \"major\"",
"Mass actions": "Mass actions",
"Mass actions": "Ações em massa",
"Master": "Mestre",
"Maximum Age": "Idade máxima",
"Metadata Only": "Somente metadados",
@@ -164,7 +168,7 @@
"Newest First": "Mais novo primeiro",
"No": "Não",
"No File Versioning": "Desligado",
"No files will be deleted as a result of this operation.": "No files will be deleted as a result of this operation.",
"No files will be deleted as a result of this operation.": "Nenhum arquivo será apagado como resultado desta operação",
"No upgrades": "Sem atualizações",
"Normal": "Normal",
"Notice": "Aviso",
@@ -179,13 +183,15 @@
"Override Changes": "Sobrescrever alterações",
"Path": "Caminho",
"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 new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {{tilde}}.",
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Caminho onde novas pastas aceitas automaticamente serão criadas, bem como o caminho padrão sugerido na adição de novas pastas pela interface. O caractere til (~) é substituído por {{tilde}}.",
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Caminho do diretório onde as versões são salvas (deixe em branco para que seja o diretório padrão .stversions dentro da pasta compartilhada). ",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "O caminho onde as versões serão salvas (deixe vazio para usar a pasta padrão .stversions dentro desta pasta).",
"Pause": "Pausar",
"Pause All": "Pausar Todas",
"Paused": "Em pausa",
"Periodic scan every": "Periodic scan every",
"Periodic scanning at given interval and disabled watching for changes": "Verificação periódica habilitada no intervalo escolhido. Verificação automática de mudanças desabilitada",
"Periodic scanning at given interval and enabled watching for changes": "Verificação periódica habilitada no intervalo escolhido. Verificação automática de mudanças habilitada",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Verificação periódica habilitada no intervalo escolhido. Não foi possível configurar a verificação automática de mudanças, tentando novamente a cada 1 minuto:",
"Please consult the release notes before performing a major upgrade.": "Por favor, consulte as notas de lançamento antes de atualizar para uma versão \"major\".",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Por favor, defina um nome de usuário e senha para acesso à interface web, nas configurações.",
"Please wait": "Aguarde",
@@ -196,36 +202,36 @@
"Quick guide to supported patterns": "Guia rápido dos padrões suportados",
"RAM Utilization": "Uso de RAM",
"Random": "Aleatória",
"Recent Changes": "Mudanças Recentes",
"Recent Changes": "Mudanças recentes",
"Reduced by ignore patterns": "Reduzido por filtros",
"Release Notes": "Notas de lançamento",
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Versões candidatas ao lançamento possuem os recursos e correções mais recentes. Elas são similares às tradicionais versões quinzenais.",
"Remote Devices": "Dispositivos remotos",
"Remove": "Remover",
"Remove Device": "Remove Device",
"Remove Device": "Remover dispositivo",
"Remove Folder": "Remover pasta",
"Required identifier for the folder. Must be the same on all cluster devices.": "Identificador obrigatório da pasta. Deve ser igual em todos os dispositivos do grupo.",
"Rescan": "Verificar agora",
"Rescan All": "Verificar todas",
"Rescan Interval": "Intervalo entre verificações",
"Rescans": "Rescans",
"Rescans": "Verificações",
"Restart": "Reiniciar",
"Restart Needed": "É necessário reiniciar",
"Restarting": "Reiniciando",
"Restore": "Restore",
"Restore Versions": "Restore Versions",
"Restore": "Restaurar",
"Restore Versions": "Restaurar versões",
"Resume": "Resumir",
"Resume All": "Resumir Todas",
"Reused": "Reutilizado",
"Running": "Running",
"Running": "Rodando",
"Save": "Salvar",
"Scan Time Remaining": "Tempo de verificação restante",
"Scanning": "Verificando",
"See external versioner help for supported templated command line parameters.": "Consulte a ajuda sobre versionamento externo para modelos de parâmetros de linha de comando aceitos.",
"See external versioning help for supported templated command line parameters.": "Consulte a ajuda sobre versionamento externo para modelos de parâmetros de linha de comando aceitos.",
"Select a version": "Selecione uma versão",
"Select latest version": "Select latest version",
"Select oldest version": "Select oldest version",
"Select latest version": "Escolher a última versão",
"Select oldest version": "Escolher a versão mais antiga",
"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.",
"Send & Receive": "Envia e recebe",
@@ -246,9 +252,9 @@
"Shutdown Complete": "Desligamento completado",
"Simple File Versioning": "Simples",
"Single level wildcard (matches within a directory only)": "Coringa de único nível (faz corresponder apenas dentro de uma pasta)",
"Size": "Size",
"Size": "Tamanho",
"Smallest First": "Menor primeiro",
"Some items could not be restored:": "Some items could not be restored:",
"Some items could not be restored:": "Alguns itens não puderam ser restaurados:",
"Source Code": "Código-fonte",
"Stable releases and release candidates": "Versões estáveis e candidatas ao lançamento",
"Stable releases are delayed by about two weeks. During this time they go through testing as release candidates.": "Versões estáveis são lançadas aproximadamente a cada duas semanas. Durante esse tempo elas são testadas como candidatas.",
@@ -295,11 +301,11 @@
"This is a major version upgrade.": "Esta é uma atualização para uma versão \"major\".",
"This setting controls the free space required on the home (i.e., index database) disk.": "Este ajuste controla o espaço livre necessário no disco que contém o banco de dados do Syncthing.",
"Time": "Hora",
"Time the item was last modified": "Time the item was last modified",
"Time the item was last modified": "Momento em que o item foi modificado pela última vez",
"Trash Can File Versioning": "Lixeira",
"Type": "Tipo",
"Unavailable": "Unavailable",
"Unavailable/Disabled by administrator or maintainer": "Unavailable/Disabled by administrator or maintainer",
"Unavailable": "Não disponível",
"Unavailable/Disabled by administrator or maintainer": "Não disponível ou desabilitado pelo administrador ou mantenedor",
"Undecided (will prompt)": "Não tenho certeza (perguntar sempre)",
"Unknown": "Desconhecida",
"Unshared": "Não compartilhada",
@@ -314,7 +320,7 @@
"Usage reporting is always enabled for candidate releases.": "O relatório de uso está sempre habilitado em versões candidatas ao lançamento",
"Use HTTPS for GUI": "Usar HTTPS para a interface web",
"Version": "Versão",
"Versions": "Versions",
"Versions": "Versões",
"Versions Path": "Caminho do versionamento",
"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.",
"Warning, this path is a parent directory of an existing folder \"{%otherFolder%}\".": "Aviso: este caminho é o diretório pai da pasta \"{{otherFolder}}\".",
+10 -4
View File
@@ -22,6 +22,7 @@
"Allowed Networks": "Redes permitidas",
"Alphabetic": "Alfabética",
"An external command handles the versioning. It has to remove the file from the shared folder.": "Um comando externo controla as versões. Esse comando tem que remover o ficheiro da pasta partilhada.",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "Um comando externo controla as versões. Esse comando tem que remover o ficheiro da pasta partilhada. Se o caminho para a aplicação contiver espaços, então terá de o escrever entre aspas.",
"An external command handles the versioning. It has to remove the file from the synced folder.": "Um comando externo trata do controle de versões. Esse comando tem que remover o ficheiro da pasta sincronizada.",
"Anonymous Usage Reporting": "Enviar relatórios anónimos de utilização",
"Anonymous usage report format has changed. Would you like to move to the new format?": "O formato do relatório anónimo de utilização foi alterado. Gostaria de mudar para o novo formato?",
@@ -64,8 +65,10 @@
"Device Name": "Nome do dispositivo",
"Device that last modified the item": "Último dispositivo a modificar o item",
"Devices": "Dispositivos",
"Disabled": "Desactivado",
"Disabled periodic scanning": "Verificação periódica desactivada",
"Disabled": "Desactivada",
"Disabled periodic scanning and disabled watching for changes": "Desactivada a verificação periódica e desactivada a vigilância de alterações",
"Disabled periodic scanning and enabled watching for changes": "Desactivada a verificação periódica e desactivada a vigilância de alterações",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Desactivada a verificação periódica e falha ao preparar a vigilância de alterações, tentando novamente a cada minuto:",
"Disconnected": "Desconectado",
"Discovered": "Descoberto",
"Discovery": "Pesquisa",
@@ -83,6 +86,7 @@
"Editing {%path%}.": "Editando {{path}}.",
"Enable NAT traversal": "Activar travessia de NAT",
"Enable Relaying": "Permitir retransmissão",
"Enabled": "Activada",
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Escreva um número positivo (ex.: \"2.35\") e seleccione uma unidade. Percentagens são relativas ao tamanho total do disco.",
"Enter a non-privileged port number (1024 - 65535).": "Escreva um número de porto não-privilegiado (1024-65535).",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Introduza endereços separados por vírgulas (\"tcp://ip:porto\", \"tcp://máquina:porto\") ou \"dynamic\" para detectar automaticamente os endereços.",
@@ -155,7 +159,7 @@
"Metadata Only": "Metadados apenas",
"Minimum Free Disk Space": "Espaço livre mínimo no disco",
"Mod. Device": "Dispositivo mod.",
"Mod. Time": "Quando mod.",
"Mod. Time": "Quando foi mod.",
"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",
@@ -185,7 +189,9 @@
"Pause": "Pausar",
"Pause All": "Pausar todas",
"Paused": "Em pausa",
"Periodic scan every": "Verificação periódica a cada",
"Periodic scanning at given interval and disabled watching for changes": "Verificação periódica no intervalo dado e desactivada a vigilância de alterações",
"Periodic scanning at given interval and enabled watching for changes": "Verificação periódica no intervalo dado e activada a vigilância de alterações",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Verificação periódica no intervalo dado e falha ao preparar a vigilância de alterações, tentando novamente a cada minuto:",
"Please consult the release notes before performing a major upgrade.": "Consulte as notas de lançamento antes de fazer uma actualização importante.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Por favor, defina um utilizador e senha de autenticação para a interface gráfica, nas configurações.",
"Please wait": "Aguarde",
+20 -14
View File
@@ -22,13 +22,14 @@
"Allowed Networks": "Разрешённые сети",
"Alphabetic": "По алфавиту",
"An external command handles the versioning. It has to remove the file from the shared folder.": "Для версионирования используется внешняя программа. Ей нужно удалить файл из общей папки.",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.",
"An external command handles the versioning. It has to remove the file from the synced folder.": "Внешний процесс управляет версиями файлов. Процесс удалит файл из синхронизируемой папки.",
"Anonymous Usage Reporting": "Анонимный отчет об использовании",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Формат анонимных отчётов изменился. Вы хотите переключиться на новый формат?",
"Any devices configured on an introducer device will be added to this device as well.": "Все устройства, подключённые к устройству-рекомендателю, будут добавлены к текущему устройству.",
"Are you sure you want to remove device {%name%}?": "Are you sure you want to remove device {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Are you sure you want to remove folder {{label}}?",
"Are you sure you want to restore {%count%} files?": "Are you sure you want to restore {{count}} files?",
"Are you sure you want to remove device {%name%}?": "Вы уверены, что хотите удалить устройство {{name}}?",
"Are you sure you want to remove folder {%label%}?": "Вы уверены, что хотите удалить папку {{label}}?",
"Are you sure you want to restore {%count%} files?": "Вы уверены, что хотите восстановить {{count}} файлов?",
"Auto Accept": "Auto Accept",
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Автоматическое обновление теперь предлагает выбор между стабильными выпусками и кандидатами в релизы.",
"Automatic upgrades": "Автообновление",
@@ -65,12 +66,14 @@
"Device that last modified the item": "Устройство, последним изменившее объект",
"Devices": "Устройства",
"Disabled": "Отключено",
"Disabled periodic scanning": "Disabled periodic scanning",
"Disabled periodic scanning and disabled watching for changes": "Disabled periodic scanning and disabled watching for changes",
"Disabled periodic scanning and enabled watching for changes": "Disabled periodic scanning and enabled watching for changes",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:",
"Disconnected": "Нет соединения",
"Discovered": "Обнаружено",
"Discovery": "Обнаружение",
"Discovery Failures": "Ошибки обнаружения",
"Do not restore": "Do not restore",
"Do not restore": "Не восстанавливать",
"Do not restore all": "Do not restore all",
"Documentation": "Документация",
"Download Rate": "Скорость загрузки",
@@ -83,6 +86,7 @@
"Editing {%path%}.": "Правка {{path}}.",
"Enable NAT traversal": "Включить NAT traversal",
"Enable Relaying": "Включить релеи",
"Enabled": "Enabled",
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.",
"Enter a non-privileged port number (1024 - 65535).": "Введите непривилегированный порт (1024—65535).",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Введите через запятую («tcp://ip:port», «tcp://host:port») адреса, либо «dynamic», чтобы выполнить автоматическое обнаружение адреса.",
@@ -101,8 +105,8 @@
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Файлы с временнОй меткой версии помещаются в папку .stversions при их замене или удалении 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.": "Файлы защищены от изменений сделанных на других устройствах, но изменения сделанные на этом устройстве будут отправлены всему кластеру.",
"Filesystem Notifications": "Уведомления файловой системы",
"Filter by date": "Filter by date",
"Filter by name": "Filter by name",
"Filter by date": "Отфильтровать по дате",
"Filter by name": "Отфильтровать по имени",
"Folder": "Папка",
"Folder ID": "ID папки",
"Folder Label": "Ярлык папки",
@@ -164,7 +168,7 @@
"Newest First": "Сначала новые",
"No": "Нет",
"No File Versioning": "Без управления версиями файлов",
"No files will be deleted as a result of this operation.": "No files will be deleted as a result of this operation.",
"No files will be deleted as a result of this operation.": "В результате этой операции никакие файлы не будут удалены",
"No upgrades": "Нет обновлений",
"Normal": "Нормально",
"Notice": "Внимание",
@@ -185,7 +189,9 @@
"Pause": "Пауза",
"Pause All": "Приостановить все",
"Paused": "Приостановлено",
"Periodic scan every": "Periodic scan every",
"Periodic scanning at given interval and disabled watching for changes": "Periodic scanning at given interval and disabled watching for changes",
"Periodic scanning at given interval and enabled watching for changes": "Periodic scanning at given interval and enabled watching for changes",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:",
"Please consult the release notes before performing a major upgrade.": "Перед проведением обновления основной версии ознакомтесь, пожалуйста, с Замечаниями к версии",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Установите имя пользователя и пароль для интерфейса в настройках",
"Please wait": "Пожалуйста, подождите",
@@ -212,20 +218,20 @@
"Restart": "Перезапустить",
"Restart Needed": "Требуется перезапуск",
"Restarting": "Перезапуск",
"Restore": "Restore",
"Restore": "Восстановить",
"Restore Versions": "Restore Versions",
"Resume": "Возобновить",
"Resume All": "Возобновить все",
"Reused": "Повторно использовано",
"Running": "Running",
"Running": "Запущено",
"Save": "Сохранить",
"Scan Time Remaining": "Оставшееся время сканирования",
"Scanning": "Сканирование",
"See external versioner help for supported templated command line parameters.": "See external versioner help for supported templated command line parameters.",
"See external versioning help for supported templated command line parameters.": "See external versioning help for supported templated command line parameters.",
"Select a version": "Выберите версию",
"Select latest version": "Select latest version",
"Select oldest version": "Select oldest version",
"Select latest version": "Выбрать последнюю версию",
"Select oldest version": "Выбрать самую старую версию",
"Select the devices to share this folder with.": "Выберите устройства, для которых будет доступна эта папка.",
"Select the folders to share with this device.": "Выберите папки, которые будут доступны этому устройству.",
"Send & Receive": "Отправить и получить",
@@ -314,7 +320,7 @@
"Usage reporting is always enabled for candidate releases.": "Отправка отчётов об использовании всегда включена для кандидатов в релизы.",
"Use HTTPS for GUI": "Использовать HTTPS для панели управления",
"Version": "Версия",
"Versions": "Versions",
"Versions": "Версии",
"Versions Path": "Путь к версиям",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Версии удаляются автоматически, если они существуют дольше максимального срока или превышают разрешённое количество файлов за интервал.",
"Warning, this path is a parent directory of an existing folder \"{%otherFolder%}\".": "Внимание! Этот путь — родительская директория уже существующей папки «{{otherFolder}}».",
+8 -2
View File
@@ -22,6 +22,7 @@
"Allowed Networks": "Povolené siete",
"Alphabetic": "Abecedne",
"An external command handles the versioning. It has to remove the file from the shared folder.": "Externý príkaz obstaráva verzie. Musí odstrániť ",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.",
"An external command handles the versioning. It has to remove the file from the synced folder.": "Verzie spravuje externý príkaz. Musí odstrániť súbor zo synchronizovaného adresára.",
"Anonymous Usage Reporting": "Anonymné hlásenie o používaní",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Anonymous usage report format has changed. Would you like to move to the new format?",
@@ -65,7 +66,9 @@
"Device that last modified the item": "Zariadenie, ktoré naposledy pozmenilo položku",
"Devices": "Zariadenia",
"Disabled": "Odpojené",
"Disabled periodic scanning": "Disabled periodic scanning",
"Disabled periodic scanning and disabled watching for changes": "Disabled periodic scanning and disabled watching for changes",
"Disabled periodic scanning and enabled watching for changes": "Disabled periodic scanning and enabled watching for changes",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:",
"Disconnected": "Odpojené",
"Discovered": "Zistené",
"Discovery": "Zisťovanie",
@@ -83,6 +86,7 @@
"Editing {%path%}.": "Úprava {{path}}.",
"Enable NAT traversal": "Povoliť priechod NAT",
"Enable Relaying": "Povoliť prenosové uzly",
"Enabled": "Povolené",
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Vložte kladné číslo (napr. \"2,35\") a zvoľte jednotku. Percentá sa zobrazujú ako časť celkovej veľkosti disku.",
"Enter a non-privileged port number (1024 - 65535).": "Vložte číslo neprivilegovaného portu (1024-65535).",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Zadaj čiarkou oddelené (\"tcp://ip:port\", \"tcp://host:port\") adresy alebo \"dynamic\" na automatické zistenie adresy.",
@@ -185,7 +189,9 @@
"Pause": "Pozastaviť",
"Pause All": "Pozastaviť všetky",
"Paused": "Pozastavené",
"Periodic scan every": "Periodic scan every",
"Periodic scanning at given interval and disabled watching for changes": "Periodic scanning at given interval and disabled watching for changes",
"Periodic scanning at given interval and enabled watching for changes": "Periodic scanning at given interval and enabled watching for changes",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:",
"Please consult the release notes before performing a major upgrade.": "Pred spustením hlavnej aktualizácie si prosím prečítajte poznámky k vydaniu.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Zadajte prosím prihlasovanie meno a heslo v dialógovom okne nastavení.",
"Please wait": "Prosím čakajte",
+11 -5
View File
@@ -22,6 +22,7 @@
"Allowed Networks": "Tillåtna nätverk",
"Alphabetic": "Alfabetisk",
"An external command handles the versioning. It has to remove the file from the shared folder.": "Ett externt kommando hanterar versionshanteringen. Det måste ta bort filen från den delade mappen.",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "Ett externt kommando hanterar versionen. Det måste ta bort filen från den delade mappen. Om sökvägen till applikationen innehåller mellanslag bör den citeras.",
"An external command handles the versioning. It has to remove the file from the synced folder.": "Ett externt kommando sköter versionshanteringen. Den behöver ta bort filen från den synkroniserade mappen.",
"Anonymous Usage Reporting": "Anonym användarstatistiksrapportering",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Anonymt användningsrapportformat har ändrats. Vill du flytta till det nya formatet?",
@@ -65,7 +66,9 @@
"Device that last modified the item": "Enhet som senast ändrade objektet",
"Devices": "Enheter",
"Disabled": "Inaktiverad",
"Disabled periodic scanning": "Disabled periodic scanning",
"Disabled periodic scanning and disabled watching for changes": "Inaktiverad periodisk skanning och inaktiverad visning av ändringar",
"Disabled periodic scanning and enabled watching for changes": "Inaktiverad periodisk skanning och aktiverad visning av ändringar",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Inaktiverad periodisk skanning och misslyckad att ställa in visning av ändringar, försök igen varje 1m:",
"Disconnected": "Frånkopplad",
"Discovered": "Upptäckt",
"Discovery": "Annonsering",
@@ -83,6 +86,7 @@
"Editing {%path%}.": "Redigerar {{path}}.",
"Enable NAT traversal": "Aktivera NAT traversering",
"Enable Relaying": "Aktivera vidarebefordra",
"Enabled": "Aktiverad",
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Ange ett icke-negativt antal (t.ex., \"2.35\") och välj en enhet. Procenttalen är som en del av den totala diskstorleken.",
"Enter a non-privileged port number (1024 - 65535).": "Ange ett icke-privilegierat portnummer (1024 - 65535).",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Ange kommaseparerade (\"tcp://ip:port\", \"tcp://host:port\")-adresser eller ordet \"dynamic\" för att använda automatisk uppslagning.",
@@ -90,7 +94,7 @@
"Error": "Fel",
"External File Versioning": "Extern filversionshantering",
"Failed Items": "Misslyckade objekt",
"Failed to setup, retrying": "Failed to setup, retrying",
"Failed to setup, retrying": "Misslyckades med att ställa in, försök igen",
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Misslyckande med att ansluta till IPv6-servrar förväntas om ingen IPv6-anslutning finns.",
"File Pull Order": "Filhämtningsprioritering",
"File Versioning": "Filversionshantering",
@@ -185,7 +189,9 @@
"Pause": "Paus",
"Pause All": "Pausa alla",
"Paused": "Pausad",
"Periodic scan every": "Periodic scan every",
"Periodic scanning at given interval and disabled watching for changes": "Periodisk skanning i givet intervall och inaktiverad visning av ändringar",
"Periodic scanning at given interval and enabled watching for changes": "Periodisk skanning i givet intervall och aktiverad visning av ändringar",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Periodisk skanning i givet intervall och misslyckades med att ställa in visning av ändringar, försök igen varje 1m:",
"Please consult the release notes before performing a major upgrade.": "Läs igenom versionsnyheterna innan den stora uppgraderingen.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Ställ in ett grafiska gränssnittets användarautentisering och lösenord i inställningsdialogrutan.",
"Please wait": "Var god vänta",
@@ -208,7 +214,7 @@
"Rescan": "Skanna om",
"Rescan All": "Skanna om alla",
"Rescan Interval": "Återskanningsintervall",
"Rescans": "Rescans",
"Rescans": "Omskanningar",
"Restart": "Starta om",
"Restart Needed": "Omstart behövs",
"Restarting": "Startar om",
@@ -217,7 +223,7 @@
"Resume": "Återuppta",
"Resume All": "Återuppta alla",
"Reused": "Återanvänds",
"Running": "Running",
"Running": "Körs",
"Save": "Spara",
"Scan Time Remaining": "Återstående skanningstid",
"Scanning": "Skannar",
+8 -2
View File
@@ -22,6 +22,7 @@
"Allowed Networks": "Allowed Networks",
"Alphabetic": "Alfabetik",
"An external command handles the versioning. It has to remove the file from the shared folder.": "An external command handles the versioning. It has to remove the file from the shared folder.",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.",
"An external command handles the versioning. It has to remove the file from the synced folder.": "Sürümleme işlemini harici bir komut yürütüyor. Dosyayı eşzamanlama klasöründen kaldırmak zorunda.",
"Anonymous Usage Reporting": "Anonim Kullanım Raporlaması",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Anonymous usage report format has changed. Would you like to move to the new format?",
@@ -65,7 +66,9 @@
"Device that last modified the item": "Device that last modified the item",
"Devices": "Aygıtlar",
"Disabled": "Disabled",
"Disabled periodic scanning": "Disabled periodic scanning",
"Disabled periodic scanning and disabled watching for changes": "Disabled periodic scanning and disabled watching for changes",
"Disabled periodic scanning and enabled watching for changes": "Disabled periodic scanning and enabled watching for changes",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:",
"Disconnected": "Bağlantı Kesik",
"Discovered": "Keşfedildi",
"Discovery": "Keşif",
@@ -83,6 +86,7 @@
"Editing {%path%}.": "Editing {{path}}.",
"Enable NAT traversal": "NAT çaprazlamasına izin ver",
"Enable Relaying": "Enable Relaying",
"Enabled": "Enabled",
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.",
"Enter a non-privileged port number (1024 - 65535).": "Enter a non-privileged port number (1024 - 65535).",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Adreslerin kendiliğinden keşfedilebilmesi için ya adresleri virgülle ayırarak (\"tcp://ip:port\", \"tcp://host:port\") girin, ya da \"dynamic\" sözcüğünü girin.",
@@ -185,7 +189,9 @@
"Pause": "Duraklat",
"Pause All": "Tümünü Duraklat",
"Paused": "Duraklatıldı",
"Periodic scan every": "Periodic scan every",
"Periodic scanning at given interval and disabled watching for changes": "Periodic scanning at given interval and disabled watching for changes",
"Periodic scanning at given interval and enabled watching for changes": "Periodic scanning at given interval and enabled watching for changes",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:",
"Please consult the release notes before performing a major upgrade.": "Birincil bir yükseltme gerçekleştirmeden önce lütfen sürüm notlarını tetkik edin.",
"Please set a GUI Authentication User and Password in the Settings dialog.": "GUI üzerinden Kimlik Doğrulaması yapmak için Ayarlar penceresinde Kullanıcı ve Parola tanımlayın lütfen.",
"Please wait": "Lütfen Bekleyin",
+8 -2
View File
@@ -22,6 +22,7 @@
"Allowed Networks": "Дозволені мережі",
"Alphabetic": "За алфавітом",
"An external command handles the versioning. It has to remove the file from the shared folder.": "Зовнішня команда керування версіями. Вона має видалити файл із спільної директорії.",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.",
"An external command handles the versioning. It has to remove the file from the synced folder.": "Зовнішня команда керування версіями. Вона має видалити файл із директорії, що синхронізується.",
"Anonymous Usage Reporting": "Анонімна статистика використання",
"Anonymous usage report format has changed. Would you like to move to the new format?": "Змінився формат анонімного звіту про користування. Бажаєте перейти на новий формат?",
@@ -65,7 +66,9 @@
"Device that last modified the item": "Пристрій, що останнім змінив елемент",
"Devices": "Пристрої",
"Disabled": "Вимкнено",
"Disabled periodic scanning": "Disabled periodic scanning",
"Disabled periodic scanning and disabled watching for changes": "Disabled periodic scanning and disabled watching for changes",
"Disabled periodic scanning and enabled watching for changes": "Disabled periodic scanning and enabled watching for changes",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:",
"Disconnected": "З’єднання відсутнє",
"Discovered": "Виявлено",
"Discovery": "Сервери координації NAT",
@@ -83,6 +86,7 @@
"Editing {%path%}.": "Редагування {{path}}.",
"Enable NAT traversal": "Увімкнути NAT traversal",
"Enable Relaying": "Увімкнути ретрансляцію (relaying)",
"Enabled": "Enabled",
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "Введіть невід'ємне число (напр. \"2.35\") та виберіть пристрій. Проценти від загального дискового простору.",
"Enter a non-privileged port number (1024 - 65535).": "Введіть номер непривілейованого порту (1024 - 65535).",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Введіть розділені комою (\"tcp://ip:port\", \"tcp://host:port\") адреси або \"dynamic\" для автоматичного визначення адреси.",
@@ -185,7 +189,9 @@
"Pause": "Пауза",
"Pause All": "Призупинити все",
"Paused": "Призупинено",
"Periodic scan every": "Periodic scan every",
"Periodic scanning at given interval and disabled watching for changes": "Periodic scanning at given interval and disabled watching for changes",
"Periodic scanning at given interval and enabled watching for changes": "Periodic scanning at given interval and enabled watching for changes",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:",
"Please consult the release notes before performing a major upgrade.": "Будь ласка перегляньте примітки до випуску перед мажорним оновленням. ",
"Please set a GUI Authentication User and Password in the Settings dialog.": "Будь ласка, встановіть у налаштуваннях ім'я користувача та пароль до графічного інтерфейсу.",
"Please wait": "Будь ласка, зачекайте",
+9 -3
View File
@@ -22,7 +22,8 @@
"Allowed Networks": "允许的网络",
"Alphabetic": "字母顺序",
"An external command handles the versioning. It has to remove the file from the shared folder.": "使用外部命令接管版本控制。该命令必须自行从共享文件夹中删除该文件。",
"An external command handles the versioning. It has to remove the file from the synced folder.": "使用外部命令接管版本控制。该命令必须自行从同步文件夹中删除该文件。",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "外部命令接管版本控制。它需要从共享文件夹中删除该文件。如果此应用程序的路径包含空格,应该用半角引号括起来。",
"An external command handles the versioning. It has to remove the file from the synced folder.": "外部命令接管了版本控制。它需要从同步文件夹中删除该文件。",
"Anonymous Usage Reporting": "匿名使用报告",
"Anonymous usage report format has changed. Would you like to move to the new format?": "匿名使用情况的报告格式已经变更。是否要迁移到新的格式?",
"Any devices configured on an introducer device will be added to this device as well.": "在中介设备上添加的任何“远程设备”,也会被自动添加到本机的“远程设备”列表。",
@@ -65,7 +66,9 @@
"Device that last modified the item": "最近修改该项的设备",
"Devices": "设备",
"Disabled": "已禁用",
"Disabled periodic scanning": "已禁用定期扫描",
"Disabled periodic scanning and disabled watching for changes": "已禁用定期扫描和更改监视",
"Disabled periodic scanning and enabled watching for changes": "已禁用定期扫描并启用更改监视",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "已禁用定期扫描但设置更改监视失败,正在以每 1m 一次重试:",
"Disconnected": "连接已断开",
"Discovered": "已发现",
"Discovery": "设备发现",
@@ -83,6 +86,7 @@
"Editing {%path%}.": "正在编辑 {{path}}。",
"Enable NAT traversal": "启用 NAT 遍历",
"Enable Relaying": "开启中继",
"Enabled": "已启用",
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "输入一个非负数(例如“2.35”)并选择单位。%表示占磁盘总容量的百分比。",
"Enter a non-privileged port number (1024 - 65535).": "输入一个非特权的端口号 (1024 - 65535)。",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "输入以半角逗号分隔的 (\"tcp://ip:port\", \"tcp://host:port\") 设备地址列表,或者输入 \"dynamic\" 以自动发现设备地址。",
@@ -185,7 +189,9 @@
"Pause": "暂停",
"Pause All": "全部暂停",
"Paused": "已暂停",
"Periodic scan every": "定期扫描每",
"Periodic scanning at given interval and disabled watching for changes": "正以给定的间隔定期扫描并已禁用更改监视",
"Periodic scanning at given interval and enabled watching for changes": "正以给定的间隔定期扫描并已启用更改监视",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "正以给定的间隔定期扫描但设置更改监视失败,正在以每 1m 一次重试:",
"Please consult the release notes before performing a major upgrade.": "请在进行重大更新前查看发布说明。",
"Please set a GUI Authentication User and Password in the Settings dialog.": "请在设置对话框中设置 GUI 验证用户及其密码。",
"Please wait": "请稍候",
+12 -6
View File
@@ -22,6 +22,7 @@
"Allowed Networks": "允許的網路",
"Alphabetic": "字母順序",
"An external command handles the versioning. It has to remove the file from the shared folder.": "處理版本的外部指令。其必須從資料夾中刪除檔案。",
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.",
"An external command handles the versioning. It has to remove the file from the synced folder.": "處理版本的外部指令。其必須從資料夾中刪除檔案。",
"Anonymous Usage Reporting": "匿名的使用資訊回報",
"Anonymous usage report format has changed. Would you like to move to the new format?": "匿名的使用資訊回報格式已經改變,你想要移至新格式嗎?",
@@ -33,7 +34,7 @@
"Automatic upgrade now offers the choice between stable releases and release candidates.": "自動更新目前有穩定發行版及發行候選版可供選擇。",
"Automatic upgrades": "自動升級",
"Automatically create or share folders that this device advertises at the default path.": "自動在預設資料夾路徑建立或分享該裝置推薦的資料夾。",
"Available debug logging facilities:": "Available debug logging facilities:",
"Available debug logging facilities:": "可用的除錯日誌工具:",
"Be careful!": "請小心!",
"Bugs": "程式錯誤",
"CPU Utilization": "CPU 使用",
@@ -65,7 +66,9 @@
"Device that last modified the item": "前次修改裝置",
"Devices": "裝置",
"Disabled": "關閉",
"Disabled periodic scanning": "Disabled periodic scanning",
"Disabled periodic scanning and disabled watching for changes": "已關閉定期掃描及觀察變動",
"Disabled periodic scanning and enabled watching for changes": "已關閉定期掃描及啟用觀察變動",
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "已關閉定期掃描,無法設定觀察變動,每 1 分鐘重試:",
"Disconnected": "斷線",
"Discovered": "已發現",
"Discovery": "探索",
@@ -83,6 +86,7 @@
"Editing {%path%}.": "正在編輯 {{path}} 。",
"Enable NAT traversal": "啟用 NAT 穿透",
"Enable Relaying": "啟用中繼",
"Enabled": "啟用",
"Enter a non-negative number (e.g., \"2.35\") and select a unit. Percentages are as part of the total disk size.": "請輸入一非負數(如:\"2.35\")並選擇一個單位。百分比表示佔用磁碟容量的大小。",
"Enter a non-privileged port number (1024 - 65535).": "輸入一個非特權通訊埠號 (1024 - 65535)。",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "輸入以半形逗號區隔的位址 (\"tcp://ip:port\", \"tcp://host:port\"),或輸入 \"dynamic\" 以進行位址的自動探索",
@@ -90,7 +94,7 @@
"Error": "錯誤",
"External File Versioning": "外部檔案版本控制",
"Failed Items": "失敗的項目",
"Failed to setup, retrying": "Failed to setup, retrying",
"Failed to setup, retrying": "無法設定,正在重試",
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "若未配置 IPv6,則無法連接 IPv6 伺服器係屬正常。",
"File Pull Order": "提取檔案的順序",
"File Versioning": "檔案版本控制",
@@ -185,7 +189,9 @@
"Pause": "暫停",
"Pause All": "全部暫停",
"Paused": "暫停",
"Periodic scan every": "Periodic scan every",
"Periodic scanning at given interval and disabled watching for changes": "在一定的時間間隔,定期掃描及關閉觀察變動",
"Periodic scanning at given interval and enabled watching for changes": "在一定的時間間隔,定期掃描及啟用觀察變動",
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "在一定的時間間隔,定期掃描,無法設定觀察變動,每 1 分鐘重試:",
"Please consult the release notes before performing a major upgrade.": "執行重大升級前請先參閱版本資訊。",
"Please set a GUI Authentication User and Password in the Settings dialog.": "請在設定對話方塊內設置 GUI 使用者認證名稱及密碼。",
"Please wait": "請稍後",
@@ -208,7 +214,7 @@
"Rescan": "重新掃描",
"Rescan All": "全部重新掃描",
"Rescan Interval": "重新掃描間隔",
"Rescans": "Rescans",
"Rescans": "重新掃描",
"Restart": "重新啟動",
"Restart Needed": "需要重新啟動",
"Restarting": "正在重新啟動",
@@ -217,7 +223,7 @@
"Resume": "繼續",
"Resume All": "全部繼續",
"Reused": "重用",
"Running": "Running",
"Running": "正在執行",
"Save": "儲存",
"Scan Time Remaining": "剩餘掃描時間",
"Scanning": "正在掃描",
@@ -5,12 +5,12 @@
</div>
<div ng-if="sizeOf(remoteNeed) > 0">
<div class="panel panel-default" ng-repeat="folder in remoteNeedFolders" ng-if="remoteNeed[folder] && remoteNeed[folder].files.length > 0">
<button class="btn panel-heading" data-toggle="collapse" data-target="#remoteNeed-{{folder}}" aria-expanded="false">
<button class="btn panel-heading" data-toggle="collapse" data-target="#remoteNeed-{{$index}}" aria-expanded="false">
<h4 class="panel-title">
<span>{{folderLabel(folder)}}</span>
</h4>
</button>
<div id="remoteNeed-{{folder}}" class="panel-collapse collapse">
<div id="remoteNeed-{{$index}}" class="panel-collapse" ng-class="{collapse: sizeOf(remoteNeedFolders) > 1}">
<div class="panel-body">
<table class="table table-striped">
<thead>
+15 -1
View File
@@ -32,7 +32,7 @@ import (
const (
OldestHandledVersion = 10
CurrentVersion = 26
CurrentVersion = 27
MaxRescanIntervalS = 365 * 24 * 60 * 60
)
@@ -312,6 +312,9 @@ func (cfg *Configuration) clean() error {
if cfg.Version == 25 {
convertV25V26(cfg)
}
if cfg.Version == 26 {
convertV26V27(cfg)
}
// Build a list of available devices
existingDevices := make(map[protocol.DeviceID]bool)
@@ -371,6 +374,17 @@ func (cfg *Configuration) clean() error {
return nil
}
func convertV26V27(cfg *Configuration) {
for i := range cfg.Folders {
f := &cfg.Folders[i]
if f.DeprecatedPullers != 0 {
f.PullerMaxPendingKiB = 128 * f.DeprecatedPullers
f.DeprecatedPullers = 0
}
}
cfg.Version = 27
}
func convertV25V26(cfg *Configuration) {
// triggers database update
cfg.Version = 26
-1
View File
@@ -107,7 +107,6 @@ func TestDeviceConfig(t *testing.T) {
FSWatcherEnabled: false,
FSWatcherDelayS: 10,
Copiers: 0,
Pullers: 0,
Hashers: 0,
AutoNormalize: true,
MinDiskFree: Size{1, "%"},
+2 -1
View File
@@ -40,7 +40,7 @@ type FolderConfiguration struct {
MinDiskFree Size `xml:"minDiskFree" json:"minDiskFree"`
Versioning VersioningConfiguration `xml:"versioning" json:"versioning"`
Copiers int `xml:"copiers" json:"copiers"` // This defines how many files are handled concurrently.
Pullers int `xml:"pullers" json:"pullers"` // Defines how many blocks are fetched at the same time, possibly between separate copier routines.
PullerMaxPendingKiB int `xml:"pullerMaxPendingKiB" json:"pullerMaxPendingKiB"`
Hashers int `xml:"hashers" json:"hashers"` // Less than one sets the value to the number of cores. These are CPU bound due to hashing.
Order PullOrder `xml:"order" json:"order"`
IgnoreDelete bool `xml:"ignoreDelete" json:"ignoreDelete"`
@@ -57,6 +57,7 @@ type FolderConfiguration struct {
DeprecatedReadOnly bool `xml:"ro,attr,omitempty" json:"-"`
DeprecatedMinDiskFreePct float64 `xml:"minDiskFreePct,omitempty" json:"-"`
DeprecatedPullers int `xml:"pullers,omitempty" json:"-"`
}
type FolderDeviceConfiguration struct {
+16
View File
@@ -0,0 +1,16 @@
<configuration version="27">
<folder id="test" path="testdata" type="readonly" ignorePerms="false" rescanIntervalS="600" fsNotifications="false" notifyDelayS="10" autoNormalize="true">
<filesystemType>basic</filesystemType>
<device id="AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR"></device>
<device id="P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2"></device>
<minDiskFree unit="%">1</minDiskFree>
<maxConflicts>-1</maxConflicts>
<fsync>true</fsync>
</folder>
<device id="AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR" name="node one" compression="metadata">
<address>tcp://a</address>
</device>
<device id="P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2" name="node two" compression="metadata">
<address>tcp://b</address>
</device>
</configuration>
+7 -6
View File
@@ -341,19 +341,20 @@ func (s *Service) connect() {
l.Debugln("Reconnect loop for", deviceID, addrs)
seen = append(seen, addrs...)
dialTargets := make([]dialTarget, 0)
for _, addr := range addrs {
nextDialAt, ok := nextDial[addr]
// Use a special key that is more than just the address, as you might have two devices connected to the same relay
nextDialKey := deviceID.String() + "/" + addr
seen = append(seen, nextDialKey)
nextDialAt, ok := nextDial[nextDialKey]
if ok && initialRampup >= sleep && nextDialAt.After(now) {
l.Debugf("Not dialing %v as sleep is %v, next dial is at %s and current time is %s", addr, sleep, nextDialAt, now)
l.Debugf("Not dialing %s via %v as sleep is %v, next dial is at %s and current time is %s", deviceID, addr, sleep, nextDialAt, now)
continue
}
// If we fail at any step before actually getting the dialer
// retry in a minute
nextDial[addr] = now.Add(time.Minute)
nextDial[nextDialKey] = now.Add(time.Minute)
uri, err := url.Parse(addr)
if err != nil {
@@ -391,7 +392,7 @@ func (s *Service) connect() {
}
dialer := dialerFactory.New(s.cfg, s.tlsCfg)
nextDial[addr] = now.Add(dialer.RedialFrequency())
nextDial[nextDialKey] = now.Add(dialer.RedialFrequency())
// For LAN addresses, increase the priority so that we
// try these first.
+3
View File
@@ -15,6 +15,8 @@ import (
"github.com/syndtr/goleveldb/leveldb/opt"
)
const dbVersion = 1
const (
KeyTypeDevice = iota
KeyTypeGlobal
@@ -26,6 +28,7 @@ const (
KeyTypeDeviceIdx
KeyTypeIndexID
KeyTypeFolderMeta
KeyTypeMiscData
)
func (l VersionList) String() string {
+89 -105
View File
@@ -83,6 +83,20 @@ func newDBInstance(db *leveldb.DB, location string) *Instance {
return i
}
// UpdateSchema does transitions to the current db version if necessary
func (db *Instance) UpdateSchema() {
miscDB := NewNamespacedKV(db, string(KeyTypeMiscData))
prevVersion, _ := miscDB.Int64("dbVersion")
if prevVersion == 0 {
db.updateSchema0to1()
}
if prevVersion != dbVersion {
miscDB.PutInt64("dbVersion", dbVersion)
}
}
// Committed returns the number of items committed to the database since startup
func (db *Instance) Committed() int64 {
return atomic.LoadInt64(&db.committed)
@@ -324,7 +338,7 @@ func (db *Instance) availability(folder, file []byte) []protocol.DeviceID {
return devices
}
func (db *Instance) withNeed(folder, device []byte, truncate bool, needAllInvalid bool, fn Iterator) {
func (db *Instance) withNeed(folder, device []byte, truncate bool, fn Iterator) {
t := db.newReadOnlyTransaction()
defer t.close()
@@ -351,12 +365,6 @@ func (db *Instance) withNeed(folder, device []byte, truncate bool, needAllInvali
if bytes.Equal(v.Device, device) {
have = true
haveFileVersion = v
// We need invalid files regardless of version when
// ignore patterns changed
if v.Invalid && needAllInvalid {
need = true
break
}
// XXX: This marks Concurrent (i.e. conflicting) changes as
// needs. Maybe we should do that, but it needs special
// handling in the puller.
@@ -531,21 +539,39 @@ func (db *Instance) checkGlobals(folder []byte, meta *metadataTracker) {
l.Debugf("db check completed for %q", folder)
}
// ConvertSymlinkTypes should be run once only on an old database. It
// changes SYMLINK_FILE and SYMLINK_DIRECTORY types to the current SYMLINK
// type (previously SYMLINK_UNKNOWN). It does this for all devices, both
// local and remote, and does not reset delta indexes. It shouldn't really
// matter what the symlink type is, but this cleans it up for a possible
// future when SYMLINK_FILE and SYMLINK_DIRECTORY are no longer understood.
func (db *Instance) ConvertSymlinkTypes() {
func (db *Instance) updateSchema0to1() {
t := db.newReadWriteTransaction()
defer t.close()
dbi := t.NewIterator(util.BytesPrefix([]byte{KeyTypeDevice}), nil)
defer dbi.Release()
conv := 0
symlinkConv := 0
changedFolders := make(map[string]struct{})
ignAdded := 0
meta := newMetadataTracker() // dummy metadata tracker
for dbi.Next() {
folder := db.deviceKeyFolder(dbi.Key())
device := db.deviceKeyDevice(dbi.Key())
name := string(db.deviceKeyName(dbi.Key()))
// Remove files with absolute path (see #4799)
if strings.HasPrefix(name, "/") {
if _, ok := changedFolders[string(folder)]; !ok {
changedFolders[string(folder)] = struct{}{}
}
t.removeFromGlobal(folder, device, nil, nil)
t.Delete(dbi.Key())
t.checkFlush()
continue
}
// Change SYMLINK_FILE and SYMLINK_DIRECTORY types to the current SYMLINK
// type (previously SYMLINK_UNKNOWN). It does this for all devices, both
// local and remote, and does not reset delta indexes. It shouldn't really
// matter what the symlink type is, but this cleans it up for a possible
// future when SYMLINK_FILE and SYMLINK_DIRECTORY are no longer understood.
var f protocol.FileInfo
if err := f.Unmarshal(dbi.Value()); err != nil {
// probably can't happen
@@ -559,99 +585,25 @@ func (db *Instance) ConvertSymlinkTypes() {
}
t.Put(dbi.Key(), bs)
t.checkFlush()
conv++
symlinkConv++
}
}
l.Infof("Updated symlink type for %d index entries", conv)
}
// AddInvalidToGlobal searches for invalid files and adds them to the global list.
// Invalid files exist in the db if they once were not ignored and subsequently
// ignored. In the new system this is still valid, but invalid files must also be
// in the global list such that they cannot be mistaken for missing files.
func (db *Instance) AddInvalidToGlobal(folder, device []byte) int {
t := db.newReadWriteTransaction()
defer t.close()
dbi := t.NewIterator(util.BytesPrefix(db.deviceKey(folder, device, nil)[:keyPrefixLen+keyFolderLen+keyDeviceLen]), nil)
defer dbi.Release()
changed := 0
for dbi.Next() {
var file protocol.FileInfo
if err := file.Unmarshal(dbi.Value()); err != nil {
// probably can't happen
continue
}
if file.Invalid {
changed++
l.Debugf("add invalid to global; folder=%q device=%v file=%q version=%v", folder, protocol.DeviceIDFromBytes(device), file.Name, file.Version)
// this is an adapted version of readWriteTransaction.updateGlobal
name := []byte(file.Name)
gk := t.db.globalKey(folder, name)
var fl VersionList
if svl, err := t.Get(gk, nil); err == nil {
fl.Unmarshal(svl) // skip error, range handles success case
}
nv := FileVersion{
Device: device,
Version: file.Version,
Invalid: file.Invalid,
}
inserted := false
// Find a position in the list to insert this file. The file at the front
// of the list is the newer, the "global".
insert:
for i := range fl.Versions {
switch fl.Versions[i].Version.Compare(file.Version) {
case protocol.Equal:
// Invalid files should go after a valid file of equal version
if nv.Invalid {
continue insert
}
fallthrough
case protocol.Lesser:
// The version at this point in the list is equal to or lesser
// ("older") than us. We insert ourselves in front of it.
fl.Versions = insertVersion(fl.Versions, i, nv)
inserted = true
break insert
case protocol.ConcurrentLesser, protocol.ConcurrentGreater:
// The version at this point is in conflict with us. We must pull
// the actual file metadata to determine who wins. If we win, we
// insert ourselves in front of the loser here. (The "Lesser" and
// "Greater" in the condition above is just based on the device
// IDs in the version vector, which is not the only thing we use
// to determine the winner.)
//
// A surprise missing file entry here is counted as a win for us.
of, ok := t.getFile(folder, fl.Versions[i].Device, name)
if !ok || file.WinsConflict(of) {
fl.Versions = insertVersion(fl.Versions, i, nv)
inserted = true
break insert
}
// Add invalid files to global list
if f.Invalid {
if t.updateGlobal(folder, device, f, meta) {
if _, ok := changedFolders[string(folder)]; !ok {
changedFolders[string(folder)] = struct{}{}
}
ignAdded++
}
if !inserted {
// We didn't find a position for an insert above, so append to the end.
fl.Versions = append(fl.Versions, nv)
}
t.Put(gk, mustMarshal(&fl))
}
}
return changed
for folder := range changedFolders {
db.dropFolderMeta([]byte(folder))
}
l.Infof("Updated symlink type for %d index entries and added %d invalid files to global list", symlinkConv, ignAdded)
}
// deviceKey returns a byte slice encoding the following information:
@@ -751,6 +703,15 @@ func (db *Instance) indexIDKey(device, folder []byte) []byte {
return k
}
func (db *Instance) indexIDDevice(key []byte) []byte {
device, ok := db.deviceIdx.Val(binary.BigEndian.Uint32(key[keyPrefixLen:]))
if !ok {
// uuh ...
return nil
}
return device
}
func (db *Instance) mtimesKey(folder []byte) []byte {
prefix := make([]byte, 5) // key type + 4 bytes folder idx number
prefix[0] = KeyTypeVirtualMtime
@@ -765,10 +726,33 @@ func (db *Instance) folderMetaKey(folder []byte) []byte {
return prefix
}
// DropDeltaIndexIDs removes all index IDs from the database. This will
// cause a full index transmission on the next connection.
func (db *Instance) DropDeltaIndexIDs() {
db.dropPrefix([]byte{KeyTypeIndexID})
// DropLocalDeltaIndexIDs removes all index IDs for the local device ID from
// the database. This will cause a full index transmission on the next
// connection.
func (db *Instance) DropLocalDeltaIndexIDs() {
db.dropDeltaIndexIDs(true)
}
// DropRemoteDeltaIndexIDs removes all index IDs for the other devices than
// the local one from the database. This will cause them to send us a full
// index on the next connection.
func (db *Instance) DropRemoteDeltaIndexIDs() {
db.dropDeltaIndexIDs(false)
}
func (db *Instance) dropDeltaIndexIDs(local bool) {
t := db.newReadWriteTransaction()
defer t.close()
dbi := t.NewIterator(util.BytesPrefix([]byte{KeyTypeIndexID}), nil)
defer dbi.Release()
for dbi.Next() {
device := db.indexIDDevice(dbi.Key())
if bytes.Equal(device, protocol.LocalDeviceID[:]) == local {
t.Delete(dbi.Key())
}
}
}
func (db *Instance) dropMtimes(folder []byte) {
+90
View File
@@ -9,6 +9,8 @@ package db
import (
"bytes"
"testing"
"github.com/syncthing/syncthing/lib/protocol"
)
func TestDeviceKey(t *testing.T) {
@@ -62,3 +64,91 @@ func TestGlobalKey(t *testing.T) {
t.Error("should not have been found")
}
}
func TestDropIndexIDs(t *testing.T) {
db := OpenMemory()
d1 := []byte("device67890123456789012345678901")
d2 := []byte("device12345678901234567890123456")
// Set some index IDs
db.setIndexID(protocol.LocalDeviceID[:], []byte("foo"), 1)
db.setIndexID(protocol.LocalDeviceID[:], []byte("bar"), 2)
db.setIndexID(d1, []byte("foo"), 3)
db.setIndexID(d1, []byte("bar"), 4)
db.setIndexID(d2, []byte("foo"), 5)
db.setIndexID(d2, []byte("bar"), 6)
// Verify them
if db.getIndexID(protocol.LocalDeviceID[:], []byte("foo")) != 1 {
t.Fatal("fail local 1")
}
if db.getIndexID(protocol.LocalDeviceID[:], []byte("bar")) != 2 {
t.Fatal("fail local 2")
}
if db.getIndexID(d1, []byte("foo")) != 3 {
t.Fatal("fail remote 1")
}
if db.getIndexID(d1, []byte("bar")) != 4 {
t.Fatal("fail remote 2")
}
if db.getIndexID(d2, []byte("foo")) != 5 {
t.Fatal("fail remote 3")
}
if db.getIndexID(d2, []byte("bar")) != 6 {
t.Fatal("fail remote 4")
}
// Drop the local ones, verify only they got dropped
db.DropLocalDeltaIndexIDs()
if db.getIndexID(protocol.LocalDeviceID[:], []byte("foo")) != 0 {
t.Fatal("fail local 1")
}
if db.getIndexID(protocol.LocalDeviceID[:], []byte("bar")) != 0 {
t.Fatal("fail local 2")
}
if db.getIndexID(d1, []byte("foo")) != 3 {
t.Fatal("fail remote 1")
}
if db.getIndexID(d1, []byte("bar")) != 4 {
t.Fatal("fail remote 2")
}
if db.getIndexID(d2, []byte("foo")) != 5 {
t.Fatal("fail remote 3")
}
if db.getIndexID(d2, []byte("bar")) != 6 {
t.Fatal("fail remote 4")
}
// Set local ones again
db.setIndexID(protocol.LocalDeviceID[:], []byte("foo"), 1)
db.setIndexID(protocol.LocalDeviceID[:], []byte("bar"), 2)
// Drop the remote ones, verify only they got dropped
db.DropRemoteDeltaIndexIDs()
if db.getIndexID(protocol.LocalDeviceID[:], []byte("foo")) != 1 {
t.Fatal("fail local 1")
}
if db.getIndexID(protocol.LocalDeviceID[:], []byte("bar")) != 2 {
t.Fatal("fail local 2")
}
if db.getIndexID(d1, []byte("foo")) != 0 {
t.Fatal("fail remote 1")
}
if db.getIndexID(d1, []byte("bar")) != 0 {
t.Fatal("fail remote 2")
}
if db.getIndexID(d2, []byte("foo")) != 0 {
t.Fatal("fail remote 3")
}
if db.getIndexID(d2, []byte("bar")) != 0 {
t.Fatal("fail remote 4")
}
}
+18 -2
View File
@@ -134,8 +134,24 @@ func (m *metadataTracker) removeFile(dev protocol.DeviceID, f FileIntf) {
}
cp.Bytes -= f.FileSize()
if cp.Deleted < 0 || cp.Files < 0 || cp.Directories < 0 || cp.Symlinks < 0 {
panic("bug: removed more than added")
// If we've run into an impossible situation, correct it for now and set
// the created timestamp to zero. Next time we start up the metadata
// will be seen as infinitely old and recalculated from scratch.
if cp.Deleted < 0 {
cp.Deleted = 0
m.counts.Created = 0
}
if cp.Files < 0 {
cp.Files = 0
m.counts.Created = 0
}
if cp.Directories < 0 {
cp.Directories = 0
m.counts.Created = 0
}
if cp.Symlinks < 0 {
cp.Symlinks = 0
m.counts.Created = 0
}
m.mut.Unlock()
+5 -17
View File
@@ -164,24 +164,12 @@ func (s *FileSet) Update(device protocol.DeviceID, fs []protocol.FileInfo) {
func (s *FileSet) WithNeed(device protocol.DeviceID, fn Iterator) {
l.Debugf("%s WithNeed(%v)", s.folder, device)
s.db.withNeed([]byte(s.folder), device[:], false, false, nativeFileIterator(fn))
s.db.withNeed([]byte(s.folder), device[:], false, nativeFileIterator(fn))
}
func (s *FileSet) WithNeedTruncated(device protocol.DeviceID, fn Iterator) {
l.Debugf("%s WithNeedTruncated(%v)", s.folder, device)
s.db.withNeed([]byte(s.folder), device[:], true, false, nativeFileIterator(fn))
}
// WithNeedOrInvalid considers all invalid files as needed, regardless of their version
// (e.g. for pulling when ignore patterns changed)
func (s *FileSet) WithNeedOrInvalid(device protocol.DeviceID, fn Iterator) {
l.Debugf("%s WithNeedExcludingInvalid(%v)", s.folder, device)
s.db.withNeed([]byte(s.folder), device[:], false, true, nativeFileIterator(fn))
}
func (s *FileSet) WithNeedOrInvalidTruncated(device protocol.DeviceID, fn Iterator) {
l.Debugf("%s WithNeedExcludingInvalidTruncated(%v)", s.folder, device)
s.db.withNeed([]byte(s.folder), device[:], true, true, nativeFileIterator(fn))
s.db.withNeed([]byte(s.folder), device[:], true, nativeFileIterator(fn))
}
func (s *FileSet) WithHave(device protocol.DeviceID, fn Iterator) {
@@ -194,9 +182,9 @@ func (s *FileSet) WithHaveTruncated(device protocol.DeviceID, fn Iterator) {
s.db.withHave([]byte(s.folder), device[:], nil, true, nativeFileIterator(fn))
}
func (s *FileSet) WithPrefixedHave(device protocol.DeviceID, prefix string, fn Iterator) {
l.Debugf("%s WithPrefixedHave(%v)", s.folder, device)
s.db.withHave([]byte(s.folder), device[:], []byte(osutil.NormalizedFilename(prefix)), false, nativeFileIterator(fn))
func (s *FileSet) WithPrefixedHaveTruncated(device protocol.DeviceID, prefix string, fn Iterator) {
l.Debugf("%s WithPrefixedHaveTruncated(%v)", s.folder, device)
s.db.withHave([]byte(s.folder), device[:], []byte(osutil.NormalizedFilename(prefix)), true, nativeFileIterator(fn))
}
func (s *FileSet) WithGlobal(fn Iterator) {
l.Debugf("%s WithGlobal()", s.folder)
+17 -22
View File
@@ -88,28 +88,10 @@ func (f *BasicFilesystem) rooted(rel string) (string, error) {
expectedPrefix += pathSep
}
// The relative path should be clean from internal dotdots and similar
// funkyness.
rel = filepath.FromSlash(rel)
if filepath.Clean(rel) != rel {
return "", ErrInvalidFilename
}
// It is not acceptable to attempt to traverse upwards.
switch rel {
case "..", pathSep:
return "", ErrNotRelative
}
if strings.HasPrefix(rel, ".."+pathSep) {
return "", ErrNotRelative
}
if strings.HasPrefix(rel, pathSep+pathSep) {
// The relative path may pretend to be an absolute path within the
// root, but the double path separator on Windows implies something
// else. It would get cleaned by the Join below, but it's out of
// spec anyway.
return "", ErrNotRelative
var err error
rel, err = Canonicalize(rel)
if err != nil {
return "", err
}
// The supposedly correct path is the one filepath.Join will return, as
@@ -154,6 +136,19 @@ func (f *BasicFilesystem) Mkdir(name string, perm FileMode) error {
return os.Mkdir(name, os.FileMode(perm))
}
// MkdirAll creates a directory named path, along with any necessary parents,
// and returns nil, or else returns an error.
// The permission bits perm are used for all directories that MkdirAll creates.
// If path is already a directory, MkdirAll does nothing and returns nil.
func (f *BasicFilesystem) MkdirAll(path string, perm FileMode) error {
path, err := f.rooted(path)
if err != nil {
return err
}
return f.mkdirAll(path, os.FileMode(perm))
}
func (f *BasicFilesystem) Lstat(name string) (FileInfo, error) {
name, err := f.rooted(name)
if err != nil {
+17 -20
View File
@@ -364,17 +364,17 @@ func TestRooted(t *testing.T) {
{"baz/foo/", "bar/baz", "baz/foo/bar/baz", true},
{"baz/foo/", "/bar/baz", "baz/foo/bar/baz", true},
// Not escape attempts, but oddly formatted relative paths. Disallowed.
{"foo", "./bar", "", false},
{"baz/foo", "./bar", "", false},
{"foo", "./bar/baz", "", false},
{"baz/foo", "./bar/baz", "", false},
{"baz/foo", "bar/../baz", "", false},
{"baz/foo", "/bar/../baz", "", false},
{"baz/foo", "./bar/../baz", "", false},
{"baz/foo", "bar/../baz", "", false},
{"baz/foo", "/bar/../baz", "", false},
{"baz/foo", "./bar/../baz", "", false},
// Not escape attempts, but oddly formatted relative paths.
{"foo", "", "foo/", true},
{"foo", "/", "foo/", true},
{"foo", "/..", "foo/", true},
{"foo", "./bar", "foo/bar", true},
{"baz/foo", "./bar", "baz/foo/bar", true},
{"foo", "./bar/baz", "foo/bar/baz", true},
{"baz/foo", "./bar/baz", "baz/foo/bar/baz", true},
{"baz/foo", "bar/../baz", "baz/foo/baz", true},
{"baz/foo", "/bar/../baz", "baz/foo/baz", true},
{"baz/foo", "./bar/../baz", "baz/foo/baz", true},
// Results in an allowed path, but does it by probing. Disallowed.
{"foo", "../foo", "", false},
@@ -385,10 +385,7 @@ func TestRooted(t *testing.T) {
{"baz/foo", "bar/../../../baz/foo/bar", "", false},
// Escape attempts.
{"foo", "", "", false},
{"foo", "/", "", false},
{"foo", "..", "", false},
{"foo", "/..", "", false},
{"foo", "../", "", false},
{"foo", "../bar", "", false},
{"foo", "../foobar", "", false},
@@ -413,8 +410,8 @@ func TestRooted(t *testing.T) {
{"/", "/foo", "/foo", true},
{"/", "../foo", "", false},
{"/", "..", "", false},
{"/", "/", "", false},
{"/", "", "", false},
{"/", "/", "/", true},
{"/", "", "/", true},
// special case for filesystems to be able to MkdirAll('.') for example
{"/", ".", "/", true},
@@ -427,11 +424,11 @@ func TestRooted(t *testing.T) {
{`c:\`, `\foo`, `c:\foo`, true},
{`\\?\c:\`, `\foo`, `\\?\c:\foo`, true},
{`c:\`, `\\foo`, ``, false},
{`c:\`, ``, ``, false},
{`c:\`, `\`, ``, false},
{`c:\`, ``, `c:\`, true},
{`c:\`, `\`, `c:\`, true},
{`\\?\c:\`, `\\foo`, ``, false},
{`\\?\c:\`, ``, ``, false},
{`\\?\c:\`, `\`, ``, false},
{`\\?\c:\`, ``, `\\?\c:\`, true},
{`\\?\c:\`, `\`, `\\?\c:\`, true},
// makes no sense, but will be treated simply as a bad filename
{`c:\foo`, `d:\bar`, `c:\foo\d:\bar`, true},
+2 -6
View File
@@ -30,12 +30,8 @@ func (f *BasicFilesystem) ReadSymlink(name string) (string, error) {
return os.Readlink(name)
}
func (f *BasicFilesystem) MkdirAll(name string, perm FileMode) error {
name, err := f.rooted(name)
if err != nil {
return err
}
return os.MkdirAll(name, os.FileMode(perm))
func (f *BasicFilesystem) mkdirAll(path string, perm os.FileMode) error {
return os.MkdirAll(path, perm)
}
// Unhide is a noop on unix, as unhiding files requires renaming them.
+1 -1
View File
@@ -13,7 +13,7 @@ import (
"errors"
"path/filepath"
"github.com/zillode/notify"
"github.com/Zillode/notify"
)
// Notify does not block on sending to channel, so the channel must be buffered.
+1 -1
View File
@@ -8,7 +8,7 @@
package fs
import "github.com/zillode/notify"
import "github.com/Zillode/notify"
const (
subEventMask = notify.Create | notify.FileModified | notify.FileRenameFrom | notify.FileDelete | notify.FileRenameTo
+1 -1
View File
@@ -8,7 +8,7 @@
package fs
import "github.com/zillode/notify"
import "github.com/Zillode/notify"
const (
subEventMask = notify.InCreate | notify.InMovedTo | notify.InDelete | notify.InDeleteSelf | notify.InModify | notify.InMovedFrom | notify.InMoveSelf
+1 -1
View File
@@ -8,7 +8,7 @@
package fs
import "github.com/zillode/notify"
import "github.com/Zillode/notify"
const (
subEventMask = notify.NoteDelete | notify.NoteWrite | notify.NoteRename
+1 -1
View File
@@ -12,7 +12,7 @@
package fs
import "github.com/zillode/notify"
import "github.com/Zillode/notify"
const (
subEventMask = notify.All
+1 -1
View File
@@ -8,7 +8,7 @@
package fs
import "github.com/zillode/notify"
import "github.com/Zillode/notify"
const (
subEventMask = notify.FileNotifyChangeFileName | notify.FileNotifyChangeDirName | notify.FileNotifyChangeSize | notify.FileNotifyChangeCreation
+1 -1
View File
@@ -18,7 +18,7 @@ import (
"testing"
"time"
"github.com/zillode/notify"
"github.com/Zillode/notify"
)
func TestMain(m *testing.M) {
+1 -14
View File
@@ -32,19 +32,6 @@ func (BasicFilesystem) CreateSymlink(target, name string) error {
return errNotSupported
}
// MkdirAll creates a directory named path, along with any necessary parents,
// and returns nil, or else returns an error.
// The permission bits perm are used for all directories that MkdirAll creates.
// If path is already a directory, MkdirAll does nothing and returns nil.
func (f *BasicFilesystem) MkdirAll(path string, perm FileMode) error {
path, err := f.rooted(path)
if err != nil {
return err
}
return f.mkdirAll(path, os.FileMode(perm))
}
// Required due to https://github.com/golang/go/issues/10900
func (f *BasicFilesystem) mkdirAll(path string, perm os.FileMode) error {
// Fast path: if we can tell whether path is a directory or file, stop with success or error.
@@ -75,7 +62,7 @@ func (f *BasicFilesystem) mkdirAll(path string, perm os.FileMode) error {
// Create parent
parent := path[0 : j-1]
if parent != filepath.VolumeName(parent) {
err = os.MkdirAll(parent, perm)
err = f.mkdirAll(parent, perm)
if err != nil {
return err
}
+36
View File
@@ -198,3 +198,39 @@ func IsInternal(file string) bool {
}
return false
}
// Canonicalize checks that the file path is valid and returns it in the "canonical" form:
// - /foo/bar -> foo/bar
// - / -> "."
func Canonicalize(file string) (string, error) {
pathSep := string(PathSeparator)
if strings.HasPrefix(file, pathSep+pathSep) {
// The relative path may pretend to be an absolute path within
// the root, but the double path separator on Windows implies
// something else and is out of spec.
return "", ErrNotRelative
}
// The relative path should be clean from internal dotdots and similar
// funkyness.
file = filepath.Clean(file)
// It is not acceptable to attempt to traverse upwards.
switch file {
case "..":
return "", ErrNotRelative
}
if strings.HasPrefix(file, ".."+pathSep) {
return "", ErrNotRelative
}
if strings.HasPrefix(file, pathSep) {
if file == pathSep {
return ".", nil
}
return file[1:], nil
}
return file, nil
}
+57
View File
@@ -41,3 +41,60 @@ func TestIsInternal(t *testing.T) {
}
}
}
func TestCanonicalize(t *testing.T) {
type testcase struct {
path string
expected string
ok bool
}
cases := []testcase{
// Valid cases
{"/bar", "bar", true},
{"/bar/baz", "bar/baz", true},
{"bar", "bar", true},
{"bar/baz", "bar/baz", true},
// Not escape attempts, but oddly formatted relative paths
{"", ".", true},
{"/", ".", true},
{"/..", ".", true},
{"./bar", "bar", true},
{"./bar/baz", "bar/baz", true},
{"bar/../baz", "baz", true},
{"/bar/../baz", "baz", true},
{"./bar/../baz", "baz", true},
// Results in an allowed path, but does it by probing. Disallowed.
{"../foo", "", false},
{"../foo/bar", "", false},
{"../foo/bar", "", false},
{"../../baz/foo/bar", "", false},
{"bar/../../foo/bar", "", false},
{"bar/../../../baz/foo/bar", "", false},
// Escape attempts.
{"..", "", false},
{"../", "", false},
{"../bar", "", false},
{"../foobar", "", false},
{"bar/../../quux/baz", "", false},
}
for _, tc := range cases {
res, err := Canonicalize(tc.path)
if tc.ok {
if err != nil {
t.Errorf("Unexpected error for Canonicalize(%q): %v", tc.path, err)
continue
}
exp := filepath.FromSlash(tc.expected)
if res != exp {
t.Errorf("Unexpected result for Canonicalize(%q): %q != expected %q", tc.path, res, exp)
}
} else if err == nil {
t.Errorf("Unexpected pass for Canonicalize(%q) => %q", tc.path, res)
continue
}
}
}
+1 -1
View File
@@ -23,7 +23,7 @@ func TestUnicodeLowercase(t *testing.T) {
{"ὈΔΥΣΣΕΎΣ", "ὀδυσσεύσ"},
// German ß doesn't really have an upper case variant, and we
// shouldn't mess things up when lower casing it either. We don't
// attempt to make ß equivalant to "ss".
// attempt to make ß equivalent to "ss".
{"Reichwaldstraße", "reichwaldstraße"},
// The Turks do their thing with the Is.... Like the Greek example
// we pick just the one canonicalized "i" although you can argue
+7 -6
View File
@@ -10,10 +10,7 @@
package fs
import (
"path/filepath"
"sort"
)
import "path/filepath"
// WalkFunc is the type of the function called for each file or directory
// visited by Walk. The path argument contains the argument to Walk as a
@@ -41,7 +38,12 @@ func NewWalkFilesystem(next Filesystem) Filesystem {
// walk recursively descends path, calling walkFn.
func (f *walkFilesystem) walk(path string, info FileInfo, walkFn WalkFunc) error {
err := walkFn(path, info, nil)
path, err := Canonicalize(path)
if err != nil {
return err
}
err = walkFn(path, info, nil)
if err != nil {
if info.IsDir() && err == SkipDir {
return nil
@@ -57,7 +59,6 @@ func (f *walkFilesystem) walk(path string, info FileInfo, walkFn WalkFunc) error
if err != nil {
return walkFn(path, info, err)
}
sort.Strings(names)
for _, name := range names {
filename := filepath.Join(path, name)
+35 -15
View File
@@ -13,6 +13,7 @@ import (
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/ignore"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/sync"
"github.com/syncthing/syncthing/lib/watchaggregator"
)
@@ -23,16 +24,21 @@ type folder struct {
stateTracker
config.FolderConfiguration
model *Model
shortID protocol.ShortID
ctx context.Context
cancel context.CancelFunc
scan folderScanner
model *Model
ctx context.Context
cancel context.CancelFunc
initialScanFinished chan struct{}
watchCancel context.CancelFunc
watchChan chan []string
restartWatchChan chan struct{}
watchErr error
watchErrMut sync.Mutex
pullScheduled chan struct{}
watchCancel context.CancelFunc
watchChan chan []string
restartWatchChan chan struct{}
watchErr error
watchErrMut sync.Mutex
}
func newFolder(model *Model, cfg config.FolderConfiguration) folder {
@@ -42,14 +48,19 @@ func newFolder(model *Model, cfg config.FolderConfiguration) folder {
stateTracker: newStateTracker(cfg.ID),
FolderConfiguration: cfg,
model: model,
shortID: model.shortID,
ctx: ctx,
cancel: cancel,
scan: newFolderScanner(cfg),
ctx: ctx,
cancel: cancel,
model: model,
initialScanFinished: make(chan struct{}),
watchCancel: func() {},
watchErr: errWatchNotStarted,
watchErrMut: sync.NewMutex(),
pullScheduled: make(chan struct{}, 1), // This needs to be 1-buffered so that we queue a pull if we're busy when it comes.
watchCancel: func() {},
watchErr: errWatchNotStarted,
watchErrMut: sync.NewMutex(),
}
}
@@ -65,7 +76,16 @@ func (f *folder) IgnoresUpdated() {
}
}
func (f *folder) SchedulePull() {}
func (f *folder) SchedulePull() {
select {
case f.pullScheduled <- struct{}{}:
default:
// We might be busy doing a pull and thus not reading from this
// channel. The channel is 1-buffered, so one notification will be
// queued to ensure we recheck after the pull, but beyond that we must
// make sure to not block index receiving.
}
}
func (f *folder) Jobs() ([]string, []string) {
return nil, nil
+105 -79
View File
@@ -1395,24 +1395,17 @@ func (m *Model) CurrentGlobalFile(folder string, file string) (protocol.FileInfo
return fs.GetGlobal(file)
}
type haveWalker struct {
fset *db.FileSet
type cFiler struct {
m *Model
r string
}
func (h haveWalker) Walk(prefix string, ctx context.Context, out chan<- protocol.FileInfo) {
ctxChan := ctx.Done()
h.fset.WithPrefixedHave(protocol.LocalDeviceID, prefix, func(fi db.FileIntf) bool {
f := fi.(protocol.FileInfo)
select {
case out <- f:
case <-ctxChan:
return false
}
return true
})
// Implements scanner.CurrentFiler
func (cf cFiler) CurrentFile(file string) (protocol.FileInfo, bool) {
return cf.m.CurrentFolderFile(cf.r, file)
}
// Connection returns the current connection for device, and a boolean wether a connection was found.
// Connection returns the current connection for device, and a boolean whether a connection was found.
func (m *Model) Connection(deviceID protocol.DeviceID) (connections.Connection, bool) {
m.pmut.RLock()
cn, ok := m.conn[deviceID]
@@ -1962,14 +1955,13 @@ func (m *Model) internalScanFolderSubdirs(ctx context.Context, folder string, su
runner.setState(FolderScanning)
haveWalker := haveWalker{fset}
rchan := scanner.Walk(ctx, scanner.Config{
fchan := scanner.Walk(ctx, scanner.Config{
Folder: folderCfg.ID,
Subs: subDirs,
Matcher: ignores,
BlockSize: protocol.BlockSize,
TempLifetime: time.Duration(m.cfg.Options().KeepTemporariesH) * time.Hour,
Have: haveWalker,
CurrentFiler: cFiler{m, folder},
Filesystem: mtimefs,
IgnorePerms: folderCfg.IgnorePerms,
AutoNormalize: folderCfg.AutoNormalize,
@@ -1986,17 +1978,6 @@ func (m *Model) internalScanFolderSubdirs(ctx context.Context, folder string, su
batch := make([]protocol.FileInfo, 0, maxBatchSizeFiles)
batchSizeBytes := 0
changes := 0
checkBatch := func() error {
if len(batch) == maxBatchSizeFiles || batchSizeBytes > maxBatchSizeBytes {
if err := runner.CheckHealth(); err != nil {
return err
}
m.updateLocalsFromScanning(folder, batch)
batch = batch[:0]
batchSizeBytes = 0
}
return nil
}
// Schedule a pull after scanning, but only if we actually detected any
// changes.
@@ -2006,49 +1987,105 @@ func (m *Model) internalScanFolderSubdirs(ctx context.Context, folder string, su
}
}()
var delDirStack []protocol.FileInfo
for r := range rchan {
if err := checkBatch(); err != nil {
l.Debugln("Stopping scan of folder %s due to: %s", folderCfg.Description(), err)
return err
}
// Append deleted dirs from stack if the current file isn't a child,
// which means all children were already processed.
for len(delDirStack) != 0 && !strings.HasPrefix(r.New.Name, delDirStack[len(delDirStack)-1].Name+string(fs.PathSeparator)) {
lastDelDir := delDirStack[len(delDirStack)-1]
batch = append(batch, lastDelDir)
batchSizeBytes += lastDelDir.ProtoSize()
changes++
if err := checkBatch(); err != nil {
for f := range fchan {
if len(batch) == maxBatchSizeFiles || batchSizeBytes > maxBatchSizeBytes {
if err := runner.CheckHealth(); err != nil {
l.Debugln("Stopping scan of folder %s due to: %s", folderCfg.Description(), err)
return err
}
delDirStack = delDirStack[:len(delDirStack)-1]
m.updateLocalsFromScanning(folder, batch)
batch = batch[:0]
batchSizeBytes = 0
}
// Delay appending deleted dirs until all its children are processed
if r.Old.IsDirectory() && (r.New.Deleted || !r.New.IsDirectory()) {
delDirStack = append(delDirStack, r.New)
continue
}
l.Debugln("Appending", r)
batch = append(batch, r.New)
batchSizeBytes += r.New.ProtoSize()
batch = append(batch, f)
batchSizeBytes += f.ProtoSize()
changes++
}
// Append remaining deleted dirs.
for i := len(delDirStack) - 1; i >= 0; i-- {
if err := checkBatch(); err != nil {
l.Debugln("Stopping scan of folder %s due to: %s", folderCfg.Description(), err)
return err
}
if err := runner.CheckHealth(); err != nil {
l.Debugln("Stopping scan of folder %s due to: %s", folderCfg.Description(), err)
return err
} else if len(batch) > 0 {
m.updateLocalsFromScanning(folder, batch)
}
batch = append(batch, delDirStack[i])
batchSizeBytes += delDirStack[i].ProtoSize()
changes++
if len(subDirs) == 0 {
// If we have no specific subdirectories to traverse, set it to one
// empty prefix so we traverse the entire folder contents once.
subDirs = []string{""}
}
// Do a scan of the database for each prefix, to check for deleted and
// ignored files.
batch = batch[:0]
batchSizeBytes = 0
for _, sub := range subDirs {
var iterError error
fset.WithPrefixedHaveTruncated(protocol.LocalDeviceID, sub, func(fi db.FileIntf) bool {
f := fi.(db.FileInfoTruncated)
if len(batch) == maxBatchSizeFiles || batchSizeBytes > maxBatchSizeBytes {
if err := runner.CheckHealth(); err != nil {
iterError = err
return false
}
m.updateLocalsFromScanning(folder, batch)
batch = batch[:0]
batchSizeBytes = 0
}
switch {
case !f.IsInvalid() && ignores.Match(f.Name).IsIgnored():
// File was valid at last pass but has been ignored. Set invalid bit.
l.Debugln("setting invalid bit on ignored", f)
nf := f.ConvertToInvalidFileInfo(m.id.Short())
batch = append(batch, nf)
batchSizeBytes += nf.ProtoSize()
changes++
case f.IsInvalid() && !ignores.Match(f.Name).IsIgnored():
// Successfully scanned items are already un-ignored during
// the scan, so check whether it is deleted.
fallthrough
case !f.IsInvalid() && !f.IsDeleted():
// The file is valid and not deleted. Lets check if it's
// still here.
// Simply stating it wont do as there are tons of corner
// cases (e.g. parent dir->simlink, missing permissions)
if !osutil.IsDeleted(mtimefs, f.Name) {
return true
}
nf := protocol.FileInfo{
Name: f.Name,
Type: f.Type,
Size: 0,
ModifiedS: f.ModifiedS,
ModifiedNs: f.ModifiedNs,
ModifiedBy: m.id.Short(),
Deleted: true,
Version: f.Version.Update(m.shortID),
}
// We do not want to override the global version
// with the deleted file. Keeping only our local
// counter makes sure we are in conflict with any
// other existing versions, which will be resolved
// by the normal pulling mechanisms.
if f.IsInvalid() {
nf.Version = nf.Version.DropOthers(m.shortID)
}
batch = append(batch, nf)
batchSizeBytes += nf.ProtoSize()
changes++
}
return true
})
if iterError != nil {
l.Debugln("Stopping scan of folder %s due to: %s", folderCfg.Description(), iterError)
return iterError
}
}
if err := runner.CheckHealth(); err != nil {
@@ -2747,7 +2784,12 @@ func unifySubs(dirs []string, exists func(dir string) bool) []string {
}
prev := "./" // Anything that can't be parent of a clean path
for i := 0; i < len(dirs); {
dir := filepath.Clean(dirs[i])
dir, err := fs.Canonicalize(dirs[i])
if err != nil {
l.Debugf("Skipping %v for scan: %s", dirs[i], err)
dirs = append(dirs[:i], dirs[i+1:]...)
continue
}
if dir == prev || strings.HasPrefix(dir, prev+string(fs.PathSeparator)) {
dirs = append(dirs[:i], dirs[i+1:]...)
continue
@@ -2781,22 +2823,6 @@ func makeForgetUpdate(files []protocol.FileInfo) []protocol.FileDownloadProgress
return updates
}
// shouldIgnore returns true when a file should be excluded from processing
func shouldIgnore(file db.FileIntf, matcher *ignore.Matcher, ignoreDelete bool) bool {
switch {
case ignoreDelete && file.IsDeleted():
// ignoreDelete first because it's a very cheap test so a win if it
// succeeds, and we might in the long run accumulate quite a few
// deleted files.
return true
case matcher.ShouldIgnore(file.FileName()):
return true
}
return false
}
// folderDeviceSet is a set of (folder, deviceID) pairs
type folderDeviceSet map[string]map[protocol.DeviceID]struct{}
+9 -82
View File
@@ -26,7 +26,6 @@ import (
"github.com/d4l3k/messagediff"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/ignore"
"github.com/syncthing/syncthing/lib/osutil"
@@ -50,6 +49,7 @@ func init() {
defaultFolderConfig = config.NewFolderConfiguration(protocol.LocalDeviceID, "default", "default", fs.FilesystemTypeBasic, "testdata")
defaultFolderConfig.Devices = []config.FolderDeviceConfiguration{{DeviceID: device1}}
_defaultConfig := config.Configuration{
Version: config.CurrentVersion,
Folders: []config.FolderConfiguration{defaultFolderConfig},
Devices: []config.DeviceConfiguration{config.NewDeviceConfiguration(device1, "device1")},
Options: config.OptionsConfiguration{
@@ -2159,6 +2159,12 @@ func unifySubsCases() []unifySubsCase {
[]string{"foo"},
nil,
},
{
// 10. absolute path
[]string{"/foo"},
[]string{"foo"},
[]string{"foo"},
},
}
if runtime.GOOS == "windows" {
@@ -2811,8 +2817,9 @@ func TestNoRequestsFromPausedDevices(t *testing.T) {
// TestIssue2571 tests replacing a directory with content with a symlink
func TestIssue2571(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("Symlinks aren't supported by fs and scanner on windows")
t.Skip("Scanning symlinks isn't supported on windows")
}
err := defaultFs.MkdirAll("replaceDir", 0755)
if err != nil {
t.Fatal(err)
@@ -3441,86 +3448,6 @@ func TestPausedFolders(t *testing.T) {
}
}
func TestPullInvalid(t *testing.T) {
if runtime.GOOS != "windows" {
t.Skip("Windows only")
}
tmpDir, err := ioutil.TempDir(".", "_model-")
if err != nil {
panic("Failed to create temporary testing dir")
}
defer os.RemoveAll(tmpDir)
cfg := defaultConfig.RawCopy()
cfg.Folders[0] = config.NewFolderConfiguration(protocol.LocalDeviceID, "default", "default", fs.FilesystemTypeBasic, tmpDir)
cfg.Folders[0].Devices = []config.FolderDeviceConfiguration{{DeviceID: device1}}
w := config.Wrap("/tmp/cfg", cfg)
db := db.OpenMemory()
m := NewModel(w, protocol.LocalDeviceID, "syncthing", "dev", db, nil)
m.AddFolder(cfg.Folders[0])
m.StartFolder("default")
m.ServeBackground()
defer m.Stop()
m.ScanFolder("default")
if err := m.SetIgnores("default", []string{"*:ignored"}); err != nil {
panic(err)
}
ign := "invalid:ignored"
del := "invalid:deleted"
var version protocol.Vector
version = version.Update(device1.Short())
m.IndexUpdate(device1, "default", []protocol.FileInfo{
{
Name: ign,
Size: 1234,
Type: protocol.FileInfoTypeFile,
Version: version,
},
{
Name: del,
Size: 1234,
Type: protocol.FileInfoTypeFile,
Version: version,
Deleted: true,
},
})
sub := events.Default.Subscribe(events.FolderErrors)
defer events.Default.Unsubscribe(sub)
timeout := time.NewTimer(5 * time.Second)
for {
select {
case ev := <-sub.C():
t.Fatalf("Errors while pulling: %v", ev)
case <-timeout.C:
t.Fatalf("File wasn't added to index until timeout")
default:
}
file, ok := m.CurrentFolderFile("default", ign)
if !ok {
time.Sleep(100 * time.Millisecond)
continue
}
if !file.Invalid {
t.Error("Ignored file isn't marked as invalid")
}
if file, ok = m.CurrentFolderFile("default", del); ok {
t.Error("Deleted invalid file was added to index")
}
return
}
}
func addFakeConn(m *Model, dev protocol.DeviceID) *fakeConnection {
fc := &fakeConnection{id: dev, model: m}
m.AddConnection(fc, protocol.HelloResult{})
+224 -24
View File
@@ -8,6 +8,7 @@ package model
import (
"bytes"
"errors"
"io/ioutil"
"os"
"path/filepath"
@@ -18,7 +19,9 @@ import (
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/events"
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/ignore"
"github.com/syncthing/syncthing/lib/protocol"
)
@@ -26,9 +29,9 @@ func TestRequestSimple(t *testing.T) {
// Verify that the model performs a request and creates a file based on
// an incoming index update.
m, fc, tmpFolder := setupModelWithConnection()
m, fc, tmpDir := setupModelWithConnection()
defer m.Stop()
defer os.RemoveAll(tmpFolder)
defer os.RemoveAll(tmpDir)
// We listen for incoming index updates and trigger when we see one for
// the expected test file.
@@ -51,13 +54,8 @@ func TestRequestSimple(t *testing.T) {
<-done
// Verify the contents
bs, err := ioutil.ReadFile(filepath.Join(tmpFolder, "testfile"))
if err != nil {
if err := equalContents(filepath.Join(tmpDir, "testfile"), contents); err != nil {
t.Error("File did not sync correctly:", err)
return
}
if !bytes.Equal(bs, contents) {
t.Error("File did not sync correctly: incorrect data")
}
}
@@ -69,9 +67,9 @@ func TestSymlinkTraversalRead(t *testing.T) {
return
}
m, fc, tmpFolder := setupModelWithConnection()
m, fc, tmpDir := setupModelWithConnection()
defer m.Stop()
defer os.RemoveAll(tmpFolder)
defer os.RemoveAll(tmpDir)
// We listen for incoming index updates and trigger when we see one for
// the expected test file.
@@ -109,9 +107,9 @@ func TestSymlinkTraversalWrite(t *testing.T) {
return
}
m, fc, tmpFolder := setupModelWithConnection()
m, fc, tmpDir := setupModelWithConnection()
defer m.Stop()
defer os.RemoveAll(tmpFolder)
defer os.RemoveAll(tmpDir)
// We listen for incoming index updates and trigger when we see one for
// the expected names.
@@ -169,9 +167,9 @@ func TestSymlinkTraversalWrite(t *testing.T) {
func TestRequestCreateTmpSymlink(t *testing.T) {
// Test that an update for a temporary file is invalidated
m, fc, tmpFolder := setupModelWithConnection()
m, fc, tmpDir := setupModelWithConnection()
defer m.Stop()
defer os.RemoveAll(tmpFolder)
defer os.RemoveAll(tmpDir)
// We listen for incoming index updates and trigger when we see one for
// the expected test file.
@@ -211,12 +209,12 @@ func TestRequestVersioningSymlinkAttack(t *testing.T) {
// Sets up a folder with trashcan versioning and tries to use a
// deleted symlink to escape
tmpFolder, err := ioutil.TempDir(".", "_request-")
tmpDir, err := ioutil.TempDir(".", "_request-")
if err != nil {
panic("Failed to create temporary testing dir")
}
cfg := defaultConfig.RawCopy()
cfg.Folders[0] = config.NewFolderConfiguration(protocol.LocalDeviceID, "default", "default", fs.FilesystemTypeBasic, tmpFolder)
cfg.Folders[0] = config.NewFolderConfiguration(protocol.LocalDeviceID, "default", "default", fs.FilesystemTypeBasic, tmpDir)
cfg.Folders[0].Devices = []config.FolderDeviceConfiguration{
{DeviceID: device1},
{DeviceID: device2},
@@ -233,7 +231,7 @@ func TestRequestVersioningSymlinkAttack(t *testing.T) {
m.StartFolder("default")
defer m.Stop()
defer os.RemoveAll(tmpFolder)
defer os.RemoveAll(tmpDir)
fc := addFakeConn(m, device2)
fc.folder = "default"
@@ -286,17 +284,200 @@ func TestRequestVersioningSymlinkAttack(t *testing.T) {
}
}
func setupModelWithConnection() (*Model, *fakeConnection, string) {
tmpFolder, err := ioutil.TempDir(".", "_request-")
if err != nil {
panic("Failed to create temporary testing dir")
}
func TestPullInvalidIgnoredSO(t *testing.T) {
pullInvalidIgnored(t, config.FolderTypeSendOnly)
}
func TestPullInvalidIgnoredSR(t *testing.T) {
pullInvalidIgnored(t, config.FolderTypeSendReceive)
}
// This test checks that (un-)ignored/invalid/deleted files are treated as expected.
func pullInvalidIgnored(t *testing.T, ft config.FolderType) {
t.Helper()
tmpDir := createTmpDir()
defer os.RemoveAll(tmpDir)
cfg := defaultConfig.RawCopy()
cfg.Folders[0] = config.NewFolderConfiguration(protocol.LocalDeviceID, "default", "default", fs.FilesystemTypeBasic, tmpFolder)
cfg.Devices = append(cfg.Devices, config.NewDeviceConfiguration(device2, "device2"))
cfg.Folders[0] = config.NewFolderConfiguration(protocol.LocalDeviceID, "default", "default", fs.FilesystemTypeBasic, tmpDir)
cfg.Folders[0].Devices = []config.FolderDeviceConfiguration{
{DeviceID: device1},
{DeviceID: device2},
}
cfg.Folders[0].Type = ft
m, fc := setupModelWithConnectionManual(cfg)
defer m.Stop()
// Reach in and update the ignore matcher to one that always does
// reloads when asked to, instead of checking file mtimes. This is
// because we might be changing the files on disk often enough that the
// mtimes will be unreliable to determine change status.
m.fmut.Lock()
m.folderIgnores["default"] = ignore.New(cfg.Folders[0].Filesystem(), ignore.WithChangeDetector(newAlwaysChanged()))
m.fmut.Unlock()
if err := m.SetIgnores("default", []string{"*ignored*"}); err != nil {
panic(err)
}
contents := []byte("test file contents\n")
otherContents := []byte("other test file contents\n")
invIgn := "invalid:ignored"
invDel := "invalid:deleted"
ign := "ignoredNonExisting"
ignExisting := "ignoredExisting"
fc.addFile(invIgn, 0644, protocol.FileInfoTypeFile, contents)
fc.addFile(invDel, 0644, protocol.FileInfoTypeFile, contents)
fc.deleteFile(invDel)
fc.addFile(ign, 0644, protocol.FileInfoTypeFile, contents)
fc.addFile(ignExisting, 0644, protocol.FileInfoTypeFile, contents)
if err := ioutil.WriteFile(filepath.Join(tmpDir, ignExisting), otherContents, 0644); err != nil {
panic(err)
}
done := make(chan struct{})
fc.mut.Lock()
fc.indexFn = func(folder string, fs []protocol.FileInfo) {
expected := map[string]struct{}{invIgn: {}, ign: {}, ignExisting: {}}
for _, f := range fs {
if _, ok := expected[f.Name]; !ok {
t.Fatalf("Unexpected file %v was added to index", f.Name)
}
if !f.Invalid {
t.Errorf("File %v wasn't marked as invalid", f.Name)
}
delete(expected, f.Name)
}
for name := range expected {
t.Errorf("File %v wasn't added to index", name)
}
done <- struct{}{}
}
fc.mut.Unlock()
sub := events.Default.Subscribe(events.FolderErrors)
defer events.Default.Unsubscribe(sub)
fc.sendIndexUpdate()
timeout := time.NewTimer(5 * time.Second)
select {
case ev := <-sub.C():
t.Fatalf("Errors while pulling: %v", ev)
case <-timeout.C:
t.Fatalf("timed out before index was received")
case <-done:
return
}
fc.mut.Lock()
fc.indexFn = func(folder string, fs []protocol.FileInfo) {
expected := map[string]struct{}{ign: {}, ignExisting: {}}
for _, f := range fs {
if _, ok := expected[f.Name]; !ok {
t.Fatalf("Unexpected file %v was updated in index", f.Name)
}
if f.Invalid {
t.Errorf("File %v is still marked as invalid", f.Name)
}
// The unignored files should only have a local version,
// to mark them as in conflict with any other existing versions.
ev := protocol.Vector{}.Update(device1.Short())
if v := f.Version; !v.Equal(ev) {
t.Errorf("File %v has version %v, expected %v", f.Name, v, ev)
}
if f.Name == ign {
if !f.Deleted {
t.Errorf("File %v was not marked as deleted", f.Name)
}
} else if f.Deleted {
t.Errorf("File %v is marked as deleted", f.Name)
}
delete(expected, f.Name)
}
for name := range expected {
t.Errorf("File %v wasn't updated in index", name)
}
done <- struct{}{}
}
// Make sure pulling doesn't interfere, as index updates are racy and
// thus we cannot distinguish between scan and pull results.
fc.requestFn = func(folder, name string, offset int64, size int, hash []byte, fromTemporary bool) ([]byte, error) {
return nil, nil
}
fc.mut.Unlock()
if err := m.SetIgnores("default", []string{"*:ignored*"}); err != nil {
panic(err)
}
timeout = time.NewTimer(5 * time.Second)
select {
case <-timeout.C:
t.Fatalf("timed out before index was received")
case <-done:
return
}
}
func TestIssue4841(t *testing.T) {
m, fc, tmpDir := setupModelWithConnection()
defer m.Stop()
defer os.RemoveAll(tmpDir)
received := make(chan protocol.FileInfo)
fc.mut.Lock()
fc.indexFn = func(folder string, fs []protocol.FileInfo) {
if len(fs) != 1 {
t.Fatalf("Sent index with %d files, should be 1", len(fs))
}
if fs[0].Name != "foo" {
t.Fatalf(`Sent index with file %v, should be "foo"`, fs[0].Name)
}
received <- fs[0]
return
}
fc.mut.Unlock()
// Setup file from remote that was ignored locally
m.updateLocals(defaultFolderConfig.ID, []protocol.FileInfo{{
Name: "foo",
Type: protocol.FileInfoTypeFile,
Invalid: true,
Version: protocol.Vector{}.Update(device2.Short()),
}})
<-received
// Scan without ignore patterns with "foo" not existing locally
if err := m.ScanFolder("default"); err != nil {
t.Fatal("Failed scanning:", err)
}
f := <-received
if expected := (protocol.Vector{}.Update(device1.Short())); !f.Version.Equal(expected) {
t.Errorf("Got Version == %v, expected %v", f.Version, expected)
}
}
func setupModelWithConnection() (*Model, *fakeConnection, string) {
tmpDir := createTmpDir()
cfg := defaultConfig.RawCopy()
cfg.Devices = append(cfg.Devices, config.NewDeviceConfiguration(device2, "device2"))
cfg.Folders[0] = config.NewFolderConfiguration(protocol.LocalDeviceID, "default", "default", fs.FilesystemTypeBasic, tmpDir)
cfg.Folders[0].Devices = []config.FolderDeviceConfiguration{
{DeviceID: device1},
{DeviceID: device2},
}
m, fc := setupModelWithConnectionManual(cfg)
return m, fc, tmpDir
}
func setupModelWithConnectionManual(cfg config.Configuration) (*Model, *fakeConnection) {
w := config.Wrap("/tmp/cfg", cfg)
db := db.OpenMemory()
@@ -308,5 +489,24 @@ func setupModelWithConnection() (*Model, *fakeConnection, string) {
fc := addFakeConn(m, device2)
fc.folder = "default"
return m, fc, tmpFolder
m.ScanFolder("default")
return m, fc
}
func createTmpDir() string {
tmpDir, err := ioutil.TempDir(".", "_request-")
if err != nil {
panic("Failed to create temporary testing dir")
}
return tmpDir
}
func equalContents(path string, contents []byte) error {
if bs, err := ioutil.ReadFile(path); err != nil {
return err
} else if !bytes.Equal(bs, contents) {
return errors.New("incorrect data")
}
return nil
}
+64
View File
@@ -10,7 +10,9 @@ import (
"fmt"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/protocol"
"github.com/syncthing/syncthing/lib/versioner"
)
@@ -43,6 +45,9 @@ func (f *sendOnlyFolder) Serve() {
case <-f.ctx.Done():
return
case <-f.pullScheduled:
f.pull()
case <-f.restartWatchChan:
f.restartWatch()
@@ -70,3 +75,62 @@ func (f *sendOnlyFolder) String() string {
func (f *sendOnlyFolder) PullErrors() []FileError {
return nil
}
// pull checks need for files that only differ by metadata (no changes on disk)
func (f *sendOnlyFolder) pull() {
select {
case <-f.initialScanFinished:
default:
// Once the initial scan finished, a pull will be scheduled
return
}
f.model.fmut.RLock()
folderFiles := f.model.folderFiles[f.folderID]
ignores := f.model.folderIgnores[f.folderID]
f.model.fmut.RUnlock()
batch := make([]protocol.FileInfo, 0, maxBatchSizeFiles)
batchSizeBytes := 0
folderFiles.WithNeed(protocol.LocalDeviceID, func(intf db.FileIntf) bool {
if len(batch) == maxBatchSizeFiles || batchSizeBytes > maxBatchSizeBytes {
f.model.updateLocalsFromPulling(f.folderID, batch)
batch = batch[:0]
batchSizeBytes = 0
}
if ignores.ShouldIgnore(intf.FileName()) {
file := intf.(protocol.FileInfo)
file.Invalidate(f.shortID)
batch = append(batch, file)
batchSizeBytes += file.ProtoSize()
l.Debugln(f, "Handling ignored file", file)
return true
}
curFile, ok := f.model.CurrentFolderFile(f.folderID, intf.FileName())
if !ok {
if intf.IsDeleted() {
panic("Should never get a deleted file as needed when we don't have it")
}
return true
}
file := intf.(protocol.FileInfo)
if !file.IsEquivalent(curFile, f.IgnorePerms, false) {
return true
}
file.Version = file.Version.Merge(curFile.Version)
batch = append(batch, file)
batchSizeBytes += file.ProtoSize()
l.Debugln(f, "Merging versions of identical file", file)
return true
})
if len(batch) > 0 {
f.model.updateLocalsFromPulling(f.folderID, batch)
}
}
+156 -124
View File
@@ -15,6 +15,7 @@ import (
"runtime"
"sort"
"strings"
stdsync "sync"
"time"
"github.com/syncthing/syncthing/lib/config"
@@ -78,9 +79,10 @@ const (
)
const (
defaultCopiers = 2
defaultPullers = 64
defaultPullerPause = 60 * time.Second
defaultCopiers = 2
defaultPullerPause = 60 * time.Second
defaultPullerPendingKiB = 8192 // must be larger than block size
maxPullerIterations = 3
)
@@ -96,8 +98,7 @@ type sendReceiveFolder struct {
versioner versioner.Versioner
pause time.Duration
queue *jobQueue
pullScheduled chan struct{}
queue *jobQueue
errors map[string]string // path -> error string
errorsMut sync.Mutex
@@ -110,33 +111,28 @@ func newSendReceiveFolder(model *Model, cfg config.FolderConfiguration, ver vers
fs: fs,
versioner: ver,
queue: newJobQueue(),
pullScheduled: make(chan struct{}, 1), // This needs to be 1-buffered so that we queue a pull if we're busy when it comes.
queue: newJobQueue(),
errorsMut: sync.NewMutex(),
}
f.configureCopiersAndPullers()
return f
}
func (f *sendReceiveFolder) configureCopiersAndPullers() {
if f.Copiers == 0 {
f.Copiers = defaultCopiers
}
if f.Pullers == 0 {
f.Pullers = defaultPullers
// If the configured max amount of pending data is zero, we use the
// default. If it's configured to something non-zero but less than the
// protocol block size we adjust it upwards accordingly.
if f.PullerMaxPendingKiB == 0 {
f.PullerMaxPendingKiB = defaultPullerPendingKiB
}
if blockSizeKiB := protocol.BlockSize / 1024; f.PullerMaxPendingKiB < blockSizeKiB {
f.PullerMaxPendingKiB = blockSizeKiB
}
f.pause = f.basePause()
}
// Helper function to check whether either the ignorePerm flag has been
// set on the local host or the FlagNoPermBits has been set on the file/dir
// which is being pulled.
func (f *sendReceiveFolder) ignorePermissions(file protocol.FileInfo) bool {
return f.IgnorePerms || file.NoPermissions
return f
}
// Serve will run scans and pulls. It will return when Stop()ed or on a
@@ -326,7 +322,7 @@ func (f *sendReceiveFolder) pullerIteration(ignores *ignore.Matcher, ignoresChan
doneWg := sync.NewWaitGroup()
updateWg := sync.NewWaitGroup()
l.Debugln(f, "c", f.Copiers, "p", f.Pullers)
l.Debugln(f, "copiers:", f.Copiers, "pullerPendingKiB:", f.PullerMaxPendingKiB)
updateWg.Add(1)
go func() {
@@ -344,14 +340,12 @@ func (f *sendReceiveFolder) pullerIteration(ignores *ignore.Matcher, ignoresChan
}()
}
for i := 0; i < f.Pullers; i++ {
pullWg.Add(1)
go func() {
// pullerRoutine finishes when pullChan is closed
f.pullerRoutine(pullChan, finisherChan)
pullWg.Done()
}()
}
pullWg.Add(1)
go func() {
// pullerRoutine finishes when pullChan is closed
f.pullerRoutine(pullChan, finisherChan)
pullWg.Done()
}()
doneWg.Add(1)
// finisherRoutine finishes when finisherChan is closed
@@ -371,14 +365,7 @@ func (f *sendReceiveFolder) pullerIteration(ignores *ignore.Matcher, ignoresChan
// Regular files to pull goes into the file queue, everything else
// (directories, symlinks and deletes) goes into the "process directly"
// pile.
// Don't iterate over invalid/ignored files unless ignores have changed
iterate := folderFiles.WithNeed
if ignoresChanged {
iterate = folderFiles.WithNeedOrInvalid
}
iterate(protocol.LocalDeviceID, func(intf db.FileIntf) bool {
folderFiles.WithNeed(protocol.LocalDeviceID, func(intf db.FileIntf) bool {
if f.IgnoreDelete && intf.IsDeleted() {
l.Debugln(f, "ignore file deletion (config)", intf.FileName())
return true
@@ -388,7 +375,7 @@ func (f *sendReceiveFolder) pullerIteration(ignores *ignore.Matcher, ignoresChan
switch {
case ignores.ShouldIgnore(file.Name):
file.Invalidate(f.model.id.Short())
file.Invalidate(f.shortID)
l.Debugln(f, "Handling ignored file", file)
dbUpdateChan <- dbUpdateJob{file, dbUpdateInvalidate}
@@ -416,7 +403,7 @@ func (f *sendReceiveFolder) pullerIteration(ignores *ignore.Matcher, ignoresChan
l.Debugln(f, "Needed file is unavailable", file)
case runtime.GOOS == "windows" && file.IsSymlink():
file.Invalidate(f.model.id.Short())
file.Invalidate(f.shortID)
l.Debugln(f, "Invalidating symlink (unsupported)", file.Name)
dbUpdateChan <- dbUpdateJob{file, dbUpdateInvalidate}
@@ -562,7 +549,7 @@ nextFile:
// we can just do a rename instead.
key := string(fi.Blocks[0].Hash)
for i, candidate := range buckets[key] {
if blocksEqual(candidate.Blocks, fi.Blocks) {
if protocol.BlocksEqual(candidate.Blocks, fi.Blocks) {
// Remove the candidate from the bucket
lidx := len(buckets[key]) - 1
buckets[key][i] = buckets[key][lidx]
@@ -617,21 +604,6 @@ nextFile:
return changed
}
// blocksEqual returns whether two slices of blocks are exactly the same hash
// and index pair wise.
func blocksEqual(src, tgt []protocol.BlockInfo) bool {
if len(tgt) != len(src) {
return false
}
for i, sblk := range src {
if !bytes.Equal(sblk.Hash, tgt[i].Hash) {
return false
}
}
return true
}
// handleDir creates or updates the given directory
func (f *sendReceiveFolder) handleDir(file protocol.FileInfo, dbUpdateChan chan<- dbUpdateJob) {
// Used in the defer closure below, updated by the function body. Take
@@ -656,7 +628,7 @@ func (f *sendReceiveFolder) handleDir(file protocol.FileInfo, dbUpdateChan chan<
}()
mode := fs.FileMode(file.Permissions & 0777)
if f.ignorePermissions(file) {
if f.IgnorePerms || file.NoPermissions {
mode = 0777
}
@@ -685,7 +657,7 @@ func (f *sendReceiveFolder) handleDir(file protocol.FileInfo, dbUpdateChan chan<
// not MkdirAll because the parent should already exist.
mkdir := func(path string) error {
err = f.fs.Mkdir(path, mode)
if err != nil || f.ignorePermissions(file) {
if err != nil || f.IgnorePerms || file.NoPermissions {
return err
}
@@ -716,7 +688,7 @@ func (f *sendReceiveFolder) handleDir(file protocol.FileInfo, dbUpdateChan chan<
// The directory already exists, so we just correct the mode bits. (We
// don't handle modification times on directories, because that sucks...)
// It's OK to change mode bits on stuff within non-writable directories.
if f.ignorePermissions(file) {
if f.IgnorePerms || file.NoPermissions {
dbUpdateChan <- dbUpdateJob{file, dbUpdateHandleDir}
} else if err := f.fs.Chmod(file.Name, mode|(fs.FileMode(info.Mode())&retainBits)); err == nil {
dbUpdateChan <- dbUpdateJob{file, dbUpdateHandleDir}
@@ -1107,7 +1079,7 @@ func (f *sendReceiveFolder) handleFile(file protocol.FileInfo, copyChan chan<- c
updated: time.Now(),
available: reused,
availableUpdated: time.Now(),
ignorePerms: f.ignorePermissions(file),
ignorePerms: f.IgnorePerms || file.NoPermissions,
hasCurFile: hasCurFile,
curFile: curFile,
mut: sync.NewRWMutex(),
@@ -1167,7 +1139,7 @@ func populateOffsets(blocks []protocol.BlockInfo) {
// shortcutFile sets file mode and modification time, when that's the only
// thing that has changed.
func (f *sendReceiveFolder) shortcutFile(file protocol.FileInfo) error {
if !f.ignorePermissions(file) {
if !f.IgnorePerms && !file.NoPermissions {
if err := f.fs.Chmod(file.Name, fs.FileMode(file.Permissions&0777)); err != nil {
f.newError("shortcut chmod", file.Name, err)
return err
@@ -1260,7 +1232,11 @@ func (f *sendReceiveFolder) copierRoutine(in <-chan copyBlocksState, pullChan ch
continue
}
buf = buf[:int(block.Size)]
if s := int(block.Size); s > cap(buf) {
buf = make([]byte, s)
} else {
buf = buf[:s]
}
found, err := weakHashFinder.Iterate(block.WeakHash, buf, func(offset int64) bool {
if _, err := verifyBuffer(buf, block); err != nil {
@@ -1367,81 +1343,104 @@ func verifyBuffer(buf []byte, block protocol.BlockInfo) ([]byte, error) {
}
func (f *sendReceiveFolder) pullerRoutine(in <-chan pullBlockState, out chan<- *sharedPullerState) {
requestLimiter := newByteSemaphore(f.PullerMaxPendingKiB * 1024)
wg := sync.NewWaitGroup()
for state := range in {
if state.failed() != nil {
out <- state.sharedPullerState
continue
}
// Get an fd to the temporary file. Technically we don't need it until
// after fetching the block, but if we run into an error here there is
// no point in issuing the request to the network.
fd, err := state.tempFile()
if err != nil {
out <- state.sharedPullerState
continue
}
// The requestLimiter limits how many pending block requests we have
// ongoing at any given time, based on the size of the blocks
// themselves.
if !f.DisableSparseFiles && state.reused == 0 && state.block.IsEmpty() {
// There is no need to request a block of all zeroes. Pretend we
// requested it and handled it correctly.
state.pullDone(state.block)
out <- state.sharedPullerState
continue
}
state := state
bytes := int(state.block.Size)
var lastError error
candidates := f.model.Availability(f.folderID, state.file.Name, state.file.Version, state.block)
for {
// Select the least busy device to pull the block from. If we found no
// feasible device at all, fail the block (and in the long run, the
// file).
selected, found := activity.leastBusy(candidates)
if !found {
if lastError != nil {
state.fail("pull", lastError)
} else {
state.fail("pull", errNoDevice)
}
break
}
requestLimiter.take(bytes)
wg.Add(1)
candidates = removeAvailability(candidates, selected)
go func() {
defer wg.Done()
defer requestLimiter.give(bytes)
// Fetch the block, while marking the selected device as in use so that
// leastBusy can select another device when someone else asks.
activity.using(selected)
buf, lastError := f.model.requestGlobal(selected.ID, f.folderID, state.file.Name, state.block.Offset, int(state.block.Size), state.block.Hash, selected.FromTemporary)
activity.done(selected)
f.pullBlock(state, out)
}()
}
wg.Wait()
}
func (f *sendReceiveFolder) pullBlock(state pullBlockState, out chan<- *sharedPullerState) {
// Get an fd to the temporary file. Technically we don't need it until
// after fetching the block, but if we run into an error here there is
// no point in issuing the request to the network.
fd, err := state.tempFile()
if err != nil {
out <- state.sharedPullerState
return
}
if !f.DisableSparseFiles && state.reused == 0 && state.block.IsEmpty() {
// There is no need to request a block of all zeroes. Pretend we
// requested it and handled it correctly.
state.pullDone(state.block)
out <- state.sharedPullerState
return
}
var lastError error
candidates := f.model.Availability(f.folderID, state.file.Name, state.file.Version, state.block)
for {
// Select the least busy device to pull the block from. If we found no
// feasible device at all, fail the block (and in the long run, the
// file).
selected, found := activity.leastBusy(candidates)
if !found {
if lastError != nil {
l.Debugln("request:", f.folderID, state.file.Name, state.block.Offset, state.block.Size, "returned error:", lastError)
continue
}
// Verify that the received block matches the desired hash, if not
// try pulling it from another device.
_, lastError = verifyBuffer(buf, state.block)
if lastError != nil {
l.Debugln("request:", f.folderID, state.file.Name, state.block.Offset, state.block.Size, "hash mismatch")
continue
}
// Save the block data we got from the cluster
_, err = fd.WriteAt(buf, state.block.Offset)
if err != nil {
state.fail("save", err)
state.fail("pull", lastError)
} else {
state.pullDone(state.block)
state.fail("pull", errNoDevice)
}
break
}
out <- state.sharedPullerState
candidates = removeAvailability(candidates, selected)
// Fetch the block, while marking the selected device as in use so that
// leastBusy can select another device when someone else asks.
activity.using(selected)
buf, lastError := f.model.requestGlobal(selected.ID, f.folderID, state.file.Name, state.block.Offset, int(state.block.Size), state.block.Hash, selected.FromTemporary)
activity.done(selected)
if lastError != nil {
l.Debugln("request:", f.folderID, state.file.Name, state.block.Offset, state.block.Size, "returned error:", lastError)
continue
}
// Verify that the received block matches the desired hash, if not
// try pulling it from another device.
_, lastError = verifyBuffer(buf, state.block)
if lastError != nil {
l.Debugln("request:", f.folderID, state.file.Name, state.block.Offset, state.block.Size, "hash mismatch")
continue
}
// Save the block data we got from the cluster
_, err = fd.WriteAt(buf, state.block.Offset)
if err != nil {
state.fail("save", err)
} else {
state.pullDone(state.block)
}
break
}
out <- state.sharedPullerState
}
func (f *sendReceiveFolder) performFinish(ignores *ignore.Matcher, state *sharedPullerState, dbUpdateChan chan<- dbUpdateJob, scanChan chan<- string) error {
// Set the correct permission bits on the new file
if !f.ignorePermissions(state.file) {
if !f.IgnorePerms && !state.file.NoPermissions {
if err := f.fs.Chmod(state.tempName, fs.FileMode(state.file.Permissions&0777)); err != nil {
return err
}
@@ -1486,7 +1485,7 @@ func (f *sendReceiveFolder) performFinish(ignores *ignore.Matcher, state *shared
case stat.IsDir():
// Dirs only have perm, no modetime/size
if !f.ignorePermissions(state.curFile) && state.curFile.HasPermissionBits() && !scanner.PermsEqual(state.curFile.Permissions, curMode) {
if !f.IgnorePerms && !state.curFile.NoPermissions && state.curFile.HasPermissionBits() && !protocol.PermsEqual(state.curFile.Permissions, curMode) {
l.Debugln("file permission modified but not rescanned; not finishing:", state.curFile.Name)
changed = true
}
@@ -1718,7 +1717,7 @@ func (f *sendReceiveFolder) inConflict(current, replacement protocol.Vector) boo
// Obvious case
return true
}
if replacement.Counter(f.model.shortID) > current.Counter(f.model.shortID) {
if replacement.Counter(f.shortID) > current.Counter(f.shortID) {
// The replacement file contains a higher version for ourselves than
// what we have. This isn't supposed to be possible, since it's only
// we who can increment that counter. We take it as a sign that
@@ -1821,11 +1820,6 @@ func (f *sendReceiveFolder) basePause() time.Duration {
return time.Duration(f.PullerPauseS) * time.Second
}
func (f *sendReceiveFolder) IgnoresUpdated() {
f.folder.IgnoresUpdated()
f.SchedulePull()
}
// deleteDir attempts to delete a directory. It checks for files/dirs inside
// the directory and removes them if possible or returns an error if it fails
func (f *sendReceiveFolder) deleteDir(dir string, ignores *ignore.Matcher, scanChan chan<- string) error {
@@ -1931,3 +1925,41 @@ func componentCount(name string) int {
}
return count
}
type byteSemaphore struct {
max int
available int
mut stdsync.Mutex
cond *stdsync.Cond
}
func newByteSemaphore(max int) *byteSemaphore {
s := byteSemaphore{
max: max,
available: max,
}
s.cond = stdsync.NewCond(&s.mut)
return &s
}
func (s *byteSemaphore) take(bytes int) {
if bytes > s.max {
panic("bug: more than max bytes will never be available")
}
s.mut.Lock()
for bytes > s.available {
s.cond.Wait()
}
s.available -= bytes
s.mut.Unlock()
}
func (s *byteSemaphore) give(bytes int) {
s.mut.Lock()
if s.available+bytes > s.max {
panic("bug: can never give more than max")
}
s.available += bytes
s.cond.Broadcast()
s.mut.Unlock()
}
+4
View File
@@ -17,6 +17,7 @@ import (
"testing"
"time"
"github.com/syncthing/syncthing/lib/config"
"github.com/syncthing/syncthing/lib/db"
"github.com/syncthing/syncthing/lib/fs"
"github.com/syncthing/syncthing/lib/ignore"
@@ -107,6 +108,9 @@ func setUpSendReceiveFolder(model *Model) *sendReceiveFolder {
model: model,
initialScanFinished: make(chan struct{}),
ctx: context.TODO(),
FolderConfiguration: config.FolderConfiguration{
PullerMaxPendingKiB: defaultPullerPendingKiB,
},
},
fs: fs.NewMtimeFS(fs.NewFilesystem(fs.FilesystemTypeBasic, "testdata"), db.NewNamespacedKV(model.db, "mtime")),
+11
View File
@@ -153,3 +153,14 @@ func init() {
func IsWindowsExecutable(path string) bool {
return execExts[strings.ToLower(filepath.Ext(path))]
}
func IsDeleted(ffs fs.Filesystem, name string) bool {
if _, err := ffs.Lstat(name); fs.IsNotExist(err) {
return true
}
switch TraversesSymlink(ffs, filepath.Dir(name)).(type) {
case *NotADirectoryError, *TraversesSymlinkError:
return true
}
return false
}
+2 -2
View File
@@ -26,7 +26,7 @@ func DebugSymlinkForTestsOnly(oldname, newname string) error {
// '/' does not work in link's content
oldname = filepath.FromSlash(oldname)
// need the exact location of the oldname when its relative to determine if its a directory
// need the exact location of the oldname when it's relative to determine if it's a directory
destpath := oldname
if !filepath.IsAbs(oldname) {
destpath = filepath.Dir(newname) + `\` + oldname
@@ -75,7 +75,7 @@ func fixLongPath(path string) string {
// minus 12)." Since MAX_PATH is 260, 260 - 12 = 248.
//
// The MSDN docs appear to say that a normal path that is 248 bytes long
// will work; empirically the path must be less then 248 bytes long.
// will work; empirically the path must be less than 248 bytes long.
if len(path) < 248 {
// Don't fix. (This is how Go 1.7 and earlier worked,
// not automatically generating the \\?\ form)
+65 -17
View File
@@ -10,6 +10,7 @@ import (
"encoding/binary"
"errors"
"fmt"
"runtime"
"time"
"github.com/syncthing/syncthing/lib/rand"
@@ -126,6 +127,70 @@ func (f FileInfo) IsEmpty() bool {
return f.Version.Counters == nil
}
// IsEquivalent checks that the two file infos represent the same actual file content,
// i.e. it does purposely not check only selected (see below) struct members.
// Permissions (config) and blocks (scanning) can be excluded from the comparison.
// Any file info is not "equivalent", if it has different
// - type
// - deleted flag
// - invalid flag
// - permissions, unless they are ignored
// A file is not "equivalent", if it has different
// - modification time
// - size
// - blocks, unless there are no blocks to compare (scanning)
// A symlink is not "equivalent", if it has different
// - target
// A directory does not have anything specific to check.
func (f FileInfo) IsEquivalent(other FileInfo, ignorePerms bool, ignoreBlocks bool) bool {
if f.Name != other.Name || f.Type != other.Type || f.Deleted != other.Deleted || f.Invalid != other.Invalid {
return false
}
if !ignorePerms && !f.NoPermissions && !other.NoPermissions && !PermsEqual(f.Permissions, other.Permissions) {
return false
}
switch f.Type {
case FileInfoTypeFile:
return f.Size == other.Size && f.ModTime().Equal(other.ModTime()) && (ignoreBlocks || BlocksEqual(f.Blocks, other.Blocks))
case FileInfoTypeSymlink:
return f.SymlinkTarget == other.SymlinkTarget
case FileInfoTypeDirectory:
return true
}
return false
}
func PermsEqual(a, b uint32) bool {
switch runtime.GOOS {
case "windows":
// There is only writeable and read only, represented for user, group
// and other equally. We only compare against user.
return a&0600 == b&0600
default:
// All bits count
return a&0777 == b&0777
}
}
// BlocksEqual returns whether two slices of blocks are exactly the same hash
// and index pair wise.
func BlocksEqual(a, b []BlockInfo) bool {
if len(b) != len(a) {
return false
}
for i, sblk := range a {
if !bytes.Equal(sblk.Hash, b[i].Hash) {
return false
}
}
return true
}
func (f *FileInfo) Invalidate(invalidatedBy ShortID) {
f.Invalid = true
f.ModifiedBy = invalidatedBy
@@ -133,23 +198,6 @@ func (f *FileInfo) Invalidate(invalidatedBy ShortID) {
f.Sequence = 0
}
func (f *FileInfo) InvalidatedCopy(invalidatedBy ShortID) FileInfo {
copy := *f
copy.Invalidate(invalidatedBy)
return copy
}
func (f *FileInfo) DeletedCopy(deletedBy ShortID) FileInfo {
copy := *f
copy.Size = 0
copy.ModifiedBy = deletedBy
copy.Deleted = true
copy.Version = f.Version.Update(deletedBy)
copy.Sequence = 0
copy.Blocks = nil
return copy
}
func (b BlockInfo) String() string {
return fmt.Sprintf("Block{%d/%d/%d/%x}", b.Offset, b.Size, b.WeakHash, b.Hash)
}
+12 -38
View File
@@ -24,10 +24,6 @@ var (
quickCfg = &quick.Config{}
)
const (
fileSize = 1 << 40
)
func TestPing(t *testing.T) {
ar, aw := io.Pipe()
br, bw := io.Pipe()
@@ -247,6 +243,12 @@ func TestMarshalledIndexMessageSize(t *testing.T) {
return
}
const (
maxMessageSize = MaxMessageLen
fileSize = 1 << 40
blockSize = BlockSize
)
f := FileInfo{
Name: "a normal length file name withoout any weird stuff.txt",
Type: FileInfoTypeFile,
@@ -254,12 +256,12 @@ func TestMarshalledIndexMessageSize(t *testing.T) {
Permissions: 0666,
ModifiedS: time.Now().Unix(),
Version: Vector{Counters: []Counter{{ID: 1 << 60, Value: 1}, {ID: 2 << 60, Value: 1}}},
Blocks: make([]BlockInfo, fileSize/BlockSize),
Blocks: make([]BlockInfo, fileSize/blockSize),
}
for i := 0; i < fileSize/BlockSize; i++ {
f.Blocks[i].Offset = int64(i) * BlockSize
f.Blocks[i].Size = BlockSize
for i := 0; i < fileSize/blockSize; i++ {
f.Blocks[i].Offset = int64(i) * blockSize
f.Blocks[i].Size = blockSize
f.Blocks[i].Hash = []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 20, 1, 2, 3, 4, 5, 6, 7, 8, 9, 30, 1, 2}
}
@@ -269,8 +271,8 @@ func TestMarshalledIndexMessageSize(t *testing.T) {
}
msgSize := idx.ProtoSize()
if msgSize > MaxMessageLen {
t.Errorf("Message size %d bytes is larger than max %d", msgSize, MaxMessageLen)
if msgSize > maxMessageSize {
t.Errorf("Message size %d bytes is larger than max %d", msgSize, maxMessageSize)
}
}
@@ -398,31 +400,3 @@ func TestCheckConsistency(t *testing.T) {
}
}
}
func TestCopyFileInfo(t *testing.T) {
f := FileInfo{
Name: "a normal length file name withoout any weird stuff.txt",
Type: FileInfoTypeFile,
Size: fileSize,
Permissions: 0666,
ModifiedS: time.Now().Unix(),
Version: Vector{Counters: []Counter{{ID: 1 << 60, Value: 1}, {ID: 2 << 60, Value: 1}}},
Blocks: make([]BlockInfo, fileSize/BlockSize),
}
del := f.DeletedCopy(LocalDeviceID.Short())
if f.Deleted {
t.Errorf("Source file info was deleted on copy")
}
if !del.Deleted {
t.Errorf("Returned file info was not deleted on copy")
}
inv := f.InvalidatedCopy(LocalDeviceID.Short())
if f.Invalid {
t.Errorf("Source file info was invalid on copy")
}
if !inv.Invalid {
t.Errorf("Returned file info was not invalid on copy")
}
}
+12
View File
@@ -109,6 +109,18 @@ func (v Vector) Counter(id ShortID) uint64 {
return 0
}
// DropOthers removes all counters, keeping only the one with given id. If there
// is no such counter, an empty Vector is returned.
func (v Vector) DropOthers(id ShortID) Vector {
for i, c := range v.Counters {
if c.ID == id {
v.Counters = v.Counters[i : i+1]
return v
}
}
return Vector{}
}
// Ordering represents the relationship between two Vectors.
type Ordering int
+9 -9
View File
@@ -65,15 +65,15 @@ type parallelHasher struct {
fs fs.Filesystem
blockSize int
workers int
outbox chan<- ScanResult
inbox <-chan ScanResult
outbox chan<- protocol.FileInfo
inbox <-chan protocol.FileInfo
counter Counter
done chan<- struct{}
useWeakHashes bool
wg sync.WaitGroup
}
func newParallelHasher(ctx context.Context, fs fs.Filesystem, blockSize, workers int, outbox chan<- ScanResult, inbox <-chan ScanResult, counter Counter, done chan<- struct{}, useWeakHashes bool) {
func newParallelHasher(ctx context.Context, fs fs.Filesystem, blockSize, workers int, outbox chan<- protocol.FileInfo, inbox <-chan protocol.FileInfo, counter Counter, done chan<- struct{}, useWeakHashes bool) {
ph := &parallelHasher{
fs: fs,
blockSize: blockSize,
@@ -104,25 +104,25 @@ func (ph *parallelHasher) hashFiles(ctx context.Context) {
return
}
if f.New.IsDirectory() || f.New.IsDeleted() {
if f.IsDirectory() || f.IsDeleted() {
panic("Bug. Asked to hash a directory or a deleted file.")
}
blocks, err := HashFile(ctx, ph.fs, f.New.Name, ph.blockSize, ph.counter, ph.useWeakHashes)
blocks, err := HashFile(ctx, ph.fs, f.Name, ph.blockSize, ph.counter, ph.useWeakHashes)
if err != nil {
l.Debugln("hash error:", f.New.Name, err)
l.Debugln("hash error:", f.Name, err)
continue
}
f.New.Blocks = blocks
f.Blocks = blocks
// The size we saw when initially deciding to hash the file
// might not have been the size it actually had when we hashed
// it. Update the size from the block list.
f.New.Size = 0
f.Size = 0
for _, b := range blocks {
f.New.Size += int64(b.Size)
f.Size += int64(b.Size)
}
select {
+154 -315
View File
@@ -8,9 +8,7 @@ package scanner
import (
"context"
"errors"
"runtime"
"strings"
"sync/atomic"
"time"
"unicode/utf8"
@@ -50,8 +48,8 @@ type Config struct {
Matcher *ignore.Matcher
// Number of hours to keep temporary files for
TempLifetime time.Duration
// Walks over file infos as present in the db before the scan alphabetically.
Have haveWalker
// If CurrentFiler is not nil, it is queried for the current file before rescanning.
CurrentFiler CurrentFiler
// The Filesystem provides an abstraction on top of the actual filesystem.
Filesystem fs.Filesystem
// If IgnorePerms is true, changes to permission bits will not be
@@ -72,28 +70,16 @@ type Config struct {
UseWeakHashes bool
}
type haveWalker interface {
// Walk passes all local file infos from the db which start with prefix
// to out and aborts early if ctx is cancelled.
Walk(prefix string, ctx context.Context, out chan<- protocol.FileInfo)
type CurrentFiler interface {
// CurrentFile returns the file as seen at last scan.
CurrentFile(name string) (protocol.FileInfo, bool)
}
type fsWalkResult struct {
path string
info fs.FileInfo
err error
}
type ScanResult struct {
New protocol.FileInfo
Old protocol.FileInfo
}
func Walk(ctx context.Context, cfg Config) chan ScanResult {
func Walk(ctx context.Context, cfg Config) chan protocol.FileInfo {
w := walker{cfg}
if w.Have == nil {
w.Have = noHaveWalker{}
if w.CurrentFiler == nil {
w.CurrentFiler = noCurrentFiler{}
}
if w.Filesystem == nil {
panic("no filesystem specified")
@@ -111,19 +97,25 @@ type walker struct {
// Walk returns the list of files found in the local folder by scanning the
// file system. Files are blockwise hashed.
func (w *walker) walk(ctx context.Context) chan ScanResult {
func (w *walker) walk(ctx context.Context) chan protocol.FileInfo {
l.Debugln("Walk", w.Subs, w.BlockSize, w.Matcher)
haveChan := make(chan protocol.FileInfo)
haveCtx, haveCancel := context.WithCancel(ctx)
go w.dbWalkerRoutine(haveCtx, haveChan)
toHashChan := make(chan protocol.FileInfo)
finishedChan := make(chan protocol.FileInfo)
fsChan := make(chan fsWalkResult)
go w.fsWalkerRoutine(ctx, fsChan, haveCancel)
toHashChan := make(chan ScanResult)
finishedChan := make(chan ScanResult)
go w.processWalkResults(ctx, fsChan, haveChan, toHashChan, finishedChan)
// A routine which walks the filesystem tree, and sends files which have
// been modified to the counter routine.
go func() {
hashFiles := w.walkAndHashFiles(ctx, toHashChan, finishedChan)
if len(w.Subs) == 0 {
w.Filesystem.Walk(".", hashFiles)
} else {
for _, sub := range w.Subs {
w.Filesystem.Walk(sub, hashFiles)
}
}
close(toHashChan)
}()
// We're not required to emit scan progress events, just kick off hashers,
// and feed inputs directly from the walker.
@@ -147,15 +139,15 @@ func (w *walker) walk(ctx context.Context) chan ScanResult {
// Parallel hasher is stopped by this routine when we close the channel over
// which it receives the files we ask it to hash.
go func() {
var filesToHash []ScanResult
var filesToHash []protocol.FileInfo
var total int64 = 1
for file := range toHashChan {
filesToHash = append(filesToHash, file)
total += file.New.Size
total += file.Size
}
realToHashChan := make(chan ScanResult)
realToHashChan := make(chan protocol.FileInfo)
done := make(chan struct{})
progress := newByteCounter()
@@ -191,7 +183,7 @@ func (w *walker) walk(ctx context.Context) chan ScanResult {
loop:
for _, file := range filesToHash {
l.Debugln("real to hash:", file.New.Name)
l.Debugln("real to hash:", file.Name)
select {
case realToHashChan <- file:
case <-ctx.Done():
@@ -204,49 +196,15 @@ func (w *walker) walk(ctx context.Context) chan ScanResult {
return finishedChan
}
// dbWalkerRoutine walks the db and sends back file infos to be compared to scan results.
func (w *walker) dbWalkerRoutine(ctx context.Context, haveChan chan<- protocol.FileInfo) {
defer close(haveChan)
if len(w.Subs) == 0 {
w.Have.Walk("", ctx, haveChan)
return
}
for _, sub := range w.Subs {
select {
case <-ctx.Done():
return
default:
}
w.Have.Walk(sub, ctx, haveChan)
}
}
// fsWalkerRoutine walks the filesystem tree and sends back file infos and potential
// errors at paths that need to be processed.
func (w *walker) fsWalkerRoutine(ctx context.Context, fsChan chan<- fsWalkResult, haveCancel context.CancelFunc) {
defer close(fsChan)
walkFn := w.createFSWalkFn(ctx, fsChan)
if len(w.Subs) == 0 {
if err := w.Filesystem.Walk(".", walkFn); err != nil {
haveCancel()
}
return
}
for _, sub := range w.Subs {
if err := w.Filesystem.Walk(sub, walkFn); err != nil {
haveCancel()
break
}
}
}
func (w *walker) createFSWalkFn(ctx context.Context, fsChan chan<- fsWalkResult) fs.WalkFunc {
func (w *walker) walkAndHashFiles(ctx context.Context, fchan, dchan chan protocol.FileInfo) fs.WalkFunc {
now := time.Now()
return func(path string, info fs.FileInfo, err error) error {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
// Return value used when we are returning early and don't want to
// process the item. For directories, this means do-not-descend.
var skip error // nil
@@ -256,9 +214,7 @@ func (w *walker) createFSWalkFn(ctx context.Context, fsChan chan<- fsWalkResult)
}
if err != nil {
if sendErr := fsWalkError(ctx, fsChan, path, err); sendErr != nil {
return sendErr
}
l.Debugln("error:", path, info, err)
return skip
}
@@ -266,6 +222,12 @@ func (w *walker) createFSWalkFn(ctx context.Context, fsChan chan<- fsWalkResult)
return nil
}
info, err = w.Filesystem.Lstat(path)
// An error here would be weird as we've already gotten to this point, but act on it nonetheless
if err != nil {
return skip
}
if fs.IsTemporary(path) {
l.Debugln("temporary:", path)
if info.IsRegular() && info.ModTime().Add(w.TempLifetime).Before(now) {
@@ -276,58 +238,85 @@ func (w *walker) createFSWalkFn(ctx context.Context, fsChan chan<- fsWalkResult)
}
if fs.IsInternal(path) {
l.Debugln("skip walking (internal):", path)
l.Debugln("ignored (internal):", path)
return skip
}
if w.Matcher.Match(path).IsIgnored() {
l.Debugln("skip walking (patterns):", path)
l.Debugln("ignored (patterns):", path)
return skip
}
if !utf8.ValidString(path) {
if err := fsWalkError(ctx, fsChan, path, errors.New("path isn't a valid utf8 string")); err != nil {
return err
}
l.Warnf("File name %q is not in UTF8 encoding; skipping.", path)
return skip
}
path, shouldSkip := w.normalizePath(path, info)
if shouldSkip {
if err := fsWalkError(ctx, fsChan, path, errors.New("failed to normalize path")); err != nil {
return skip
}
switch {
case info.IsSymlink():
if err := w.walkSymlink(ctx, path, dchan); err != nil {
return err
}
return skip
}
if info.IsDir() {
// under no circumstances shall we descend into a symlink
return fs.SkipDir
}
return nil
select {
case fsChan <- fsWalkResult{
path: path,
info: info,
err: nil,
}:
case <-ctx.Done():
return ctx.Err()
}
case info.IsDir():
err = w.walkDir(ctx, path, info, dchan)
// under no circumstances shall we descend into a symlink
if info.IsSymlink() && info.IsDir() {
l.Debugln("skip walking (symlinked directory):", path)
return skip
case info.IsRegular():
err = w.walkRegular(ctx, path, info, fchan)
}
return err
}
}
func fsWalkError(ctx context.Context, dst chan<- fsWalkResult, path string, err error) error {
func (w *walker) walkRegular(ctx context.Context, relPath string, info fs.FileInfo, fchan chan protocol.FileInfo) error {
curMode := uint32(info.Mode())
if runtime.GOOS == "windows" && osutil.IsWindowsExecutable(relPath) {
curMode |= 0111
}
cf, ok := w.CurrentFiler.CurrentFile(relPath)
f := protocol.FileInfo{
Name: relPath,
Type: protocol.FileInfoTypeFile,
Version: cf.Version.Update(w.ShortID),
Permissions: curMode & uint32(maskModePerm),
NoPermissions: w.IgnorePerms,
ModifiedS: info.ModTime().Unix(),
ModifiedNs: int32(info.ModTime().Nanosecond()),
ModifiedBy: w.ShortID,
Size: info.Size(),
}
if ok {
if cf.IsEquivalent(f, w.IgnorePerms, true) {
return nil
}
if cf.Invalid {
// We do not want to override the global version with the file we
// currently have. Keeping only our local counter makes sure we are in
// conflict with any other existing versions, which will be resolved by
// the normal pulling mechanisms.
f.Version = f.Version.DropOthers(w.ShortID)
}
l.Debugln("rescan:", cf, info.ModTime().Unix(), info.Mode()&fs.ModePerm)
}
l.Debugln("to hash:", relPath, f)
select {
case dst <- fsWalkResult{
path: path,
info: nil,
err: err,
}:
case fchan <- f:
case <-ctx.Done():
return ctx.Err()
}
@@ -335,199 +324,51 @@ func fsWalkError(ctx context.Context, dst chan<- fsWalkResult, path string, err
return nil
}
func (w *walker) processWalkResults(ctx context.Context, fsChan <-chan fsWalkResult, haveChan <-chan protocol.FileInfo, toHashChan, finishedChan chan<- ScanResult) {
ctxChan := ctx.Done()
fsRes, fsChanOpen := <-fsChan
currDBFile, haveChanOpen := <-haveChan
for fsChanOpen {
if haveChanOpen {
// File infos below an error walking the filesystem tree
// may be marked as ignored but should not be deleted.
if fsRes.err != nil && (strings.HasPrefix(currDBFile.Name, fsRes.path+string(fs.PathSeparator)) || fsRes.path == ".") {
w.checkIgnoredAndInvalidate(currDBFile, finishedChan, ctxChan)
currDBFile, haveChanOpen = <-haveChan
continue
}
// Delete file infos that were not encountered when
// walking the filesystem tree, except on error (see
// above) or if they are ignored.
if currDBFile.Name < fsRes.path {
w.checkIgnoredAndDelete(currDBFile, finishedChan, ctxChan)
currDBFile, haveChanOpen = <-haveChan
continue
}
}
func (w *walker) walkDir(ctx context.Context, relPath string, info fs.FileInfo, dchan chan protocol.FileInfo) error {
cf, ok := w.CurrentFiler.CurrentFile(relPath)
var oldFile protocol.FileInfo
if haveChanOpen && currDBFile.Name == fsRes.path {
oldFile = currDBFile
currDBFile, haveChanOpen = <-haveChan
}
if fsRes.err != nil {
if fs.IsNotExist(fsRes.err) && !oldFile.IsEmpty() && !oldFile.Deleted {
select {
case finishedChan <- ScanResult{
New: oldFile.DeletedCopy(w.ShortID),
Old: oldFile,
}:
case <-ctx.Done():
return
}
}
fsRes, fsChanOpen = <-fsChan
continue
}
switch {
case fsRes.info.IsDir():
w.walkDir(ctx, fsRes.path, fsRes.info, oldFile, finishedChan)
case fsRes.info.IsSymlink():
w.walkSymlink(ctx, fsRes.path, oldFile, finishedChan)
case fsRes.info.IsRegular():
w.walkRegular(ctx, fsRes.path, fsRes.info, oldFile, toHashChan)
}
fsRes, fsChanOpen = <-fsChan
f := protocol.FileInfo{
Name: relPath,
Type: protocol.FileInfoTypeDirectory,
Version: cf.Version.Update(w.ShortID),
Permissions: uint32(info.Mode() & maskModePerm),
NoPermissions: w.IgnorePerms,
ModifiedS: info.ModTime().Unix(),
ModifiedNs: int32(info.ModTime().Nanosecond()),
ModifiedBy: w.ShortID,
}
// Filesystem tree walking finished, if there is anything left in the
// db, mark it as deleted, except when it's ignored.
if haveChanOpen {
w.checkIgnoredAndDelete(currDBFile, finishedChan, ctxChan)
for currDBFile = range haveChan {
w.checkIgnoredAndDelete(currDBFile, finishedChan, ctxChan)
if ok {
if cf.IsEquivalent(f, w.IgnorePerms, true) {
return nil
}
if cf.Invalid {
// We do not want to override the global version with the file we
// currently have. Keeping only our local counter makes sure we are in
// conflict with any other existing versions, which will be resolved by
// the normal pulling mechanisms.
f.Version = f.Version.DropOthers(w.ShortID)
}
}
close(toHashChan)
}
func (w *walker) checkIgnoredAndDelete(f protocol.FileInfo, finishedChan chan<- ScanResult, done <-chan struct{}) {
if w.checkIgnoredAndInvalidate(f, finishedChan, done) {
return
}
if !f.Invalid && !f.Deleted {
select {
case finishedChan <- ScanResult{
New: f.DeletedCopy(w.ShortID),
Old: f,
}:
case <-done:
}
}
}
func (w *walker) checkIgnoredAndInvalidate(f protocol.FileInfo, finishedChan chan<- ScanResult, done <-chan struct{}) bool {
if !w.Matcher.Match(f.Name).IsIgnored() {
return false
}
if !f.Invalid {
select {
case finishedChan <- ScanResult{
New: f.InvalidatedCopy(w.ShortID),
Old: f,
}:
case <-done:
}
}
return true
}
func (w *walker) walkRegular(ctx context.Context, relPath string, info fs.FileInfo, cf protocol.FileInfo, toHashChan chan<- ScanResult) {
curMode := uint32(info.Mode())
if runtime.GOOS == "windows" && osutil.IsWindowsExecutable(relPath) {
curMode |= 0111
}
// A file is "unchanged", if it
// - exists
// - has the same permissions as previously, unless we are ignoring permissions
// - was not marked deleted (since it apparently exists now)
// - had the same modification time as it has now
// - was not a directory previously (since it's a file now)
// - was not a symlink (since it's a file now)
// - was not invalid (since it looks valid now)
// - has the same size as previously
if !cf.IsEmpty() {
permUnchanged := w.IgnorePerms || !cf.HasPermissionBits() || PermsEqual(cf.Permissions, curMode)
if permUnchanged && !cf.IsDeleted() && cf.ModTime().Equal(info.ModTime()) && !cf.IsDirectory() &&
!cf.IsSymlink() && !cf.IsInvalid() && cf.Size == info.Size() {
return
}
l.Debugln("rescan:", cf, info.ModTime().Unix(), info.Mode()&fs.ModePerm)
}
f := ScanResult{
New: protocol.FileInfo{
Name: relPath,
Type: protocol.FileInfoTypeFile,
Version: cf.Version.Update(w.ShortID),
Permissions: curMode & uint32(maskModePerm),
NoPermissions: w.IgnorePerms,
ModifiedS: info.ModTime().Unix(),
ModifiedNs: int32(info.ModTime().Nanosecond()),
ModifiedBy: w.ShortID,
Size: info.Size(),
},
Old: cf,
}
l.Debugln("to hash:", relPath, f)
select {
case toHashChan <- f:
case <-ctx.Done():
}
}
func (w *walker) walkDir(ctx context.Context, relPath string, info fs.FileInfo, cf protocol.FileInfo, finishedChan chan<- ScanResult) {
// A directory is "unchanged", if it
// - exists
// - has the same permissions as previously, unless we are ignoring permissions
// - was not marked deleted (since it apparently exists now)
// - was a directory previously (not a file or something else)
// - was not a symlink (since it's a directory now)
// - was not invalid (since it looks valid now)
if !cf.IsEmpty() {
permUnchanged := w.IgnorePerms || !cf.HasPermissionBits() || PermsEqual(cf.Permissions, uint32(info.Mode()))
if permUnchanged && !cf.IsDeleted() && cf.IsDirectory() && !cf.IsSymlink() && !cf.IsInvalid() {
return
}
}
f := ScanResult{
New: protocol.FileInfo{
Name: relPath,
Type: protocol.FileInfoTypeDirectory,
Version: cf.Version.Update(w.ShortID),
Permissions: uint32(info.Mode() & maskModePerm),
NoPermissions: w.IgnorePerms,
ModifiedS: info.ModTime().Unix(),
ModifiedNs: int32(info.ModTime().Nanosecond()),
ModifiedBy: w.ShortID,
},
Old: cf,
}
l.Debugln("dir:", relPath, f)
select {
case finishedChan <- f:
case dchan <- f:
case <-ctx.Done():
return ctx.Err()
}
return nil
}
// walkSymlink returns nil or an error, if the error is of the nature that
// it should stop the entire walk.
func (w *walker) walkSymlink(ctx context.Context, relPath string, cf protocol.FileInfo, finishedChan chan<- ScanResult) {
func (w *walker) walkSymlink(ctx context.Context, relPath string, dchan chan protocol.FileInfo) error {
// Symlinks are not supported on Windows. We ignore instead of returning
// an error.
if runtime.GOOS == "windows" {
return
return nil
}
// We always rehash symlinks as they have no modtime or
@@ -538,36 +379,42 @@ func (w *walker) walkSymlink(ctx context.Context, relPath string, cf protocol.Fi
target, err := w.Filesystem.ReadSymlink(relPath)
if err != nil {
l.Debugln("readlink error:", relPath, err)
return
return nil
}
// A symlink is "unchanged", if
// - it exists
// - it wasn't deleted (because it isn't now)
// - it was a symlink
// - it wasn't invalid
// - the target was the same
if !cf.IsEmpty() && !cf.IsDeleted() && cf.IsSymlink() && !cf.IsInvalid() && cf.SymlinkTarget == target {
return
cf, ok := w.CurrentFiler.CurrentFile(relPath)
f := protocol.FileInfo{
Name: relPath,
Type: protocol.FileInfoTypeSymlink,
Version: cf.Version.Update(w.ShortID),
NoPermissions: true, // Symlinks don't have permissions of their own
SymlinkTarget: target,
ModifiedBy: w.ShortID,
}
f := ScanResult{
New: protocol.FileInfo{
Name: relPath,
Type: protocol.FileInfoTypeSymlink,
Version: cf.Version.Update(w.ShortID),
NoPermissions: true, // Symlinks don't have permissions of their own
SymlinkTarget: target,
},
Old: cf,
if ok {
if cf.IsEquivalent(f, w.IgnorePerms, true) {
return nil
}
if cf.Invalid {
// We do not want to override the global version with the file we
// currently have. Keeping only our local counter makes sure we are in
// conflict with any other existing versions, which will be resolved by
// the normal pulling mechanisms.
f.Version = f.Version.DropOthers(w.ShortID)
}
}
l.Debugln("symlink changedb:", relPath, f)
select {
case finishedChan <- f:
case dchan <- f:
case <-ctx.Done():
return ctx.Err()
}
return nil
}
// normalizePath returns the normalized relative path (possibly after fixing
@@ -633,18 +480,6 @@ func (w *walker) normalizePath(path string, info fs.FileInfo) (normPath string,
return normPath, false
}
func PermsEqual(a, b uint32) bool {
switch runtime.GOOS {
case "windows":
// There is only writeable and read only, represented for user, group
// and other equally. We only compare against user.
return a&0600 == b&0600
default:
// All bits count
return a&0777 == b&0777
}
}
// A byteCounter gets bytes added to it via Update() and then provides the
// Total() and one minute moving average Rate() in bytes per second.
type byteCounter struct {
@@ -690,6 +525,10 @@ func (c *byteCounter) Close() {
close(c.stop)
}
type noHaveWalker struct{}
// A no-op CurrentFiler
func (noHaveWalker) Walk(prefix string, ctx context.Context, out chan<- protocol.FileInfo) {}
type noCurrentFiler struct{}
func (noCurrentFiler) CurrentFile(name string) (protocol.FileInfo, bool) {
return protocol.FileInfo{}, false
}
+97 -85
View File
@@ -69,7 +69,7 @@ func TestWalkSub(t *testing.T) {
Matcher: ignores,
Hashers: 2,
})
var files []ScanResult
var files []protocol.FileInfo
for f := range fchan {
files = append(files, f)
}
@@ -80,10 +80,10 @@ func TestWalkSub(t *testing.T) {
if len(files) != 2 {
t.Fatalf("Incorrect length %d != 2", len(files))
}
if files[0].New.Name != "dir2" {
if files[0].Name != "dir2" {
t.Errorf("Incorrect file %v != dir2", files[0])
}
if files[1].New.Name != filepath.Join("dir2", "cfile") {
if files[1].Name != filepath.Join("dir2", "cfile") {
t.Errorf("Incorrect file %v != dir2/cfile", files[1])
}
}
@@ -103,7 +103,7 @@ func TestWalk(t *testing.T) {
Hashers: 2,
})
var tmp []ScanResult
var tmp []protocol.FileInfo
for f := range fchan {
tmp = append(tmp, f)
}
@@ -251,9 +251,9 @@ func TestNormalization(t *testing.T) {
}
func TestIssue1507(t *testing.T) {
w := &walker{Config{Matcher: ignore.New(fs.NewFilesystem(fs.FilesystemTypeBasic, "."))}}
c := make(chan fsWalkResult, 100)
fn := w.createFSWalkFn(context.TODO(), c)
w := &walker{}
c := make(chan protocol.FileInfo, 100)
fn := w.walkAndHashFiles(context.TODO(), c, c)
fn("", nil, protocol.ErrClosed)
}
@@ -274,14 +274,15 @@ func TestWalkSymlinkUnix(t *testing.T) {
// Scan it
files, _ := walkDir(fs.NewFilesystem(fs.FilesystemTypeBasic, "_symlinks"), path)
// Verify that we got one symlink and with the correct attributes
if len(files) != 1 {
t.Errorf("expected 1 symlink, not %d", len(files))
}
if len(files[0].New.Blocks) != 0 {
t.Errorf("expected zero blocks for symlink, not %d", len(files[0].New.Blocks))
if len(files[0].Blocks) != 0 {
t.Errorf("expected zero blocks for symlink, not %d", len(files[0].Blocks))
}
if files[0].New.SymlinkTarget != "../testdata" {
t.Errorf("expected symlink to have target destination, not %q", files[0].New.SymlinkTarget)
if files[0].SymlinkTarget != "../testdata" {
t.Errorf("expected symlink to have target destination, not %q", files[0].SymlinkTarget)
}
}
}
@@ -341,7 +342,7 @@ func TestWalkRootSymlink(t *testing.T) {
}
}
func walkDir(fs fs.Filesystem, dir string) ([]ScanResult, error) {
func walkDir(fs fs.Filesystem, dir string) ([]protocol.FileInfo, error) {
fchan := Walk(context.TODO(), Config{
Filesystem: fs,
Subs: []string{dir},
@@ -350,7 +351,7 @@ func walkDir(fs fs.Filesystem, dir string) ([]ScanResult, error) {
Hashers: 2,
})
var tmp []ScanResult
var tmp []protocol.FileInfo
for f := range fchan {
tmp = append(tmp, f)
}
@@ -359,14 +360,14 @@ func walkDir(fs fs.Filesystem, dir string) ([]ScanResult, error) {
return tmp, nil
}
type fileList []ScanResult
type fileList []protocol.FileInfo
func (l fileList) Len() int {
return len(l)
}
func (l fileList) Less(a, b int) bool {
return l[a].New.Name < l[b].New.Name
return l[a].Name < l[b].Name
}
func (l fileList) Swap(a, b int) {
@@ -376,12 +377,12 @@ func (l fileList) Swap(a, b int) {
func (l fileList) testfiles() testfileList {
testfiles := make(testfileList, len(l))
for i, f := range l {
if len(f.New.Blocks) > 1 {
if len(f.Blocks) > 1 {
panic("simple test case stuff only supports a single block per file")
}
testfiles[i] = testfile{name: f.New.Name, length: f.New.FileSize()}
if len(f.New.Blocks) == 1 {
testfiles[i].hash = fmt.Sprintf("%x", f.New.Blocks[0].Hash)
testfiles[i] = testfile{name: f.Name, length: f.FileSize()}
if len(f.Blocks) == 1 {
testfiles[i].hash = fmt.Sprintf("%x", f.Blocks[0].Hash)
}
}
return testfiles
@@ -464,13 +465,13 @@ func TestStopWalk(t *testing.T) {
for {
f := <-fchan
t.Log("Scanned", f)
if f.New.IsDirectory() {
if len(f.New.Name) == 0 || f.New.Permissions == 0 {
if f.IsDirectory() {
if len(f.Name) == 0 || f.Permissions == 0 {
t.Error("Bad directory entry", f)
}
dirs++
} else {
if len(f.New.Name) == 0 || len(f.New.Blocks) == 0 || f.New.Permissions == 0 {
if len(f.Name) == 0 || len(f.Blocks) == 0 || f.Permissions == 0 {
t.Error("Bad file entry", f)
}
files++
@@ -497,6 +498,76 @@ func TestStopWalk(t *testing.T) {
}
}
func TestIssue4799(t *testing.T) {
tmp, err := ioutil.TempDir("", "")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmp)
fs := fs.NewFilesystem(fs.FilesystemTypeBasic, tmp)
fd, err := fs.Create("foo")
if err != nil {
t.Fatal(err)
}
fd.Close()
files, err := walkDir(fs, "/foo")
if err != nil {
t.Fatal(err)
}
if len(files) != 1 || files[0].Name != "foo" {
t.Error(`Received unexpected file infos when walking "/foo"`, files)
}
}
func TestIssue4841(t *testing.T) {
tmp, err := ioutil.TempDir("", "")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(tmp)
fs := fs.NewFilesystem(fs.FilesystemTypeBasic, tmp)
fd, err := fs.Create("foo")
if err != nil {
panic(err)
}
fd.Close()
fchan := Walk(context.TODO(), Config{
Filesystem: fs,
Subs: nil,
BlockSize: 128 * 1024,
AutoNormalize: true,
Hashers: 2,
CurrentFiler: fakeCurrentFiler{
"foo": {
Name: "foo",
Type: protocol.FileInfoTypeFile,
Invalid: true,
Version: protocol.Vector{}.Update(1),
},
},
ShortID: protocol.LocalDeviceID.Short(),
})
var files []protocol.FileInfo
for f := range fchan {
files = append(files, f)
}
sort.Sort(fileList(files))
if len(files) != 1 {
t.Fatalf("Expected 1 file, got %d: %v", len(files), files)
}
if expected := (protocol.Vector{}.Update(protocol.LocalDeviceID.Short())); !files[0].Version.Equal(expected) {
t.Fatalf("Expected Version == %v, got %v", expected, files[0].Version)
}
}
// Verify returns nil or an error describing the mismatch between the block
// list and actual reader contents
func verify(r io.Reader, blocksize int, blocks []protocol.BlockInfo) error {
@@ -529,68 +600,9 @@ func verify(r io.Reader, blocksize int, blocks []protocol.BlockInfo) error {
return nil
}
// The following (randomish) scenario produced an error uncovered by integration tests
func TestWalkIntegration(t *testing.T) {
tmpDir, err := ioutil.TempDir(".", "_request-")
if err != nil {
panic("Failed to create temporary testing dir")
}
defer os.RemoveAll(tmpDir)
type fakeCurrentFiler map[string]protocol.FileInfo
fs := fs.NewFilesystem(fs.FilesystemTypeBasic, tmpDir)
fs.Mkdir("a", 0777)
toDel := filepath.Join("a", "b")
for _, f := range []string{"b", toDel} {
fi, err := fs.Create(f)
if err != nil {
panic(err)
}
fi.Close()
}
conf := Config{
Filesystem: fs,
BlockSize: 128 * 1024,
Hashers: 2,
}
rchan := Walk(context.TODO(), conf)
var res []ScanResult
for r := range rchan {
res = append(res, r)
}
sort.Sort(fileList(res))
thw := make([]protocol.FileInfo, 0, len(res))
for _, r := range res {
thw = append(thw, r.New)
}
conf.Have = testHaveWalker(thw)
if err = fs.Remove(toDel); err != nil {
panic(err)
}
rchan = Walk(context.TODO(), conf)
for r := range rchan {
if r.New.Name != toDel {
t.Fatalf("Received unexpected result %v", r)
}
}
}
type testHaveWalker []protocol.FileInfo
func (thw testHaveWalker) Walk(prefix string, ctx context.Context, out chan<- protocol.FileInfo) {
if prefix != "" {
panic("cannot walk with prefix")
}
for _, f := range thw {
select {
case out <- f:
case <-ctx.Done():
return
}
}
func (fcf fakeCurrentFiler) CurrentFile(name string) (protocol.FileInfo, bool) {
f, ok := fcf[name]
return f, ok
}
+1 -1
View File
@@ -122,7 +122,7 @@ func TestSelectedReleaseMacOS(t *testing.T) {
t.Skip("macOS only")
}
// The alterantives that we expect should work
// The alternatives that we expect should work
assetNames := []string{
"syncthing-macos-amd64-v0.14.47.tar.gz",
"syncthing-macosx-amd64-v0.14.47.tar.gz",
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "STDISCOSRV" "1" "Feb 05, 2018" "v0.14" "Syncthing"
.TH "STDISCOSRV" "1" "Mar 04, 2018" "v0.14" "Syncthing"
.SH NAME
stdiscosrv \- Syncthing Discovery Server
.
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "STRELAYSRV" "1" "Feb 05, 2018" "v0.14" "Syncthing"
.TH "STRELAYSRV" "1" "Mar 04, 2018" "v0.14" "Syncthing"
.SH NAME
strelaysrv \- Syncthing Relay Server
.
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-BEP" "7" "Feb 05, 2018" "v0.14" "Syncthing"
.TH "SYNCTHING-BEP" "7" "Mar 04, 2018" "v0.14" "Syncthing"
.SH NAME
syncthing-bep \- Block Exchange Protocol v1
.
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-CONFIG" "5" "Feb 05, 2018" "v0.14" "Syncthing"
.TH "SYNCTHING-CONFIG" "5" "Mar 04, 2018" "v0.14" "Syncthing"
.SH NAME
syncthing-config \- Syncthing Configuration
.
+7 -6
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-DEVICE-IDS" "7" "Feb 05, 2018" "v0.14" "Syncthing"
.TH "SYNCTHING-DEVICE-IDS" "7" "Mar 04, 2018" "v0.14" "Syncthing"
.SH NAME
syncthing-device-ids \- Understanding Device IDs
.
@@ -40,11 +40,12 @@ the public key in use.
To understand device IDs we need to look at the underlying mechanisms. At first
startup, Syncthing will create a public/private keypair.
.sp
Currently this is a 3072 bit RSA key. The keys are saved in the form of the
private key (\fBkey.pem\fP) and a self signed certificate (\fBcert.pem\fP). The self
signing part doesnt actually add any security or functionality as far as
Syncthing is concerned but it enables the use of the keys in a standard TLS
exchange.
Currently this is a 384 bit ECDSA key (3072 bit RSA prior to v0.12.5,
which is what is used as an example in this article). The keys are saved in
the form of the private key (\fBkey.pem\fP) and a self signed certificate
(\fBcert.pem\fP). The self signing part doesnt actually add any security or
functionality as far as Syncthing is concerned but it enables the use of the
keys in a standard TLS exchange.
.sp
The typical certificate will look something like this, inspected with
\fBopenssl x509\fP:
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-EVENT-API" "7" "Feb 05, 2018" "v0.14" "Syncthing"
.TH "SYNCTHING-EVENT-API" "7" "Mar 04, 2018" "v0.14" "Syncthing"
.SH NAME
syncthing-event-api \- Event API
.
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-FAQ" "7" "Feb 05, 2018" "v0.14" "Syncthing"
.TH "SYNCTHING-FAQ" "7" "Mar 04, 2018" "v0.14" "Syncthing"
.SH NAME
syncthing-faq \- Frequently Asked Questions
.
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-GLOBALDISCO" "7" "Feb 05, 2018" "v0.14" "Syncthing"
.TH "SYNCTHING-GLOBALDISCO" "7" "Mar 04, 2018" "v0.14" "Syncthing"
.SH NAME
syncthing-globaldisco \- Global Discovery Protocol v3
.
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-LOCALDISCO" "7" "Feb 05, 2018" "v0.14" "Syncthing"
.TH "SYNCTHING-LOCALDISCO" "7" "Mar 04, 2018" "v0.14" "Syncthing"
.SH NAME
syncthing-localdisco \- Local Discovery Protocol v4
.
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-NETWORKING" "7" "Feb 05, 2018" "v0.14" "Syncthing"
.TH "SYNCTHING-NETWORKING" "7" "Mar 04, 2018" "v0.14" "Syncthing"
.SH NAME
syncthing-networking \- Firewall Setup
.
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-RELAY" "7" "Feb 05, 2018" "v0.14" "Syncthing"
.TH "SYNCTHING-RELAY" "7" "Mar 04, 2018" "v0.14" "Syncthing"
.SH NAME
syncthing-relay \- Relay Protocol v1
.
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-REST-API" "7" "Feb 05, 2018" "v0.14" "Syncthing"
.TH "SYNCTHING-REST-API" "7" "Mar 04, 2018" "v0.14" "Syncthing"
.SH NAME
syncthing-rest-api \- REST API
.
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-SECURITY" "7" "Feb 05, 2018" "v0.14" "Syncthing"
.TH "SYNCTHING-SECURITY" "7" "Mar 04, 2018" "v0.14" "Syncthing"
.SH NAME
syncthing-security \- Security Principles
.
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-STIGNORE" "5" "Feb 05, 2018" "v0.14" "Syncthing"
.TH "SYNCTHING-STIGNORE" "5" "Mar 04, 2018" "v0.14" "Syncthing"
.SH NAME
syncthing-stignore \- Prevent files from being synchronized to other nodes
.
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-VERSIONING" "7" "Feb 05, 2018" "v0.14" "Syncthing"
.TH "SYNCTHING-VERSIONING" "7" "Mar 04, 2018" "v0.14" "Syncthing"
.SH NAME
syncthing-versioning \- Keep automatic backups of deleted files by other nodes
.
+74 -52
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING" "1" "Feb 05, 2018" "v0.14" "Syncthing"
.TH "SYNCTHING" "1" "Mar 04, 2018" "v0.14" "Syncthing"
.SH NAME
syncthing \- Syncthing
.
@@ -48,7 +48,7 @@ syncthing [\-audit] [\-auditfile=<file|\-|\-\->] [\-browser\-only] [device\-id]
.UNINDENT
.SH DESCRIPTION
.sp
Syncthing is an application that lets you synchronize your files across multiple
Syncthing lets you synchronize your files bidirectionally across multiple
devices. This means the creation, modification or deletion of files on one
machine will automatically be replicated to your other devices. We believe your
data is your data alone and you deserve to choose where it is stored. Therefore
@@ -99,8 +99,8 @@ Set destination filename for logging (use \fB"\-"\fP for stdout, which is the de
.INDENT 0.0
.TP
.B \-logflags=<flags>
Select information in log line prefix, default 2. The \fB\-logflags\fP value is
a sum of the following:
Select information in log line prefix. The \fB\-logflags\fP value is a sum of
the following:
.INDENT 7.0
.IP \(bu 2
1: Date
@@ -146,9 +146,11 @@ Start with all devices and folders paused.
.INDENT 0.0
.TP
.B \-reset\-database
Reset the database, forcing a full rescan and resync.
Create \fI\&.stfolder\fP folders in each sync folder if they do not already exist.
\fBCaution\fP: Ensure that all sync folders which are mountpoints are already mounted. Inconsistent versions may result if the mountpoint is later mounted and contains older versions.
Reset the database, forcing a full rescan and resync. Create \fI\&.stfolder\fP
folders in each sync folder if they do not already exist. \fBCaution\fP:
Ensure that all sync folders which are mountpoints are already mounted.
Inconsistent versions may result if the mountpoint is later mounted and
contains older versions.
.UNINDENT
.INDENT 0.0
.TP
@@ -235,33 +237,30 @@ If you start Syncthing from within service managers like systemd or supervisor,
path expansion may not be supported.
.INDENT 0.0
.TP
.B STNODEFAULTFOLDER
Dont create a default folder when starting for the first time. This
variable will be ignored anytime after the first run.
.TP
.B STGUIASSETS
Directory to load GUI assets from. Overrides compiled in assets.
.TP
.B STTRACE
Used to increase the debugging verbosity in specific or all facilities, generally mapping to a Go package. Enabling any of these also enables microsecond timestamps, file names plus line numbers. Enter a comma\-separated string of facilities to trace. \fBsyncthing \-help\fP always outputs an up\-to\-date list. The valid facility strings
are:
Used to increase the debugging verbosity in specific or all facilities,
generally mapping to a Go package. Enabling any of these also enables
microsecond timestamps, file names plus line numbers. Enter a
comma\-separated string of facilities to trace. \fBsyncthing \-help\fP always
outputs an up\-to\-date list. The valid facility strings are:
.INDENT 7.0
.TP
.B Main and operational facilities:
.INDENT 7.0
.TP
.B main
Main package.
.TP
.B model
The root hub; the largest chunk of the system. File pulling, index transmission and requests for chunks.
.TP
.B config
Configuration loading and saving.
.TP
.B db
The database layer.
.TP
.B main
Main package.
.TP
.B model
The root hub; the largest chunk of the system. File pulling, index
transmission and requests for chunks.
.TP
.B scanner
File change detection and hashing.
.TP
@@ -273,7 +272,8 @@ File versioning.
.INDENT 7.0
.TP
.B beacon
Multicast and broadcast discovery packets.
Multicast and broadcast UDP discovery packets: Selected interfaces
and addresses.
.TP
.B connections
Connection handling.
@@ -282,13 +282,8 @@ Connection handling.
Dialing connections.
.TP
.B discover
Remote device discovery requests, replies and registration of devices.
.TP
.B relay
Relay interaction.
.TP
.B protocol
The BEP protocol.
Remote device discovery requests, replies and registration of
devices.
.TP
.B nat
NAT discovery and port mapping.
@@ -296,6 +291,12 @@ NAT discovery and port mapping.
.B pmp
NAT\-PMP discovery and port mapping.
.TP
.B protocol
The BEP protocol.
.TP
.B relay
Relay interaction (\fBstrelaysrv\fP).
.TP
.B upnp
UPnP discovery and port mapping.
.UNINDENT
@@ -303,6 +304,9 @@ UPnP discovery and port mapping.
.B Other facilities:
.INDENT 7.0
.TP
.B fs
Filesystem access.
.TP
.B events
Event generation and logging.
.TP
@@ -321,32 +325,19 @@ Mutexes. Used for debugging race conditions and deadlocks.
.B upgrade
Binary upgrades.
.TP
.B walkfs
Filesystem access while walking.
.TP
.B all
All of the above.
.UNINDENT
.UNINDENT
.TP
.B STPROFILER
Set to a listen address such as “127.0.0.1:9090” to start the profiler with
HTTP access.
.TP
.B STCPUPROFILE
Write a CPU profile to cpu\-$pid.pprof on exit.
.TP
.B STHEAPPROFILE
Write heap profiles to \fBheap\-$pid\-$timestamp.pprof\fP each time heap usage
increases.
.TP
.B STBLOCKPROFILE
Write block profiles to \fBblock\-$pid\-$timestamp.pprof\fP every 20 seconds.
.TP
.B STPERFSTATS
Write running performance statistics to \fBperf\-$pid.csv\fP\&. Not supported on
Windows.
.TP
.B STDEADLOCK
Used for debugging internal deadlocks. Use only under direction of a
developer.
.B STCPUPROFILE
Write a CPU profile to \fBcpu\-$pid.pprof\fP on exit.
.TP
.B STDEADLOCKTIMEOUT
Used for debugging internal deadlocks; sets debug sensitivity. Use only
@@ -356,15 +347,46 @@ under direction of a developer.
Used for debugging internal deadlocks; sets debug sensitivity. Use only
under direction of a developer.
.TP
.B STGUIASSETS
Directory to load GUI assets from. Overrides compiled in assets. Useful for
developing webgui, commonly use \fBSTGUIASSETS=gui bin/syncthing\fP\&.
.TP
.B STHASHING
Specify which hashing package to use. Defaults to automatic based on
performance. Specify “minio” (compatibility) or “standard” for the default
Go implementation.
.TP
.B STHEAPPROFILE
Write heap profiles to \fBheap\-$pid\-$timestamp.pprof\fP each time heap usage
increases.
.TP
.B STNODEFAULTFOLDER
Dont create a default folder when starting for the first time. This
variable will be ignored anytime after the first run.
.TP
.B STNORESTART
Equivalent to the \-no\-restart argument. Disable the Syncthing monitor process which handles restarts for some configuration changes, upgrades, crashes and also log file writing (stdout is still written).
Equivalent to the \fB\-no\-restart\fP flag. Disable the Syncthing monitor
process which handles restarts for some configuration changes, upgrades,
crashes and also log file writing (stdout is still written).
.TP
.B STNOUPGRADE
Disable automatic upgrades.
.TP
.B STHASHING
Specify which hashing package to use. Defaults to automatic based on
performance. Specify “minio” (compatibility) or “standard” for the default Go implementation.
.B STPROFILER
Set to a listen address such as “127.0.0.1:9090” to start the profiler with
HTTP access, which then can be reached at
\fI\%http://localhost:9090/debug/pprof\fP\&. See \fBgo tool pprof\fP for more
information.
.TP
.B STPERFSTATS
Write running performance statistics to \fBperf\-$pid.csv\fP\&. Not supported on
Windows.
.TP
.B STRECHECKDBEVERY
Time before folder statistics (file, dir, … counts) are recalculated from
scratch. The given duration must be parseable by GOs time.ParseDuration. If
missing or not parseable, the default value of 1 month is used. To force
recalculation on every startup, set it to \fB0\fP\&.
.TP
.B GOMAXPROCS
Set the maximum number of CPU cores to use. Defaults to all available CPU

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