Compare commits

...

60 Commits

Author SHA1 Message Date
Jakob Borg
5bb74ee61c gui, man: Update docs & translations 2016-11-22 09:32:57 +01:00
Jakob Borg
462fde5e7d cmd/syncthing: Make the default folder default again
The current way is quite confusing for new users - we create a default
folder, but it's not usable with the default folder created somewhere
else. Instead, when setting up for the first time with two devices, the
default folder must be removed and recreated on one of them. This comes
up on IRC and the forum now and then.

I think this matches expectactions better.

Another alternative would be to remove it entirely (not create a default
folder), but then we should also add some guidance in the UI on how to
proceed.

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3751
2016-11-22 08:18:43 +00:00
Unrud
f1e83a57cd lib/osutil: Remove unnecessary fsync in Copy()
Fsyncing the file has a small performance penalty and seems unnecessary. The
file will be fsynced anyway, when the changes are commited to the database.

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3749
2016-11-22 07:59:54 +00:00
Jakob Borg
cc9a9fb390 lib/model, lib/protocol: Add Folder.Description() for logging (ref #3741) 2016-11-22 08:36:14 +01:00
Jakob Borg
8fbcceb742 authors: Add further Unrud address 2016-11-22 08:14:22 +01:00
Roman Zaynetdinov
d3a251e6d9 lib/model: Log folder IDs and labels (fixes #3724)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3741
2016-11-21 20:09:18 +01:00
Jakob Borg
be80b26c18 authors: Add zaynetro 2016-11-21 20:08:31 +01:00
Unrud
1574b7d834 lib/model: Add fsync of files and directories, option to disable (fixes #3711) 2016-11-21 18:09:51 +01:00
Jakob Borg
51e10e344d Add Unrud 2016-11-21 17:59:44 +01:00
kwhite17
0d55d8c5b0 gui: Convert URLs in warning messages to HTML links (fixes #3241)
Skip-check: metalint (annoying timeout)

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3747
2016-11-21 08:27:44 +00:00
Jakob Borg
1392589d36 authors: Add kwhite17 2016-11-21 09:12:03 +01:00
Jakob Borg
548a324256 lib/connections: Slow down failing listeners
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3745
2016-11-19 12:37:14 +00:00
Jakob Borg
a8a0bc356a lib/model: Minor cleanup to not fondle cfg.Raw things in handleDeintroductions
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3739
2016-11-17 08:56:55 +00:00
Jakob Borg
faee1d5a8d lib/model: Fix locking around introduction handling (fixes #3737)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3738
2016-11-17 08:50:24 +00:00
Jakob Borg
3088dac33b lib/model: Clean up generateClusterConfig, fix spurious test failure by sorting 2016-11-17 07:45:45 +01:00
Jakob Borg
2641062c17 gui, man: Update docs & translations 2016-11-15 07:23:48 +01:00
Jakob Borg
95c738ea28 lib/protocol: Serialize the all zeroes device ID to the empty string
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3734
2016-11-15 06:22:36 +00:00
Jakob Borg
562d2f67a6 snapcraft: Point home and config dir towards non-versioned snap home (fixes #3730) 2016-11-14 19:06:05 +01:00
Ben Schulz
ba6aff4a1b gui: Use icons and tooltips for folder size info (fixes #3710)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3731
2016-11-13 13:56:07 +00:00
Audrius Butkevicius
bb23e3940e cmd/strelaysrv: Use listen address for outgoing HTTP requests (fixes #3682) 2016-11-13 09:32:05 +01:00
Audrius Butkevicius
94e4370c7e cmd/strelaysrv: Outbox will get GCed (fixes #3718) 2016-11-13 09:32:05 +01:00
Audrius Butkevicius
38d28c3f4a lib/relay: Close invitation channel in all error cases (fixes #3726) 2016-11-13 09:32:05 +01:00
Audrius Butkevicius
f60b424d70 lib/config: Raw() -> RawCopy() 2016-11-13 09:29:35 +01:00
Audrius Butkevicius
a1a91d5ef4 lib/model: Introducer can remove stuff it introduced (fixes #1015)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3522
2016-11-13 09:29:33 +01:00
Jakob Borg
bfb48b5dde jenkins: Clean should remove old snaps 2016-11-12 10:08:13 +01:00
Jakob Borg
2860813a8e build: Set snap grade to "stable" for releases 2016-11-12 09:47:57 +01:00
Jakob Borg
72538e350d build: Snap versions should not have initial "v"
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3728
2016-11-12 08:36:19 +00:00
Jakob Borg
59f3d1445f Revert "lib/model: Introducer can remove stuff it introduced (fixes #1015)"
This reverts commit 0b88cf1d03.
2016-11-12 08:38:29 +01:00
Audrius Butkevicius
0b88cf1d03 lib/model: Introducer can remove stuff it introduced (fixes #1015)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3522
2016-11-11 15:54:25 +01:00
Audrius Butkevicius
56e2ba29d0 lib/config: Subscribers get a copy of the config
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3722
2016-11-11 14:52:23 +00:00
Jakob Borg
6ec9b84674 test: Fix test config 2016-11-09 09:02:55 +08:00
Leo Arias
afd15392b1 build: Build snaps for ARM
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3717
2016-11-09 00:52:33 +00:00
Jakob Borg
ae4cc94a9d lib/model: Fix locking order in Availability() (fixes #3634)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3714
2016-11-08 06:38:50 +00:00
Jakob Borg
3f9b75b7b3 Revert "lib/model: Introducer can remove stuff it introduced (fixes #1015)"
This reverts commit ec2b097313.
2016-11-08 14:27:32 +08:00
Audrius Butkevicius
ec2b097313 lib/model: Introducer can remove stuff it introduced (fixes #1015)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3522
2016-11-08 00:40:48 +08:00
Audrius Butkevicius
caaab462bc lib/sync: Fix broken build 2016-11-05 02:31:52 +00:00
Audrius Butkevicius
da413b823b lib/sync: Add option for sasha-s/go-deadlock 2016-11-05 02:24:53 +00:00
Audrius Butkevicius
14937e7dd2 build: Fix proto builder on Windows 2016-11-03 22:06:51 +00:00
Audrius Butkevicius
3418497f3d lib/sync: Log everything... 2016-11-03 21:33:33 +00:00
Stefan Kuntz
e408f1061a etc: Added ufw firewall application preset
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3703
2016-11-03 15:46:25 +00:00
佛跳墙
c08fe4e2c5 gui: Remove erroneous right parenthesis
Skip-check: authors

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3699
2016-11-02 13:03:57 +00:00
Jakob Borg
b7e21984a1 gui, man: Update docs & translations 2016-11-01 11:26:25 +01:00
Audrius Butkevicius
7fba8cf759 lib/sync: Print all lockers, add holder to RWMutex 2016-10-30 00:17:25 +01:00
Jakob Borg
0296c23685 lib/protocol: Use DeviceID in protocol messages, with custom marshalling
This makes the device ID a real type that can be used in the protobuf
schema. That avoids the juggling back and forth from []byte in a bunch
of places and simplifies the code.

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3695
2016-10-29 21:56:24 +00:00
Jakob Borg
1cdfef4d6a script: Missed a newline in the commit-msg hook output 2016-10-27 21:47:14 +02:00
Jakob Borg
cead20ec91 script: Add commit message check hook 2016-10-27 21:42:05 +02:00
MikeLund
74dd051d51 all: Update docs.s.n links to use https
Skip-check: authors

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3691
2016-10-27 17:02:19 +00:00
Jakob Borg
5473285010 gui: Add help link for sync protocol listen addresses 2016-10-26 21:16:53 +02:00
Jakob Borg
4b3adfa21c vendor: Update gobwas/glob to fix question mark handling 2016-10-23 15:47:31 +02:00
Simon Frei
7c37301c91 lib/ignore: Add directory separator to glob.Compile call
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3674
LGTM: calmh
2016-10-21 07:33:40 +00:00
Jakob Borg
d9040f8038 authors: Add imsodin 2016-10-21 15:23:17 +08:00
Jakob Borg
f41606c0b0 jenkins: Build snap
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3678
2016-10-20 09:31:07 +00:00
Leo Arias
31d9750579 build: Add build method for snapcraft
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3636
2016-10-20 09:16:30 +00:00
Jakob Borg
173fb97832 authors: Add elopio 2016-10-20 16:38:14 +08:00
Wulf Weich
81248c3f56 gui: resurrect old dark theme as black theme
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3676
2016-10-19 09:06:27 +00:00
Frank Isemann
2914a0a0a5 gui: Slightly lighten Text for Disconnected/Scanning in Dark Theme 2016-10-19 08:27:17 +08:00
Audrius Butkevicius
815588daba lib/sync, lib/model: Capture locker routine ID, print locker details on deadlock 2016-10-18 21:00:01 +01:00
Jakob Borg
6152eb6d6d gui: Hide "Failed Items" unless there is an actual failure (fixes #3647)
Since delta indexes it's perfectly normal for us to need files that are
currently unavailable due to devices being disconnected. This doesn't
imply a failure, so we should not show the "Failed Items" line and
corresponding eternal spinner (since it would never be filled in, since
there is no failure).

We still show state "Out of Sync" (correct) and the list of files we
need (correct).
2016-10-17 23:57:58 +02:00
Jakob Borg
ff0ebc196c gui: Improve display of local size, ignore pattern status (fixes #3623)
The discrepancy between global and local sizes is fine and expected in
the presence of ignores. This just moves the "we have ignore patterns"
indication to the actual local size metric, as an explanation of why it
may differ from the global size...
2016-10-17 23:57:58 +02:00
Jakob Borg
4e8c8d7e2c cmd/syncthing, lib/db, lib/model: Track more detailed file/dirs/links/deleted counts 2016-10-17 23:57:43 +02:00
135 changed files with 4611 additions and 796 deletions

1
.gitignore vendored
View File

@@ -16,3 +16,4 @@ syncthing.sig
RELEASE
deb
lib/auto/gui.files.go
snapcraft.yaml

View File

@@ -61,8 +61,10 @@ Karol Różycki (krozycki) <rozycki.karol@gmail.com>
Kelong Cong (kc1212) <kc04bc@gmx.com> <kc1212@users.noreply.github.com>
Ken'ichi Kamada (kamadak) <kamada@nanohz.org>
Kevin Allen (ironmig) <kma1660@gmail.com>
Kevin White, Jr. (kwhite17) <kevinwhite1710@gmail.com>
Lars K.W. Gohlke (lkwg82) <lkwg82@gmx.de>
Laurent Etiemble (letiemble) <laurent.etiemble@gmail.com> <laurent.etiemble@monobjc.net>
Leo Arias (elopio) <yo@elopio.net>
Lode Hoste (Zillode) <zillode@zillode.be>
Lord Landon Agahnim (LordLandon) <lordlandon@gmail.com>
Majed Abdulaziz (majedev) <majed.alhajry@gmail.com>
@@ -81,9 +83,11 @@ Peter Hoeg (peterhoeg) <peter@speartail.com>
Philippe Schommers (filoozoom) <philippe@schommers.be>
Phill Luby (pluby) <phill.luby@newredo.com>
Piotr Bejda (piobpl) <piotrb10@gmail.com>
Roman Zaynetdinov (zaynetro) <romanznet@gmail.com>
Ryan Sullivan (KayoticSully) <kayoticsully@gmail.com>
Scott Klupfel (kluppy) <kluppy@going2blue.com>
Sergey Mishin (ralder) <ralder@yandex.ru>
Simon Frei (imsodin) <freisim93@gmail.com>
Stefan Kuntz (Stefan-Code) <stefan.github@gmail.com> <Stefan.github@gmail.com>
Stefan Tatschner (rumpelsepp) <stefan@sevenbyte.org> <rumpelsepp@sevenbyte.org>
Tim Abell (timabell) <tim@timwise.co.uk>
@@ -92,6 +96,7 @@ Tobias Nygren (tnn2) <tnn@nygren.pp.se>
Tomas Cerveny (kozec) <kozec@kozec.com>
Tully Robinson (tojrobinson) <tully@tojr.org>
Tyler Brazier (tylerbrazier) <tyler@tylerbrazier.com>
Unrud (Unrud) <unrud@openaliasbox.org> <Unrud@users.noreply.github.com>
Veeti Paananen (veeti) <veeti.paananen@rojekti.fi>
Victor Buinsky (buinsky) <vix_booja@tut.by>
Vil Brekin (Vilbrekin) <vilbrekin@gmail.com>

View File

@@ -33,12 +33,12 @@ latest info on Transifex.
Every contribution is welcome. If you want to contribute but are unsure
where to start, any open issues are fair game! See the [Contribution
Guidelines](http://docs.syncthing.net/dev/contributing.html) for the full
Guidelines](https://docs.syncthing.net/dev/contributing.html) for the full
story on committing code.
## Contributing Documentation
Updates to the [documentation site](http://docs.syncthing.net/) can be
Updates to the [documentation site](https://docs.syncthing.net/) can be
made as pull requests on the [documentation
repository](https://github.com/syncthing/docs).

6
NICKS
View File

@@ -37,6 +37,7 @@ dva <denisva@gmail.com>
dzarda <dzardacz@gmail.com>
eipiminus1 <eipiminusone+github@gmail.com>
eipiminus1 <eipiminus1@users.noreply.github.com>
elopio <yo@elopio.net>
facastagnini <federico.castagnini@gmail.com>
filoozoom <philippe@schommers.be>
frioux <frew@afoolishmanifesto.com>
@@ -44,6 +45,7 @@ frioux <frioux@gmail.com>
fti7 <frank@isemann.name>
gillisig <gilli@vx.is>
hadogenes <szafar@linux.pl>
imsodin <freisim93@gmail.com>
ironmig <kma1660@gmail.com>
jarlebring <jarlebring@gmail.com>
jedie <github.com@jensdiemer.de>
@@ -61,6 +63,7 @@ kozec <kozec@kozec.com>
kralo <max.schulze@online.de>
kralo <kralo@users.noreply.github.com>
krozycki <rozycki.karol@gmail.com>
kwhite17 <kevinwhite1710@gmail.com>
letiemble <laurent.etiemble@gmail.com>
letiemble <laurent.etiemble@monobjc.net>
lkwg82 <lkwg82@gmx.de>
@@ -107,6 +110,8 @@ tnn2 <tnn@nygren.pp.se>
tojrobinson <tully@tojr.org>
tpng <benny.tpng@gmail.com>
tylerbrazier <tyler@tylerbrazier.com>
Unrud <unrud@openaliasbox.org>
Unrud <Unrud@users.noreply.github.com>
uok <ueomkail@gmail.com>
uok <uok@users.noreply.github.com>
veeti <veeti.paananen@rojekti.fi>
@@ -116,5 +121,6 @@ WSGCSysadmin <e.meitner@willystreet.coop>
wweich <wweich@users.noreply.github.com>
wweich <wweich@gmx.de>
xduugu <cedric@gmx.ca>
zaynetro <romanznet@gmail.com>
Zillode <zillode@zillode.be>
zukoo <fxgsell@gmail.com>

View File

@@ -61,15 +61,15 @@ Please see the [Syncthing documentation site][6].
All code is licensed under the [MPLv2 License][7].
[1]: http://docs.syncthing.net/specs/bep-v1.html
[2]: http://docs.syncthing.net/intro/getting-started.html
[1]: https://docs.syncthing.net/specs/bep-v1.html
[2]: https://docs.syncthing.net/intro/getting-started.html
[3]: https://github.com/syncthing/syncthing/blob/master/etc
[4]: http://www.freenode.net/irc_servers.shtml
[5]: http://docs.syncthing.net/dev/building.html
[6]: http://docs.syncthing.net/
[5]: https://docs.syncthing.net/dev/building.html
[6]: https://docs.syncthing.net/
[7]: https://github.com/syncthing/syncthing/blob/master/LICENSE
[8]: https://forum.syncthing.net/
[9]: https://kiwiirc.com/client/irc.freenode.net/#syncthing
[10]: https://github.com/syncthing/syncthing/issues
[11]: http://docs.syncthing.net/users/contrib.html#gui-wrappers
[11]: https://docs.syncthing.net/users/contrib.html#gui-wrappers
[12]: https://www.bountysource.com/teams/syncthing/issues

View File

@@ -28,6 +28,7 @@ import (
"strconv"
"strings"
"syscall"
"text/template"
"time"
)
@@ -94,6 +95,7 @@ var targets = map[string]target{
{src: "etc/linux-systemd/system/syncthing@.service", dst: "deb/lib/systemd/system/syncthing@.service", perm: 0644},
{src: "etc/linux-systemd/system/syncthing-resume.service", dst: "deb/lib/systemd/system/syncthing-resume.service", perm: 0644},
{src: "etc/linux-systemd/user/syncthing.service", dst: "deb/usr/lib/systemd/user/syncthing.service", perm: 0644},
{src: "etc/firewall-ufw/syncthing", dst: "deb/etc/ufw/applications.d/syncthing", perm: 0644},
},
},
"stdiscosrv": {
@@ -275,6 +277,9 @@ func runCommand(cmd string, target target) {
case "deb":
buildDeb(target)
case "snap":
buildSnap(target)
case "clean":
clean()
@@ -481,8 +486,8 @@ func buildDeb(target target) {
os.RemoveAll("deb")
// "goarch" here is set to whatever the Debian packages expect. We correct
// "it to what we actually know how to build and keep the Debian variant
// "name in "debarch".
// it to what we actually know how to build and keep the Debian variant
// name in "debarch".
debarch := goarch
switch goarch {
case "i386":
@@ -520,6 +525,44 @@ func buildDeb(target target) {
"--license", "MPL-2")
}
func buildSnap(target target) {
os.RemoveAll("snap")
tmpl, err := template.ParseFiles("snapcraft.yaml.template")
if err != nil {
log.Fatal(err)
}
f, err := os.Create("snapcraft.yaml")
defer f.Close()
if err != nil {
log.Fatal(err)
}
snaparch := goarch
if snaparch == "armhf" {
goarch = "arm"
}
snapver := version
if strings.HasPrefix(snapver, "v") {
snapver = snapver[1:]
}
snapgrade := "devel"
if matched, _ := regexp.MatchString(`^\d+\.\d+\.\d+$`, snapver); matched {
snapgrade = "stable"
}
err = tmpl.Execute(f, map[string]string{
"Version": snapver,
"Architecture": snaparch,
"Grade": snapgrade,
})
if err != nil {
log.Fatal(err)
}
runPrint("snapcraft", "clean")
build(target, []string{"noupgrade"})
runPrint("snapcraft")
}
func copyFile(src, dst string, perm os.FileMode) error {
dstDir := filepath.Dir(dst)
os.MkdirAll(dstDir, 0755) // ignore error

View File

@@ -7,7 +7,6 @@
package main
import (
"bytes"
"crypto/rand"
"encoding/binary"
"flag"
@@ -45,7 +44,7 @@ func main() {
flag.Parse()
if fake {
log.Println("My ID:", protocol.DeviceIDFromBytes(myID))
log.Println("My ID:", myID)
}
runbeacon(beacon.NewMulticast(mc), fake)
@@ -75,17 +74,17 @@ func recv(bc beacon.Interface) {
var ann discover.Announce
ann.Unmarshal(data[4:])
if bytes.Equal(ann.ID, myID) {
if ann.ID == myID {
// This is one of our own fake packets, don't print it.
continue
}
// Print announcement details for the first packet from a given
// device ID and source address, or if -all was given.
key := string(ann.ID) + src.String()
key := ann.ID.String() + src.String()
if all || !seen[key] {
log.Printf("Announcement from %v\n", src)
log.Printf(" %v at %s\n", protocol.DeviceIDFromBytes(ann.ID), strings.Join(ann.Addresses, ", "))
log.Printf(" %v at %s\n", ann.ID, strings.Join(ann.Addresses, ", "))
seen[key] = true
}
}
@@ -106,9 +105,9 @@ func send(bc beacon.Interface) {
}
// returns a random but recognizable device ID
func randomDeviceID() []byte {
var id [32]byte
func randomDeviceID() protocol.DeviceID {
var id protocol.DeviceID
copy(id[:], randomPrefix)
rand.Read(id[len(randomPrefix):])
return id[:]
return id
}

View File

@@ -172,7 +172,7 @@ func protocolConnectionHandler(tcpConn net.Conn, config *tls.Config) {
if debug {
log.Println("Sent invitation from", id, "to", requestedPeer)
}
default:
case <-time.After(time.Second):
if debug {
log.Println("Could not send invitation from", id, "to", requestedPeer, "as peer disconnected")
}
@@ -204,7 +204,6 @@ func protocolConnectionHandler(tcpConn net.Conn, config *tls.Config) {
if debug {
log.Printf("Closing connection %s: %s", id, err)
}
close(outbox)
// Potentially closing a second time.
conn.Close()
@@ -260,10 +259,6 @@ func protocolConnectionHandler(tcpConn net.Conn, config *tls.Config) {
conn.Close()
case msg := <-outbox:
if msg == nil {
conn.Close()
return
}
if debug {
log.Printf("Sending message %T to %s", msg, id)
}

View File

@@ -8,6 +8,7 @@ import (
"fmt"
"log"
"net"
"net/http"
"net/url"
"os"
"os/signal"
@@ -120,6 +121,21 @@ func main() {
log.Fatal(err)
}
laddr, err := net.ResolveTCPAddr(proto, listen)
if err != nil {
log.Fatal(err)
}
if laddr.IP != nil && !laddr.IP.IsUnspecified() {
laddr.Port = 0
transport, ok := http.DefaultTransport.(*http.Transport)
if ok {
transport.DialContext = (&net.Dialer{
Timeout: 30 * time.Second,
LocalAddr: laddr,
}).DialContext
}
}
log.Println(LongVersion)
maxDescriptors, err := osutil.MaximizeOpenFileLimit()

View File

@@ -72,7 +72,7 @@ type modelIntf interface {
Completion(device protocol.DeviceID, folder string) model.FolderCompletion
Override(folder string)
NeedFolderFiles(folder string, page, perpage int) ([]db.FileInfoTruncated, []db.FileInfoTruncated, []db.FileInfoTruncated, int)
NeedSize(folder string) (nfiles, ndeletes int, bytes int64)
NeedSize(folder string) db.Counts
ConnectionStats() map[string]interface{}
DeviceStatistics() map[string]stats.DeviceStatistics
FolderStatistics() map[string]stats.FolderStatistics
@@ -90,8 +90,8 @@ type modelIntf interface {
ScanFolderSubdirs(folder string, subs []string) error
BringToFront(folder, file string)
ConnectedTo(deviceID protocol.DeviceID) bool
GlobalSize(folder string) (nfiles, deleted int, bytes int64)
LocalSize(folder string) (nfiles, deleted int, bytes int64)
GlobalSize(folder string) db.Counts
LocalSize(folder string) db.Counts
CurrentSequence(folder string) (int64, bool)
RemoteSequence(folder string) (int64, bool)
State(folder string) (string, time.Time, error)
@@ -99,7 +99,7 @@ type modelIntf interface {
type configIntf interface {
GUI() config.GUIConfiguration
Raw() config.Configuration
RawCopy() config.Configuration
Options() config.OptionsConfiguration
Replace(cfg config.Configuration) error
Subscribe(c config.Committer)
@@ -634,16 +634,16 @@ func folderSummary(cfg configIntf, m modelIntf, folder string) map[string]interf
res["invalid"] = "" // Deprecated, retains external API for now
globalFiles, globalDeleted, globalBytes := m.GlobalSize(folder)
res["globalFiles"], res["globalDeleted"], res["globalBytes"] = globalFiles, globalDeleted, globalBytes
global := m.GlobalSize(folder)
res["globalFiles"], res["globalDirectories"], res["globalSymlinks"], res["globalDeleted"], res["globalBytes"] = global.Files, global.Directories, global.Symlinks, global.Deleted, global.Bytes
localFiles, localDeleted, localBytes := m.LocalSize(folder)
res["localFiles"], res["localDeleted"], res["localBytes"] = localFiles, localDeleted, localBytes
local := m.LocalSize(folder)
res["localFiles"], res["localDirectories"], res["localSymlinks"], res["localDeleted"], res["localBytes"] = local.Files, local.Directories, local.Symlinks, local.Deleted, local.Bytes
needFiles, needDeletes, needBytes := m.NeedSize(folder)
res["needFiles"], res["needDeletes"], res["needBytes"] = needFiles, needDeletes, needBytes
need := m.NeedSize(folder)
res["needFiles"], res["needDirectories"], res["needSymlinks"], res["needDeletes"], res["needBytes"] = need.Files, need.Directories, need.Symlinks, need.Deleted, need.Bytes
res["inSyncFiles"], res["inSyncBytes"] = globalFiles-needFiles, globalBytes-needBytes
res["inSyncFiles"], res["inSyncBytes"] = global.Files-need.Files, global.Bytes-need.Bytes
var err error
res["state"], res["stateChanged"], err = m.State(folder)
@@ -736,7 +736,7 @@ func (s *apiService) getDBFile(w http.ResponseWriter, r *http.Request) {
}
func (s *apiService) getSystemConfig(w http.ResponseWriter, r *http.Request) {
sendJSON(w, s.cfg.Raw())
sendJSON(w, s.cfg.RawCopy())
}
func (s *apiService) postSystemConfig(w http.ResponseWriter, r *http.Request) {

View File

@@ -47,6 +47,8 @@ import (
"github.com/syncthing/syncthing/lib/upgrade"
"github.com/thejerf/suture"
_ "net/http/pprof" // Need to import this to support STPROFILER.
)
var (
@@ -672,7 +674,7 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
}
}
if cfg.Raw().OriginalVersion == 15 {
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.
@@ -871,7 +873,7 @@ func loadOrCreateConfig() *config.Wrapper {
l.Fatalln("Config:", err)
}
if cfg.Raw().OriginalVersion != config.CurrentVersion {
if cfg.RawCopy().OriginalVersion != config.CurrentVersion {
err = archiveAndSaveConfig(cfg)
if err != nil {
l.Fatalln("Config archive:", err)
@@ -883,7 +885,7 @@ func loadOrCreateConfig() *config.Wrapper {
func archiveAndSaveConfig(cfg *config.Wrapper) error {
// Copy the existing config to an archive copy
archivePath := cfg.ConfigPath() + fmt.Sprintf(".v%d", cfg.Raw().OriginalVersion)
archivePath := cfg.ConfigPath() + fmt.Sprintf(".v%d", cfg.RawCopy().OriginalVersion)
l.Infoln("Archiving a copy of old config file format at:", archivePath)
if err := copyFile(cfg.ConfigPath(), archivePath); err != nil {
return err
@@ -953,9 +955,8 @@ func defaultConfig(myName string) config.Configuration {
if !noDefaultFolder {
l.Infoln("Default folder created and/or linked to new config")
folderID := strings.ToLower(rand.String(5) + "-" + rand.String(5))
defaultFolder = config.NewFolderConfiguration(folderID, locations[locDefFolder])
defaultFolder.Label = "Default Folder (" + folderID + ")"
defaultFolder = config.NewFolderConfiguration("default", locations[locDefFolder])
defaultFolder.Label = "Default Folder"
defaultFolder.RescanIntervalS = 60
defaultFolder.MinDiskFreePct = 1
defaultFolder.Devices = []config.FolderDeviceConfiguration{{DeviceID: myID}}

View File

@@ -23,7 +23,7 @@ func (c *mockedConfig) ListenAddresses() []string {
return nil
}
func (c *mockedConfig) Raw() config.Configuration {
func (c *mockedConfig) RawCopy() config.Configuration {
return config.Configuration{}
}

View File

@@ -31,8 +31,8 @@ func (m *mockedModel) NeedFolderFiles(folder string, page, perpage int) ([]db.Fi
return nil, nil, nil, 0
}
func (m *mockedModel) NeedSize(folder string) (nfiles, ndeletes int, bytes int64) {
return 0, 0, 0
func (m *mockedModel) NeedSize(folder string) db.Counts {
return db.Counts{}
}
func (m *mockedModel) ConnectionStats() map[string]interface{} {
@@ -95,12 +95,12 @@ func (m *mockedModel) ConnectedTo(deviceID protocol.DeviceID) bool {
return false
}
func (m *mockedModel) GlobalSize(folder string) (nfiles, deleted int, bytes int64) {
return 0, 0, 0
func (m *mockedModel) GlobalSize(folder string) db.Counts {
return db.Counts{}
}
func (m *mockedModel) LocalSize(folder string) (nfiles, deleted int, bytes int64) {
return 0, 0, 0
func (m *mockedModel) LocalSize(folder string) db.Counts {
return db.Counts{}
}
func (m *mockedModel) CurrentSequence(folder string) (int64, bool) {

View File

@@ -45,7 +45,7 @@ func newUsageReportingManager(cfg *config.Wrapper, m *model.Model) *usageReporti
}
// Start UR if it's enabled.
mgr.CommitConfiguration(config.Configuration{}, cfg.Raw())
mgr.CommitConfiguration(config.Configuration{}, cfg.RawCopy())
// Listen to future config changes so that we can start and stop as
// appropriate.
@@ -93,14 +93,14 @@ func reportData(cfg configIntf, m modelIntf) map[string]interface{} {
var totFiles, maxFiles int
var totBytes, maxBytes int64
for folderID := range cfg.Folders() {
files, _, bytes := m.GlobalSize(folderID)
totFiles += files
totBytes += bytes
if files > maxFiles {
maxFiles = files
global := m.GlobalSize(folderID)
totFiles += global.Files
totBytes += global.Bytes
if global.Files > maxFiles {
maxFiles = global.Files
}
if bytes > maxBytes {
maxBytes = bytes
if global.Bytes > maxBytes {
maxBytes = global.Bytes
}
}

View File

@@ -0,0 +1,22 @@
Uncomplicated FireWall application preset
===================
Installation
-----------
**Please note:** When you installed syncthing using the official deb package, you can skip the copying.
Copy the file `syncthing` to your ufw applications directory usually located at `/etc/ufw/applications.d/`. (root permissions required).
In a terminal run
```
sudo ufw app update syncthing
```
to load the preset.
To allow the syncthing ports, run
```
sudo ufw allow syncthing
```
You can then verify the opened ports
```
sudo ufw status verbose
```

View File

@@ -0,0 +1,4 @@
[syncthing]
title=Syncthing
description=Syncthing file synchronisation
ports=22000/tcp|21027/udp

View File

@@ -3,6 +3,6 @@
This directory contains configuration files for running Syncthing under the
"systemd" service manager on Linux both under either a systemd system service or
systemd user service. For further documentation take a look at the [systemd
section][1] on http://docs.syncthing.net.
section][1] on https://docs.syncthing.net.
[1]: http://docs.syncthing.net/users/autostart.html#systemd
[1]: https://docs.syncthing.net/users/autostart.html#systemd

View File

@@ -11,6 +11,6 @@ To manualy start syncthing via Upstart when using the system configuration use:
sudo initctl start syncthing
```
For further documentation see [http://docs.syncthing.net/users/autostart.html][1].
For further documentation see [https://docs.syncthing.net/users/autostart.html][1].
[1]: http://docs.syncthing.net/users/autostart.html#Upstart
[1]: https://docs.syncthing.net/users/autostart.html#Upstart

View File

@@ -0,0 +1,245 @@
/**
Black theme
Author: alessandro.g89
Source: https://userstyles.org/styles/122502/syncthing-dark
**/
body {
color: #aaa !important;
background-color: black !important;
}
a:hover,a:focus,a.focus{
outline: none !important;
}
/* navbar */
.navbar {
background-color: #333 !important;
border-color: #333 !important;
border-width: 2px !important;
}
.navbar-text, .dropdown>a, .dropdown-menu>li>a, .hidden-xs>a, .navbar-link {
color: #aaa !important;
}
.dropdown-menu {
border-color: #333 !important;
border-width: 2px !important;
background-color: #222 !important;
}
.dropdown-menu>li>a:hover, .dropdown-menu>li>a:focus {
color: #fff !important;
background-color: #333 !important;
}
.open>.dropdown-toggle, .dropdown-toggle:hover {
border-color: #333 !important;
background-color: #222 !important;
}
.divider {
background-color: #333 !important;
height: 2px !important;
}
li.hidden-xs:hover, .navbar-link:hover, .navbar-link:focus {
outline: none !important;
border-color: #333 !important;
background-color: #222 !important;
}
.dropdown-menu>.active>a {
color: #fff !important;
background-color: #217dbb !important;
}
/* main panel */
.panel {
background-color: #111 !important;
border-width: 2px !important;
}
.panel-default {
border-color: #222 !important;
}
.panel-default > .panel-heading {
color: #aaa !important;
border-color: #222 !important;
background-color: #222 !important;
}
.panel-warning > .panel-heading {
color: #222 !important;
}
.panel-progress {
background: #3498db;
}
.panel-footer {
background-color: #111 !important;
border-width: 0 !important;
}
.table-striped>tbody>tr:nth-of-type(odd) {
background-color: #181818 !important;
}
.panel-group .panel-heading+.panel-collapse>.panel-body, .panel-group .panel-heading+.panel-collapse>.list-group {
border-top: 1px solid #222 !important;
}
.identicon rect {
fill: #aaa;
}
.panel-warning .identicon rect {
fill: #222;
}
.panel-heading:hover, .panel-heading:focus {
text-decoration: none;
}
/* buttons */
.btn {
border-radius: 3px !important;
border-width: 0px !important;
}
.btn:hover, .btn:focus, .btn.focus {
outline: none !important;
}
.btn-default {
color: #aaa !important;
background-color: #333 !important;
}
.btn-default:hover, .btn-default:focus, .btn-default.focus {
color: #fff !important;
background-color: #484848 !important;
}
.btn-primary {
background-color: #217dbb !important;
}
.btn-primary:hover, .btn-primary:focus, .btn-primary.focus {
background-color: #3498db !important;
}
.btn-warning {
background-color: #c29d0b !important;
}
.btn-warning:hover, .btn-warning:focus, .btn-warning.focus {
background-color: #f1c40f !important;
}
.btn-danger {
background-color: #d62c1a !important;
}
.btn-danger:hover, .btn-danger:focus, .btn-danger.focus {
background-color: #e74c3c !important;
}
/* modal dialogs */
.modal-header {
border-bottom-color: #222 !important;
}
.modal-header:not(.alert) {
background-color: #222;
}
.alert-info {
color: #222 !important;
}
.alert-warning {
color: #222 !important;
}
.alert-danger {
color: #222 !important;
background-color: #d62c1a !important;
}
.modal-content {
border-color: #666 !important;
border-width: 2px !important;
background-color: #111 !important;
}
.modal-footer {
border-color: #111 !important;
background-color: #111 !important;
}
.help-block {
color: #aaa !important;
}
.form-control {
color: #aaa !important;
border-color: #444 !important;
background-color: black !important;
}
code.ng-binding{
color: #f99 !important;
background-color: #444 !important;
}
.well, .form-control[readonly="readonly"], .popover { /* read-only fields*/
color: #666 !important;
border-color: #444 !important;
background-color: #111 !important;
}
/* buttons for pagination */
.pagination>li>a, .pagination>li>span {
background-color: #333 !important;
border-color: #484848 !important;
}
.pagination>li>a:hover, .pagination>li>a:focus, .pagination>li>a.focus {
background-color: #484848 !important;
}
/* progress bars */
.progress-bar {
background-color: #217dbb !important;
}
.progress-bar-success {
background-color: #0A8522 !important;
}
.progress-bar-info {
background-color: #9b59b6 !important;
}
.progress-bar-warning {
background-color: #c29d0b !important;
}
.progress-bar-danger {
background-color: #d62c1a !important;
}
.progress .frontal {
color: #222;
}

View File

@@ -247,3 +247,12 @@ code.ng-binding{
.progress .frontal {
color: #222;
}
.text-info {
color: #9a6dc0;
}
.text-primary {
color: #3fa9f0;
}

View File

@@ -31,7 +31,7 @@
"Command": "Команда",
"Comment, when used at the start of a line": "Коментар, използван в началото на реда",
"Compression": "Компресиране",
"Configured": "Configured",
"Configured": "Настроен",
"Connection Error": "Грешка при свързването",
"Connection Type": "Вид връзка",
"Copied from elsewhere": "Копиране от някъде другаде",
@@ -45,7 +45,7 @@
"Device Name": "Име на устройството",
"Devices": "Устройства",
"Disconnected": "Не е свързано",
"Discovered": "Discovered",
"Discovered": "Открит",
"Discovery": "Откриване",
"Documentation": "Документация",
"Download Rate": "Скорост на сваляне",
@@ -135,6 +135,7 @@
"Quick guide to supported patterns": "Бърз наръчник към поддържаните шаблони",
"RAM Utilization": "Използван RAM",
"Random": "Произволен",
"Reduced by ignore patterns": "Намалено посредством шаблон за игнориране",
"Release Notes": "Бележки по обновяването",
"Remote Devices": "Чужди устройства",
"Remove": "Премахни",
@@ -230,6 +231,8 @@
"Yes": "Да",
"You must keep at least one version.": "Трябва да пазиш поне една версия.",
"days": "дни",
"directories": "директории",
"files": "файла",
"full documentation": "пълна документация",
"items": "елемента",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} желае да сподели папка \"{{folder}}\".",

View File

@@ -135,6 +135,7 @@
"Quick guide to supported patterns": "Guia ràpida per als possibles patrons",
"RAM Utilization": "Utilització de la RAM",
"Random": "Aleatori",
"Reduced by ignore patterns": "Reduced by ignore patterns",
"Release Notes": "Notes de llançament",
"Remote Devices": "Remote Devices",
"Remove": "Esborrar",
@@ -230,6 +231,8 @@
"Yes": "Si",
"You must keep at least one version.": "Has de mantenir com a mínim una versió.",
"days": "dies",
"directories": "directories",
"files": "files",
"full documentation": "documentació sencera",
"items": "Elements",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} vol compartir la carpeta \"{{folder}}\".",

View File

@@ -135,6 +135,7 @@
"Quick guide to supported patterns": "Guía ràpida de patrons suportats",
"RAM Utilization": "Utilització de la RAM",
"Random": "Aleatori",
"Reduced by ignore patterns": "Reduced by ignore patterns",
"Release Notes": "Notes de la versió",
"Remote Devices": "Dispositius Remots",
"Remove": "Eliminar",
@@ -230,6 +231,8 @@
"Yes": "Sí",
"You must keep at least one version.": "Es deu mantindre al menys una versió.",
"days": "dies",
"directories": "directories",
"files": "files",
"full documentation": "Documentació completa",
"items": "Elements",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} vol compartit la carpeta \"{{folder}}\".",

View File

@@ -31,7 +31,7 @@
"Command": "Příkaz",
"Comment, when used at the start of a line": "Komentář, pokud použito na začátku řádku",
"Compression": "Komprese",
"Configured": "Configured",
"Configured": "Nastaveno",
"Connection Error": "Chyba připojení",
"Connection Type": "Typ připojení",
"Copied from elsewhere": "Zkopírováno odjinud",
@@ -45,7 +45,7 @@
"Device Name": "Jméno přístroje",
"Devices": "Přístroje",
"Disconnected": "Odpojen",
"Discovered": "Discovered",
"Discovered": "Nalezeno",
"Discovery": "Oznamování",
"Documentation": "Dokumentace",
"Download Rate": "Rychlost stahování",
@@ -135,6 +135,7 @@
"Quick guide to supported patterns": "Rychlá nápověda k podporovaným vzorům",
"RAM Utilization": "Využití RAM",
"Random": "Náhodně",
"Reduced by ignore patterns": "Redukováno o ignorované vzory",
"Release Notes": "Poznámky k vydání",
"Remote Devices": "Vzdálená zařízení",
"Remove": "Odstranit",
@@ -230,6 +231,8 @@
"Yes": "Ano",
"You must keep at least one version.": "Je třeba ponechat alespoň jednu verzi.",
"days": "dní",
"directories": "složek",
"files": "souborů",
"full documentation": "plná dokumentace",
"items": "položky",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} chce sdílet adresář \"{{folder}}\".",

View File

@@ -135,6 +135,7 @@
"Quick guide to supported patterns": "Hurtig guide til supporteret mønstre",
"RAM Utilization": "RAM-forbrug",
"Random": "Tilfældig",
"Reduced by ignore patterns": "Reduced by ignore patterns",
"Release Notes": "Udgivelsesnoter",
"Remote Devices": "Remote Devices",
"Remove": "Fjern",
@@ -230,6 +231,8 @@
"Yes": "Ja",
"You must keep at least one version.": "Du skal beholde mindst én version.",
"days": "dage",
"directories": "directories",
"files": "files",
"full documentation": "Fuld dokumentation",
"items": "poster",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} ønsker at dele mappen \"{{folder}}\". ",

View File

@@ -31,7 +31,7 @@
"Command": "Befehl",
"Comment, when used at the start of a line": "Kommentar, wenn am Anfang der Zeile benutzt.",
"Compression": "Komprimierung",
"Configured": "Configured",
"Configured": "Konfiguriert",
"Connection Error": "Verbindungsfehler",
"Connection Type": "Verbindungstyp",
"Copied from elsewhere": "Von anderer Quelle kopiert",
@@ -45,7 +45,7 @@
"Device Name": "Gerätename",
"Devices": "Geräte",
"Disconnected": "Getrennt",
"Discovered": "Discovered",
"Discovered": "Ermittelt",
"Discovery": "Gerätesuche",
"Documentation": "Dokumentation",
"Download Rate": "Download",
@@ -135,6 +135,7 @@
"Quick guide to supported patterns": "Schnellanleitung zu den unterstützten Mustern",
"RAM Utilization": "RAM Auslastung",
"Random": "Zufall",
"Reduced by ignore patterns": "Beschränkt durch Ignoriermuster",
"Release Notes": "Veröffentlichungsnotizen",
"Remote Devices": "Remote-Geräte",
"Remove": "Entfernen",
@@ -230,6 +231,8 @@
"Yes": "Ja",
"You must keep at least one version.": "Du musst mindestens eine Version behalten.",
"days": "Tage",
"directories": "Verzeichnisse",
"files": "Dateien",
"full documentation": "Komplette Dokumentation",
"items": "Objekte",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} möchte das Verzeichnis \"{{folder}}\" teilen.",

View File

@@ -26,7 +26,7 @@
"Bugs": "Bugs",
"CPU Utilization": "Επιβάρυνση του επεξεργαστή",
"Changelog": "Πληροφορίες εκδόσεων",
"Clean out after": "Μετά από αυτό, εκκαθάρισε",
"Clean out after": "Εκκαθάριση μετά από",
"Close": "Τέλος",
"Command": "Εντολή",
"Comment, when used at the start of a line": "Σχόλιο, όταν χρησιμοποιείται στην αρχή μιας γραμμής",
@@ -36,7 +36,7 @@
"Connection Type": "Τύπος Σύνδεσης",
"Copied from elsewhere": "Έχει αντιγραφεί από κάπου αλλού",
"Copied from original": "Έχει αντιγραφεί από το πρωτότυπο",
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 οι παρακάτω Συνεισφέροντες:",
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 για τους παρακάτω συνεισφέροντες:",
"Danger!": "Προσοχή!",
"Deleted": "Διαγραμμένα",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Η συσκευή \"{{name}}\" ({{device}} στη διεύθυνση {{address}}) επιθυμεί να συνδεθεί. Προσθήκη της νέας συσκευής;",
@@ -86,7 +86,7 @@
"Ignore Patterns": "Πρότυπο για αγνόηση",
"Ignore Permissions": "Αγνόησε τα δικαιώματα",
"Incoming Rate Limit (KiB/s)": "Περιορισμός ταχύτητας λήψης (KiB/s)",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Με μια εσφαλμένη ρύθμιση μπορεί προκαληθεί ζημιά στα περιεχόμενα του φακέλου και το Syncthing μπορεί να σταματήσει να λειτουργεί.",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Με μια εσφαλμένη ρύθμιση μπορεί να προκληθεί ζημιά στο περιεχόμενο των φακέλων και το Syncthing ενδέχεται να σταματήσει να λειτουργεί.",
"Introducer": "Βασικός κόμβος",
"Inversion of the given condition (i.e. do not exclude)": "Αντιστροφή της δοσμένης συνθήκης (π.χ. να μην εξαιρείς) ",
"Keep Versions": "Διατήρηση εκδόσεων",
@@ -135,6 +135,7 @@
"Quick guide to supported patterns": "Σύντομη βοήθεια σχετικά με τα πρότυπα αναζήτησης που υποστηρίζονται",
"RAM Utilization": "Επιβάρυνση RAM",
"Random": "Τυχαία",
"Reduced by ignore patterns": "Περιορισμένα λόγω προτύπων αγνόησης",
"Release Notes": "Σημείωμα έκδοσης",
"Remote Devices": "Απομακρυσμένες συσκευές",
"Remove": "Αφαίρεση",
@@ -145,7 +146,7 @@
"Restart": "Επανεκκίνηση",
"Restart Needed": "Απαιτείται επανεκκίνηση",
"Restarting": "Επανεκκίνηση",
"Resume": "Συνέχιση",
"Resume": "Συνέχεια",
"Reused": "Χρησιμοποιήθηκε ξανά",
"Save": "Αποθήκευση",
"Scan Time Remaining": "Εναπομείναντας χρόνος για τον έλεγχο ",
@@ -208,8 +209,8 @@
"They are retried automatically and will be synced when the error is resolved.": "Όταν επιλυθεί το σφάλμα θα κατεβούν και θα συχρονιστούν αυτόματα.",
"This Device": "Αυτή η συσκευή",
"This can easily give hackers access to read and change any files on your computer.": "Αυτό μπορεί εύκολα να δώσει πρόσβαση ανάγνωσης και επεξεργασίας αρχείων του υπολογιστή σας σε χάκερς.",
"This is a major version upgrade.": "Αυτή είναι μιας σημαντική αναβάθμιση.",
"Trash Can File Versioning": "Ο κάδος μπορεί να τηρεί εκδόσεις",
"This is a major version upgrade.": "Αυτή είναι μια σημαντική αναβάθμιση.",
"Trash Can File Versioning": "Τήρηση εκδόσεων κάδου ανακύκλωσης",
"Unknown": "Άγνωστο",
"Unshared": "Δε μοιράζεται",
"Unused": "Δε χρησιμοποιείται",
@@ -230,6 +231,8 @@
"Yes": "Ναι",
"You must keep at least one version.": "Πρέπει να τηρήσεις τουλάχιστον μια έκδοση.",
"days": "Μέρες",
"directories": "κατάλογοι",
"files": "αρχεία",
"full documentation": "πλήρης τεκμηρίωση",
"items": "εγγραφές",
"{%device%} wants to share folder \"{%folder%}\".": "Η συσκευή {{device}} θέλει να μοιράσει τον φάκελο «{{folder}}».",

View File

@@ -135,6 +135,7 @@
"Quick guide to supported patterns": "Quick guide to supported patterns",
"RAM Utilization": "RAM Utilisation",
"Random": "Random",
"Reduced by ignore patterns": "Reduced by ignore patterns",
"Release Notes": "Release Notes",
"Remote Devices": "Remote Devices",
"Remove": "Remove",
@@ -230,6 +231,8 @@
"Yes": "Yes",
"You must keep at least one version.": "You must keep at least one version.",
"days": "days",
"directories": "directories",
"files": "files",
"full documentation": "full documentation",
"items": "items",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\".",

View File

@@ -135,6 +135,7 @@
"Quick guide to supported patterns": "Quick guide to supported patterns",
"RAM Utilization": "RAM Utilization",
"Random": "Random",
"Reduced by ignore patterns": "Reduced by ignore patterns",
"Release Notes": "Release Notes",
"Remote Devices": "Remote Devices",
"Remove": "Remove",
@@ -230,6 +231,8 @@
"Yes": "Yes",
"You must keep at least one version.": "You must keep at least one version.",
"days": "days",
"directories": "directories",
"files": "files",
"full documentation": "full documentation",
"items": "items",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\".",

View File

@@ -135,6 +135,7 @@
"Quick guide to supported patterns": "Guía rápida de patrones soportados",
"RAM Utilization": "Uso de RAM",
"Random": "Aleatorio",
"Reduced by ignore patterns": "Reduced by ignore patterns",
"Release Notes": "Notas de la versión",
"Remote Devices": "Otros dispositivos",
"Remove": "Eliminar",
@@ -230,6 +231,8 @@
"Yes": "Si",
"You must keep at least one version.": "Debes mantener al menos una versión.",
"days": "días",
"directories": "directories",
"files": "files",
"full documentation": "Documentación completa",
"items": "Elementos",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} quiere compartir la carpeta \"{{folder}}\".",

View File

@@ -14,13 +14,13 @@
"Addresses": "Direcciones",
"Advanced": "Avanzada",
"Advanced Configuration": "Configuración avanzada",
"Advanced settings": "Configuración avanzada",
"Advanced settings": "Optiones avanzadas",
"All Data": "Todos los datos",
"Allow Anonymous Usage Reporting?": "Permitir reporte anónimo de uso?",
"Alphabetic": "Alfabético",
"An external command handles the versioning. It has to remove the file from the synced folder.": "Un comando exterior maneja el control de versiones. Éste tiene que eliminar el archivo de la carpeta sincronizada.",
"Anonymous Usage Reporting": "Reporte anónimo de uso",
"Any devices configured on an introducer device will be added to this device as well.": "Cualquier dispositivo configurado en un dispositivo introductor será también agregado a este dispositivo.",
"Any devices configured on an introducer device will be added to this device as well.": "Cualquier dispositivo configurado en un dispositivo introductor será también agregado a tu propio dispositivo.",
"Automatic upgrades": "Actualizaciones automáticas",
"Be careful!": "¡Sé cuidadoso!",
"Bugs": "Errores",
@@ -31,7 +31,7 @@
"Command": "Comando",
"Comment, when used at the start of a line": "Comentario, cuando es utilizado al inicio de una línea.",
"Compression": "Compresión",
"Configured": "Configured",
"Configured": "Configurado",
"Connection Error": "Error de conexión",
"Connection Type": "Tipo de conexión",
"Copied from elsewhere": "Copiado desde otra parte.",
@@ -45,7 +45,7 @@
"Device Name": "Nombre del dispositivo",
"Devices": "Dispositivos",
"Disconnected": "Desconectado",
"Discovered": "Discovered",
"Discovered": "Descubierto",
"Discovery": "Búsqueda",
"Documentation": "Documentación",
"Download Rate": "Tasa de descarga",
@@ -55,7 +55,7 @@
"Editing": "Editando",
"Enable NAT traversal": "Habilitar NAT trasversal",
"Enable Relaying": "Habilitar Retransmisión",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Introduce las direcciones (\"tcp://ip:port\", \"tcp://host:port\") separadas por comas o \"dynamic\" para ejecutar un descubrimiento automático de la dirección. ",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Introduce las direcciones (\"tcp://ip:puerto\", \"tcp://huésped:puerto\") separadas por comas, o \"dynamic\" para ejecutar un descubrimiento automático de la dirección. ",
"Enter ignore patterns, one per line.": "Añadir patrones de exclusión, uno por línea.",
"Error": "Error",
"External File Versioning": "Control de versiones externo",
@@ -63,8 +63,8 @@
"File Pull Order": "Orden para coger ficheros",
"File Versioning": "Control de versiones",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Los permisos de archivo son ignorados al buscar cambios. Usar el sistemas de archivos FAT.",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Los archivos son movidos al directorio .stversions cuando son reemplazados o eliminados por Syncthing.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Lo archivos son movidos al directorio .stversions y renombrados a versiones marcadas por fecha cuando son reemplazados o eliminados por Syncthing,",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Los archivos son movidos al directorio .stversions cuando son reemplazados o eliminados por Syncthing. Sus caminos de accesos relativos son recreados aquí si necesidad. ",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Lo archivos son movidos al directorio .stversions y renombrados a versiones marcadas por fecha cuando son reemplazados o eliminados por Syncthing, Sus caminos de accesos relativos son recreados aquí si necesidad.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Los archivos están protegidos frente a los cambios realizados en otros dispositivos, peros los cambios realizados en este dispositivo serán envíados al resto del grupo",
"Folder": "Repositorio",
"Folder ID": "ID del repositorio",
@@ -87,7 +87,7 @@
"Ignore Permissions": "Ignorar permisos",
"Incoming Rate Limit (KiB/s)": "Límite de velocidad de entrada (KiB/s)",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Configuración incorrecta puede dañar los contenidos de la carpeta y hacer Syncthing inoperable.",
"Introducer": "Introductor",
"Introducer": "Dispositivo introductor",
"Inversion of the given condition (i.e. do not exclude)": "Inversión de la condición dada (es decir, no excluir)",
"Keep Versions": "Conservar versiones",
"Largest First": "Más grande primero",
@@ -135,6 +135,7 @@
"Quick guide to supported patterns": "Guía rápida sobre los patrones soportados",
"RAM Utilization": "Utilización de RAM",
"Random": "Aleatorio",
"Reduced by ignore patterns": "(Restringido por patrones de exclusión)",
"Release Notes": "Notas de lanzamiento",
"Remote Devices": "Otros dispositivos",
"Remove": "Eliminar",
@@ -161,8 +162,8 @@
"Shared With": "Compartido con",
"Show ID": "Mostrar ID",
"Show QR": "Mostrar QR",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Mostrado en lugar de la ID del dispositivo en el estado del grupo. Será sugerido a otros dispositivos como nombre predeterminado opcional.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Mostrado en lugar de la ID del dispositivo en el estado del grupo. Si se deja en blanco, será usado el nombre sugerido por el dispositivo.",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Mostrado en lugar de la ID del dispositivo en el estado del grupo. Será sugerido a otros dispositivos como nombre fácil de usar predeterminado opcional.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Mostrado en lugar de la ID del dispositivo en el estado del grupo. Si se deja en blanco, será llenado por el nombre fácil de usar sugerido por el dispositivo.",
"Shutdown": "Apagar",
"Shutdown Complete": "Apagado completado",
"Simple File Versioning": "Versiones simple de archivos",
@@ -230,6 +231,8 @@
"Yes": "Sí",
"You must keep at least one version.": "Debe mantener al menos una versión",
"days": "días",
"directories": "directorios",
"files": "archivos",
"full documentation": "documentación completa",
"items": "ítems",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} quiere compartir repositorio \"{{folder}}\".",

View File

@@ -0,0 +1,240 @@
{
"A device with that ID is already added.": "Id horrekin beste talde bat gehitu da.",
"A negative number of days doesn't make sense.": "Hemen 0 edo zenbaki positiboa ,mesedez.",
"A new major version may not be compatible with previous versions.": "Aldaketa garrantzitsuak dituen bertsio berria beharbada ez da bateragarria izango bertsio zaharragoekin.",
"API Key": "API giltza",
"About": "Egoki",
"Actions": "Egintzak",
"Add": "Gaineratu",
"Add Device": "Gaineratu makina",
"Add Folder": "Gaineratu partekatze",
"Add Remote Device": "Gaineratu makinan izan",
"Add new folder?": "Gaineratu hau partekatze ?",
"Address": "Helbide",
"Addresses": "Helbidek",
"Advanced": "Aditu",
"Advanced Configuration": "Konfigurazio aintzinatua",
"Advanced settings": "Ezarpen aurreratuak",
"All Data": "Datu guziak",
"Allow Anonymous Usage Reporting?": "Izenik gabeko erabiltze erreportak baimendu",
"Alphabetic": "Alfabetikoa",
"An external command handles the versioning. It has to remove the file from the synced folder.": "Kanpoko kontrolagailu batek bertsioak erabiltzen ditu. Fitxeroak errepertorio sinkronizatutik desagertaraztea berari doakio.",
"Anonymous Usage Reporting": "Izenik gabeko erabiltze erreportak",
"Any devices configured on an introducer device will be added to this device as well.": "Sarrarazle batean sartua izanen edozein tresna dena huntan ere izanen da.",
"Automatic upgrades": "Aktualizatze automatikoak",
"Be careful!": "Kasu!",
"Bugs": "Akatsek",
"CPU Utilization": "Prozesadore erabiltze",
"Changelog": "Bertsio historia",
"Clean out after": "Garbi …. epearen ondotik",
"Close": "Ezeztatu",
"Command": "Kontrolagailua",
"Comment, when used at the start of a line": "Komentarioa, lerro baten hastean delarik",
"Compression": "Trinkotze",
"Configured": "Konfiguratua",
"Connection Error": "Konexio hutsa",
"Connection Type": "Konexion mota",
"Copied from elsewhere": "Beste nunbaitik kopiatua",
"Copied from original": "Kopiatua jatorrizkoatik",
"Copyright © 2014-2016 the following Contributors:": "Copyright 2014-2016, ekarle hauk:",
"Danger!": "Lanjer !",
"Deleted": "Ezeztatu",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Makina \"{{name}}\" ({{device}} izan {{address}}) konektatu nahi du. Gaineratu berri makina ?",
"Device ID": "Makina ID",
"Device Identification": "Tresnaren identifikazioa",
"Device Name": "Makina izen",
"Devices": "Makinak",
"Disconnected": "Desloturik",
"Discovered": "Ziloa",
"Discovery": "Aurkikuntza",
"Documentation": "Dokumentazio",
"Download Rate": "Deskargatze emari",
"Downloaded": "Telekargatua",
"Downloading": "Deskargatua",
"Edit": "Aldatu",
"Editing": "Aldaketa",
"Enable NAT traversal": "Ahalbidetu NAT",
"Enable Relaying": "Ahalbidetu lekua hartu",
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": " (\"tcp://ip:port\" ou \"tcp://nom:port\") zuzenbideak sar, krakotx batez separatuak edo bestenaz \"dynamic\", zuzenbidearen xekatze automatikoa aktibatzeko\nTu peux traduire nom et port dans la parenthèse, c'est pas des variables du programme, juste du texte explicatif",
"Enter ignore patterns, one per line.": "Ezkluzio filtroak sar, lerro batean bakar bat",
"Error": "Huts",
"External File Versioning": "Fitxero bertsioen kanpoko kudeaketa",
"Failed Items": "Fitxategiken huts",
"File Pull Order": "Fitxategiak irekitzeko agindua",
"File Versioning": "Artxiboak babesteko metodoa",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Aldaketa bilaketetan fitxero baimenen bitak ez dira kontuan hartuko. Fitxero FAT sistimetan erabilia.",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": ".stbersioak azpi karpetan lekutuko dira fitxeroak, Syncthing-ek aldatu edo ezeztatuko dituelarik. Beren helbide errelatiboak hor berean berriz sortuak izanen dira, behar balin bada",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": ".stbersioak azpi karpetan lekutuko eta ordu-markatuko dira fitxeroak, Syncthing-ek aldatu edo ezeztatuko dituelarik. Beren helbide errelatiboak hor berean berriz sortuak izanen dira, behar balin bada",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Beste tresnetan eginak izanen diren aldaketetatik zainduak izanen dira fitxeroak; haatik, tresna huntan egindako aldaketak besteeri hedatuak izanen dira",
"Folder": "Partekatze",
"Folder ID": "Partekatze ID",
"Folder Label": "Partekatze izengoiti",
"Folder Path": "Partekatze bidexka",
"Folder Type": "Partekatze mota",
"Folders": "Partekatzek",
"GUI": "Interfaze grafiko",
"GUI Authentication Password": "Interfaze grafiko pasahitz",
"GUI Authentication User": "Interfaze grafiko erabiltzaile",
"GUI Listen Addresses": "Interfaze grafiko helbide",
"Generate": "Sortu",
"Global Discovery": "Aurkikuntza oso",
"Global Discovery Servers": "Aurkikuntza oso zerbitzarik",
"Global State": "Oso egoera",
"Help": "Aiuta",
"Home page": "Errezibitze",
"Ignore": "Baztertu",
"Ignore Patterns": "Bazterketak arauk",
"Ignore Permissions": "Baztertu baimenek",
"Incoming Rate Limit (KiB/s)": "Deskargatze emari gehieneko (KiB/s)",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Langer !!!!",
"Introducer": "Abiarazle",
"Inversion of the given condition (i.e. do not exclude)": "Emana izan den baldintza alderantziz eman (i.e ez baztertu)",
"Keep Versions": "Gorde bertsioak",
"Largest First": "Handienak lehenik",
"Last File Received": " Fitxategi azken eskuratu",
"Last Scan": "Azterketa azken",
"Last seen": "Azken agerraldia",
"Later": "Berantago",
"Listeners": "Entzungailuak",
"Local Discovery": "Lekuko aurkikuntza",
"Local State": "Lekuko egoera",
"Local State (Total)": "Lekuko egoera (Oso)",
"Major Upgrade": "Nagusi eguneratu",
"Master": "Nagusi",
"Maximum Age": "Goren adin",
"Metadata Only": "Metadatuak bakarrik",
"Minimum Free Disk Space": "Diskoan leku libre gutxieneko",
"Move to top of queue": "Igurikatze zerrenda bururat lekuz alda",
"Multi level wildcard (matches multiple directory levels)": "Hein askorentzako jokerra (errepertorio eta azpi errepertorioeri dagokiona)",
"Never": "Sekulan",
"New Device": "Berria makina",
"New Folder": "Berri partekatze",
"Newest First": "Gehien gertatu berri lehen",
"No": "Ez",
"No File Versioning": "Ez babestu",
"Normal": "Normal",
"Notice": "Jakinaraztea",
"OK": "Ados",
"Off": "Desgaitu",
"Oldest First": "Gehien zahar lehen",
"Optional descriptive label for the folder. Can be different on each device.": "Partikatzearen izen hautuzkoa eta atsegina, zure gisa. Tresna bakotxean desberdina izaiten ahal da.",
"Options": "Hautuk",
"Out of Sync": "Ez sinkronizatua",
"Out of Sync Items": "Ez sinkronizatu elementuak",
"Outgoing Rate Limit (KiB/s)": "Bidaltze emari gehieneko (KiB/s)",
"Override Changes": "Aldaketak desegin",
"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": "Bertako tresnaren karpetari buruzko bidea. Ez balin bada, asmatu beharko da bat. Tildea (~, edo ~+Espazioa Windows XP+Azerty-n) erabil litzateke bide motz gisa.",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Kopiak kontserbatzeko bidea (hutsa utzezazu, .stversioen karpetaren bide ohituan).",
"Pause": "Pausa",
"Paused": "Geldirik",
"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 sartzeko giltza bat",
"Please wait": "Oraino pazientzia pixka bat har ezazu",
"Preview": "Aurrebista",
"Preview Usage Report": "Erabiltze aurrebista",
"Quick guide to supported patterns": "Filtro ez onartuen txostena",
"RAM Utilization": "RAM erabiltze",
"Random": "Aleatorio",
"Reduced by ignore patterns": "(Mugatu baztertzek patroikez)",
"Release Notes": "Bertsioen notak",
"Remote Devices": "Besteak makinak",
"Remove": "Kendu",
"Required identifier for the folder. Must be the same on all cluster devices.": "Partikatzearen erabilzaile izena. Diren tresna guzietan berdin berdina izan behar du",
"Rescan": "Berreskanea",
"Rescan All": "Berreskanea guzia",
"Rescan Interval": "Arte berreskanea",
"Restart": "Berriz abiatu",
"Restart Needed": "Berriz piztea beharrezkoa",
"Restarting": "Berriz piztea martxan",
"Resume": "Berriz hasi",
"Reused": "Berriz erabilia",
"Save": "Begiratu",
"Scan Time Remaining": "Gelditzen den denbora azterketa",
"Scanning": "Etengabeko azterketa",
"Select the devices to share this folder with.": "Honekin sinkronizatua:",
"Select the folders to share with this device.": "Tresna hunek erabiltzen dituen banaketak hauta",
"Settings": "Egokitzek",
"Share": "Banatu",
"Share Folder": "Banatu",
"Share Folders With Device": "Partekatu makinakekin",
"Share With Devices": "Partekatuekin makinak",
"Share this folder?": "Banatze hau onartzen duzu?",
"Shared With": "Partekatuekin",
"Show ID": "Erakutsi ene ID",
"Show QR": "Erakutsi QR",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Tresnaren ID-aren ordez erakutsia, taldearen egoeran. Beste tresneri erakutsia izanen da, izen erabilgarria bezala",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Tresnaren ID-aren ordez erakutsia, taldearen egoeran. Hutsa utzia balin bada, urrun den tresnak proposatu izenarekin aktualizatua izanen da",
"Shutdown": "Utzi",
"Shutdown Complete": "Gelditua!",
"Simple File Versioning": "Bertsioen segitze sinplifikatuak",
"Single level wildcard (matches within a directory only)": "Hein bakar bateko jokerra (karpetaren barnean bakarrik dagokiona)",
"Smallest First": "Tipienak lehenik",
"Source Code": "Iturri kode",
"Staggered File Versioning": "Bertsio eskaleratuak",
"Start Browser": "Web nabigatzailea pitz",
"Statistics": "Statistikak",
"Stopped": "Gelditua!",
"Support": "Foroa",
"Sync Protocol Listen Addresses": "Sinkronizatu protokoloaren entzun zuzenbideak",
"Syncing": "Sinkronizazio joaira",
"Syncthing has been shut down.": "Syncthing gelditua izan da",
"Syncthing includes the following software or portions thereof:": "Syncthing-ek programa hauk integratzen ditu (edo programa hauetatik datozten elementuak):",
"Syncthing is restarting.": "Syncthing berriz pizten ari",
"Syncthing is upgrading.": "Syncthing aktualizatzen ari da",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Iduri luke Syncthing gelditua dela, edo bestenaz arrazo bat bada interneten konekzioarekin. Berriz entsea zaitez…",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Iduri luke Syncthing arazo bat duela zure eskaeraren tratatzeko. Otoi, horria freska ezazu edo bestenaz Syncthing berriz pitz arazoak segitzen badu.",
"The Syncthing admin interface is configured to allow remote access without a password.": "Syncthing administrazio interfazea pentsatua da urrundikako irisbideak sekretu hitz gabe onartzeko !!!",
"The aggregated statistics are publicly available at the URL below.": "Estadistikak zuzen bide honetan publikoki ikusgarri dira",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Konfigurazioa grabatua izan da bainan ez aktibatua. Syncthing berriz piztu behar da konfigurazio berriaren berriz aktibatzeko.",
"The device ID cannot be blank.": "Tresnaren ID-a ez da hutsa izaiten ahal.",
"The device ID to enter here can be found in the \"Actions > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "Tresnaren ID-a atxemaiten ahal da \"Ekintza> Tresna urrunduaren \"ID-a erakuts\" menuan. Espazio eta gioiak ez dira beharrezkoak.",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Erabileraren zifratu txostena egun guziz igorria da. Erabili diren plataformak, banaketeen neurriak eta aplikazioaren bertsioen zerendatzeko balio du.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "Sartu den tresnaren ID-ak iduri du ez duela balio. 52 edo 56-ko ezaugarriko kadena baten itxura behar luke, hizkiak, zifrak eta baita ere tarte edo gioiez egina",
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "Agingailu lerroaren lehen parametroa banatua den karpetaren bidea da, eta bigarrena karpetan den errelatibo bidea",
"The folder ID cannot be blank.": "banatzearen ID-a ez da hutsa izaiten ahal",
"The folder ID must be unique.": "Banatzearen ID-ak bakarra izan behar du",
"The folder path cannot be blank.": "Karpetari buruzko bidea ez da hutsa izaiten ahal",
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "Hunako tarteak erabiliak dira: oren bateko denboraldian bertsio bat kontserbatua da 30 segundu guziz. Egun batekoan,bertsio bat egunero. Handik harat, adinaren mugetan egonez, bertsio bat astero.",
"The following items could not be synchronized.": "Ondoko fitxero hauk ez dira sinkronizatuak ahal izan",
"The maximum age must be a number and cannot be blank.": "Gehieneko adinak zenbaki bat behar du izan eta ez da hutsa izaiten ahal.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Bertsio baten kontserbatzeko epe haundiena (egunez behar du izan. Jar ezazu zerotan bertsioak betirako atxikitzeko)",
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "Diskoaren ehuneko espazioa hutsak zenbaki positibo bat behar du izan, 0 eta 100-en artekoa (100 barne)",
"The number of days must be a number and cannot be blank.": "Egunen kopuruak numerikoa izan behar du eta ez da hutsa izaiten ahal",
"The number of days to keep files in the trash can. Zero means forever.": "Zikin ontziko elgar hizketen kontserbatzeko egun kopurua. Beti 0 erran nahi du.",
"The number of old versions to keep, per file.": "Atxikitzeko diren lehenagoko bertsio kopurua,fitxero bakotxarentzat",
"The number of versions must be a number and cannot be blank.": "Bertsio kopuruak numerikoa behar du izan eta ez da hutsa izaiten ahal",
"The path cannot be blank.": "Bidea ez da hutsa izaiten ahal",
"The rate limit must be a non-negative number (0: no limit)": "Ixuriaren neurria ez da negatiboa izaiten ahal (0 = mugarik gabekoa)",
"The rescan interval must be a non-negative number of seconds.": "Ikerketaren tartea ez da segundu kopuru negatiboa izaiten ahal",
"They are retried automatically and will be synced when the error is resolved.": "Akatsa zuzendua izanen delarik, automatikoki berriz entseatuak et sinkronizatuak izanen dira",
"This Device": "Makina hau",
"This can easily give hackers access to read and change any files on your computer.": "Hunek errexki irakurtzen eta aldatzen uzten ahal du zure ordenagailuko edozein fitxero, nahiz eta sartu denak ez haizu!",
"This is a major version upgrade.": "Aktualizatze garrantzitsu bat da",
"Trash Can File Versioning": "Zakarrontzia",
"Unknown": "Ez ezaguna",
"Unshared": "Partekatu ez den",
"Unused": "Ez baliatua",
"Up to Date": "Egun",
"Updated": "Berritu",
"Upgrade": "Aktualizatu",
"Upgrade To {%version%}": "Egunetaratzea {{version}}-i buruz",
"Upgrading": "Syncthing-en egunetaratzea",
"Upload Rate": "Bidaltze emari",
"Uptime": "Denbora ibiltze",
"Use HTTPS for GUI": "HTTPS-a erabil GUI-arentzat",
"Version": "Bertsio",
"Versions Path": "Bertsioen egon tokia",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Bertsioak automatikoki ezeztatuak izanen dira, kontserbatzeko iraupen denbora pasatua badute edo bitartean onartua den kopurua gainditua balin bada",
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Kasu emazu, \"{{otherFolder}}\" banaketa azpi-karpetaren bidea da. Arazoak emaiten ahal ditu, fitxero batzuen ezeztatze edo duplikatzeak batez ere.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Tresna bat gehitzen duzularik, gogoan atxik ezazu zurea bestaldean gehitu behar dela ere",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Tresna bat gehitzen delarik, gogoan atxik ezazu bere IDa erabilia dela errepertorioak lotzeko tresnen bitartez. ID-a hautskorra da eta banatze huntan parte hartzen duten tresna guzietan berdina izanen da",
"Yes": "Bai",
"You must keep at least one version.": "Bertsio bat bederen behar duzu atxiki",
"days": "Egunak",
"directories": "karpetak",
"files": "fitxategik",
"full documentation": "Dokumentazio osoa",
"items": "Elementuak",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} banaketa \"{{folder}}\" gomitatzen zaitu.",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} \"{{folderlabel}}\" ({{folder}}) gomitatzen zaitu."
}

View File

@@ -135,6 +135,7 @@
"Quick guide to supported patterns": "Tuettujen lausekkeiden pikaohje",
"RAM Utilization": "RAM:n käyttö",
"Random": "Satunnainen",
"Reduced by ignore patterns": "Vähennetty ohituslausekkeiden perusteella",
"Release Notes": "Julkaisutiedot",
"Remote Devices": "Laitteet",
"Remove": "Poista",
@@ -230,6 +231,8 @@
"Yes": "Kyllä",
"You must keep at least one version.": "Sinun tulee säilyttää ainakin yksi versio.",
"days": "päivää",
"directories": "kansiot",
"files": "tiedostot",
"full documentation": "täysi dokumentaatio",
"items": "kohteet",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} haluaa jakaa kansion \"{{folder}}\".",

View File

@@ -9,18 +9,18 @@
"Add Device": "Ajouter l'appareil",
"Add Folder": "Ajouter un partage",
"Add Remote Device": "Ajouter un appareil",
"Add new folder?": "Ajouter un nouveau partage ?",
"Add new folder?": "Ajouter ce partage ?",
"Address": "Adresse",
"Addresses": "Adresses",
"Advanced": "Avancé",
"Advanced Configuration": "Configuration avancée",
"Advanced settings": "Réglages experts",
"Advanced settings": "Paramètres avancés",
"All Data": "Toutes les données",
"Allow Anonymous Usage Reporting?": "Autoriser l'envoi de statistiques d'utilisation anonymisées ?",
"Alphabetic": "Alphabétique",
"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",
"Any devices configured on an introducer device will be added to this device as well.": "Tout appareil ajouté depuis un appareil introducteur sera aussi ajouté sur votre appareil.",
"Any devices configured on an introducer device will be added to this device as well.": "Tout appareil ajouté sur un appareil introducteur sera aussi ajouté sur votre propre appareil.",
"Automatic upgrades": "Mises à jour automatiques",
"Be careful!": "Faites attention !",
"Bugs": "Bugs",
@@ -31,7 +31,7 @@
"Command": "Commande",
"Comment, when used at the start of a line": "Commentaire lorsque utilisé en début de ligne",
"Compression": "Compression",
"Configured": "Configured",
"Configured": "Configuré",
"Connection Error": "Erreur de connexion",
"Connection Type": "Type de connexion",
"Copied from elsewhere": "Copié d'ailleurs",
@@ -45,7 +45,7 @@
"Device Name": "Nom de l'appareil",
"Devices": "Appareil",
"Disconnected": "Déconnecté",
"Discovered": "Discovered",
"Discovered": "Découvert",
"Discovery": "Découverte",
"Documentation": "Documentation",
"Download Rate": "Débit de réception",
@@ -55,7 +55,7 @@
"Editing": "Modifications",
"Enable NAT traversal": "Activer transfert d'adresses NAT",
"Enable Relaying": "Activer le relayage",
"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://host:port\") séparées par une virgule ou \"dynamic\" afin d'activer la recherche automatique de l'adresse.",
"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.": "Entrer les masques d'exclusion, un par ligne.",
"Error": "Erreur",
"External File Versioning": "Gestion externe des versions de fichiers",
@@ -63,8 +63,8 @@
"File Pull Order": "Ordre de récupération de fichier",
"File Versioning": "Méthode de préservation des fichiers",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Les bits de permission de fichier sont ignorés lors de la recherche de changements. Utilisé sur les systèmes de fichiers FAT.",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Les fichiers sont déplacés vers le répertoire .stversions quand ils sont remplacés ou effacés par Syncthing.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Les fichiers sont déplacés, avec horodatage, dans le répertoire .stversions quand ils sont remplacés ou supprimés par Syncthing.",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Les fichiers sont déplacés dans le sous-répertoire .stversions quand ils sont remplacés ou supprimés par Syncthing. Leurs chemins d'accès relatifs y sont recréés si besoin.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Les fichiers sont déplacés, avec horodatage, dans le répertoire .stversions quand ils sont remplacés ou supprimés par Syncthing. Leurs chemins d'accès relatifs y sont recréés si besoin.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Les fichiers sont protégés des changements réalisés sur les autres appareils, mais les changements réalisés sur celui-ci seront transférés aux autres.",
"Folder": "Partage",
"Folder ID": "ID du partage",
@@ -87,10 +87,10 @@
"Ignore Permissions": "Ignorer les permissions",
"Incoming Rate Limit (KiB/s)": "Limite du débit entrant (KiB/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.",
"Introducer": "Introducteur",
"Introducer": "Appareil introducteur",
"Inversion of the given condition (i.e. do not exclude)": "Inverser la condition donnée (i.e. ne pas exclure)",
"Keep Versions": "Combien de versions conserver",
"Largest First": "Les plus volumineux en premier",
"Largest First": "Les plus volumineux d'abord",
"Last File Received": "Dernière mise à jour",
"Last Scan": "Dernière analyse",
"Last seen": "Dernière apparition",
@@ -105,7 +105,7 @@
"Metadata Only": "Métadonnées uniquement",
"Minimum Free Disk Space": "Espace disque libre minimum",
"Move to top of queue": "Déplacer en haut de la file",
"Multi level wildcard (matches multiple directory levels)": "Astérisque à plusieurs niveaux (correspond aux répertoires et sous-répertoires)",
"Multi level wildcard (matches multiple directory levels)": "Joker multi niveaux (correspond aux répertoires et sous-répertoires)",
"Never": "Jamais",
"New Device": "Nouvel appareil",
"New Folder": "Nouveau partage",
@@ -120,10 +120,10 @@
"Optional descriptive label for the folder. Can be different on each device.": "Nom convivial du partage, à votre guise. il peut être différent sur chaque appareil.",
"Options": "Options",
"Out of Sync": "Désynchronisé",
"Out of Sync Items": "Objets non synchronisés",
"Out of Sync Items": "Éléments non synchronisés",
"Outgoing Rate Limit (KiB/s)": "Limite du débit sortant (KiB/s)",
"Override Changes": "Écraser les changements",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Chemin vers le répertoire dans l'appareil local. Il sera créé s'il n'existe pas. Le caractère tilde (~, ou ~+Espace sous Windows+Azerty) peut être utilisé comme raccourci vers",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Chemin vers le répertoire dans l'appareil local. Il sera créé s'il n'existe pas. Le caractère tilde (~, ou ~+Espace sous Windows XP+Azerty) peut être utilisé comme raccourci vers",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Chemin où les versions doivent être conservées (laisser vide pour le chemin par défaut de .stversions dans le répertoire)",
"Pause": "Pause",
"Paused": "En pause",
@@ -135,10 +135,11 @@
"Quick guide to supported patterns": "Guide rapide des masques supportés",
"RAM Utilization": "Utilisation de la RAM",
"Random": "Aléatoire",
"Reduced by ignore patterns": "(Limité par des masques d'exclusion)",
"Release Notes": "Notes de version",
"Remote Devices": "Autres appareils",
"Remove": "Enlever",
"Required identifier for the folder. Must be the same on all cluster devices.": "Identifiant obligatoire du partage. Il doit être identique chez chaque participant.",
"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.",
"Rescan": "Réanalyser",
"Rescan All": "Réanalyser tout",
"Rescan Interval": "Intervalle d'analyse",
@@ -161,16 +162,16 @@
"Shared With": "Synchronisé avec",
"Show ID": "Afficher mon ID",
"Show QR": "Afficher l'image QR",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Affiché à la place de l'ID de l'appareil dans le groupe. Sera proposé aux autres appareils comme nom convivial optionnel par défaut.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Affiché à la place de l'ID de l'appareil dans l'état du groupe. Si laissé vide, il sera mis à jour par le nom proposé par l'appareil distant.",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Affiché à la place de l'ID de l'appareil dans l'état du groupe. Sera diffusé aux autres appareils comme nom convivial optionnel par défaut.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Affiché à la place de l'ID de l'appareil dans l'état du groupe. Si laissé vide, il sera renseigné par le nom convivial proposé par l'appareil distant.",
"Shutdown": "Arrêter",
"Shutdown Complete": "Arrêté !",
"Simple File Versioning": "Suivi simplifié des versions",
"Single level wildcard (matches within a directory only)": "Astérisque à un seul niveau (correspond uniquement à lintérieur du répertoire)",
"Smallest First": "Les plus petits en premier",
"Single level wildcard (matches within a directory only)": "Joker à un seul niveau (correspond uniquement à lintérieur du répertoire)",
"Smallest First": "Les plus petits d'abord",
"Source Code": "Code source",
"Staggered File Versioning": "Versions échelonnées",
"Start Browser": "Démarrer le navigateur web",
"Start Browser": "Lancer le navigateur web",
"Statistics": "Statistiques",
"Stopped": "Arrêté",
"Support": "Forum",
@@ -178,8 +179,8 @@
"Syncing": "En cours de synchronisation",
"Syncthing has been shut down.": "Syncthing a été arrêté.",
"Syncthing includes the following software or portions thereof:": "Syncthing intègre les logiciels suivants (ou des éléments provenant de ces logiciels) :",
"Syncthing is restarting.": "Syncthing est cours de redémarrage.",
"Syncthing is upgrading.": "Syncthing est cours de mise à jour.",
"Syncthing is restarting.": "Syncthing redémarre.",
"Syncthing is upgrading.": "Syncthing se met à jour.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing semble être arrêté, ou il y a un problème avec votre connexion Internet. Nouvelle tentative ...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing semble avoir un problème pour traiter votre demande. S'il vous plaît, rafraîchissez la page ou redémarrez Syncthing si le problème persiste.",
"The Syncthing admin interface is configured to allow remote access without a password.": "L'interface d'administration de Syncthing est configuré pour accepter l'accès distant sans mot de passe !",
@@ -203,12 +204,12 @@
"The number of old versions to keep, per file.": "Le nombre d'anciennes versions à garder, par fichier.",
"The number of versions must be a number and cannot be blank.": "Le nombre de versions doit être numérique, et ne peut pas être vide.",
"The path cannot be blank.": "Le chemin ne peut pas être vide.",
"The rate limit must be a non-negative number (0: no limit)": "La limite de débit ne doit pas être négative (0: Aucune limite)",
"The rate limit must be a non-negative number (0: no limit)": "La limite de débit ne doit pas être négative (0 = pas de limite)",
"The rescan interval must be a non-negative number of seconds.": "L'intervalle d'analyse ne doit pas être un nombre négatif de secondes.",
"They are retried automatically and will be synced when the error is resolved.": "Ils seront automatiquement retentés et synchronisés quand l'erreur sera résolue.",
"This Device": "Cet appareil",
"This can easily give hackers access to read and change any files on your computer.": "Ceci peut aisément permettre à un intrus de lire et modifier n'importe quel fichier de votre ordinateur. ",
"This is a major version upgrade.": "Ceci est une mise à jour majeure.",
"This is a major version upgrade.": "Il s'agit d'une mise à jour majeure.",
"Trash Can File Versioning": "Style poubelle",
"Unknown": "Inconnu",
"Unshared": "Non partagé",
@@ -226,12 +227,14 @@
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Les versions seront supprimées automatiquement, si elles dépassent la durée maximum de conservation, ou si leur nombre est supérieur à la valeur autorisée dans l'intervalle.",
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Attention, ce chemin est un sous-répertoire du partage existant \"{{otherFolder}}\". Ceci peut causer des problèmes tels que duplications de fichiers ou suppressions intempestives sur les autres machines.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Lorsque vous ajoutez un appareil, gardez à l'esprit que le votre doit aussi être ajouté de l'autre coté.",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Lorsqu'un nouveau partage est ajouté, gardez à l'esprit que son ID est utilisée pour lier les répertoires à travers les appareils. L'ID est sensible à la casse et sera forcément la même sur toutes les appareils participant à ce partage.",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Lorsqu'un nouveau partage est ajouté, gardez à l'esprit que son ID est utilisée pour lier les répertoires à travers les appareils. L'ID est sensible à la casse et sera forcément la même sur tous les appareils participant à ce partage.",
"Yes": "Oui",
"You must keep at least one version.": "Vous devez garder au minimum une version.",
"days": "Jours",
"directories": "répertoires",
"files": "fichiers",
"full documentation": "documentation complète",
"items": "éléments",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} vous invite au partage \"{{folderLabel}}\".",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} vous invite au partage \"{{folderLabel}}\" ({{folder}})."
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} vous invite au partage \"{{folder}}\".",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} vous invite au partage \"{{folderlabel}}\" ({{folder}})."
}

View File

@@ -9,18 +9,18 @@
"Add Device": "Ajouter l'appareil",
"Add Folder": "Ajouter un partage",
"Add Remote Device": "Ajouter un appareil",
"Add new folder?": "Ajouter un nouveau partage ?",
"Add new folder?": "Ajouter ce partage ?",
"Address": "Adresse",
"Addresses": "Adresses",
"Advanced": "Avancé",
"Advanced Configuration": "Configuration avancée",
"Advanced settings": "Configuration avancée",
"Advanced settings": "Paramètres avancés",
"All Data": "Toutes les données",
"Allow Anonymous Usage Reporting?": "Autoriser l'envoi de statistiques d'utilisation anonymisées ?",
"Alphabetic": "Alphabétique",
"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",
"Any devices configured on an introducer device will be added to this device as well.": "Tout appareil ajouté depuis un appareil introducteur sera aussi ajouté sur cet appareil.",
"Any devices configured on an introducer device will be added to this device as well.": "Tout appareil ajouté sur un appareil introducteur sera aussi ajouté sur votre propre appareil.",
"Automatic upgrades": "Mises à jour automatiques",
"Be careful!": "Faites attention !",
"Bugs": "Bugs",
@@ -55,7 +55,7 @@
"Editing": "Modifications",
"Enable NAT traversal": "Activer la translation d'adresses (NAT)",
"Enable Relaying": "Relayage possible",
"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://host:port\") séparées par une virgule ou \"dynamic\" afin d'activer la recherche automatique de l'adresse.",
"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",
@@ -63,8 +63,8 @@
"File Pull Order": "Ordre de récupération des fichiers",
"File Versioning": "Méthode de préservation des fichiers",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Les bits de permission de fichier sont ignorés lors de la recherche de changements. Utilisé sur les systèmes de fichiers FAT.",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Les fichiers sont déplacés dans le sous-répertoire .stversions quand ils sont remplacés ou supprimés par Syncthing.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Les fichiers sont déplacés et horodatés dans le sous-répertoire .stversions quand ils sont remplacés ou supprimés par Syncthing.",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Les fichiers sont déplacés dans le sous-répertoire .stversions quand ils sont remplacés ou supprimés par Syncthing. Leurs chemins d'accès relatifs y sont recréés si besoin.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Les fichiers sont déplacés et horodatés dans le sous-répertoire .stversions quand ils sont remplacés ou supprimés par Syncthing. Leurs chemins d'accès relatifs y sont recréés si besoin.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Les fichiers sont protégés des changements réalisés sur les autres appareils, mais les changements réalisés sur celui-ci seront transférés aux autres.",
"Folder": "Partage",
"Folder ID": "ID du partage",
@@ -87,10 +87,10 @@
"Ignore Permissions": "Ignorer les permissions",
"Incoming Rate Limit (KiB/s)": "Limite du débit de réception (Ko/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.",
"Introducer": "introducteur",
"Introducer": "Appareil introducteur",
"Inversion of the given condition (i.e. do not exclude)": "Inverser la condition donnée (i.e. ne pas exclure)",
"Keep Versions": "Combien de versions conserver",
"Largest First": "Les plus volumineux en premier",
"Largest First": "Les plus volumineux d'abord",
"Last File Received": "Dernière mise à jour",
"Last Scan": "Dernière analyse",
"Last seen": "Dernière apparition",
@@ -105,7 +105,7 @@
"Metadata Only": "Métadonnées uniquement",
"Minimum Free Disk Space": "Espace disque libre minimum",
"Move to top of queue": "Déplacer en haut de la file",
"Multi level wildcard (matches multiple directory levels)": "Astérisque à plusieurs niveaux (correspond aux répertoires et sous-répertoires)",
"Multi level wildcard (matches multiple directory levels)": "Joker multi niveaux (correspond aux répertoires et sous-répertoires)",
"Never": "Jamais",
"New Device": "Nouvel appareil",
"New Folder": "Nouveau partage",
@@ -120,10 +120,10 @@
"Optional descriptive label for the folder. Can be different on each device.": "Nom convivial et optionnel du partage, à votre guise. il peut être différent sur chaque appareil.",
"Options": "Options",
"Out of Sync": "Désynchronisé",
"Out of Sync Items": "Fichiers non synchronisés",
"Out of Sync Items": "Éléments non synchronisés",
"Outgoing Rate Limit (KiB/s)": "Limite du débit d'émission (Ko/s)",
"Override Changes": "Écraser les changements",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Chemin vers le répertoire dans l'appareil local. Il sera créé s'il n'existe pas. Le caractère tilde (~, ou ~+Espace sous Windows+Azerty) peut être utilisé comme raccourci vers",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Chemin vers le répertoire dans l'appareil local. Il sera créé s'il n'existe pas. Le caractère tilde (~, ou ~+Espace sous Windows XP+Azerty) peut être utilisé comme raccourci vers",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Chemin où les copies doivent être conservées (laisser vide pour le chemin par défaut de .stversions dans le répertoire)",
"Pause": "Pause",
"Paused": "En pause",
@@ -132,13 +132,14 @@
"Please wait": "Merci de patienter",
"Preview": "Aperçu",
"Preview Usage Report": "Aperçu du rapport de statistiques d'utilisation",
"Quick guide to supported patterns": "Guide rapide des masques supportés",
"Quick guide to supported patterns": "Filtro ez onartuen txostena",
"RAM Utilization": "Utilisation de la RAM",
"Random": "Aléatoire",
"Reduced by ignore patterns": "(Limité par des masques d'exclusion)",
"Release Notes": "Notes de version",
"Remote Devices": "Autres appareils",
"Remove": "Enlever",
"Required identifier for the folder. Must be the same on all cluster devices.": "Identifiant du partage. Doit être le même sur l'ensemble des appareils concernés.",
"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.",
"Rescan": "Réanalyser",
"Rescan All": "Réanalyser tout",
"Rescan Interval": "Intervalle d'analyse",
@@ -161,16 +162,16 @@
"Shared With": "Synchronisé avec",
"Show ID": "Afficher mon ID",
"Show QR": "Afficher le QR",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Affiché à la place de l'ID de l'appareil dans le groupe. Sera proposé aux autres appareils comme nom convivial optionnel par défaut.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Affiché à la place de l'ID de l'appareil dans l'état du groupe. Si laissé vide, il sera mis à jour par le nom proposé par l'appareil distant.",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Affiché à la place de l'ID de l'appareil dans l'état du groupe. Sera diffusé aux autres appareils comme nom convivial optionnel par défaut.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Affiché à la place de l'ID de l'appareil dans l'état du groupe. Si laissé vide, il sera renseigné par le nom convivial proposé par l'appareil distant.",
"Shutdown": "Arrêter",
"Shutdown Complete": "Arrêté !",
"Simple File Versioning": "Suivi simplifié des versions",
"Single level wildcard (matches within a directory only)": "Astérisque à un seul niveau (correspond uniquement à lintérieur du répertoire)",
"Smallest First": "Les plus petits en premier",
"Single level wildcard (matches within a directory only)": "Joker à un seul niveau (correspond uniquement à lintérieur du répertoire)",
"Smallest First": "Les plus petits d'abord",
"Source Code": "Code source",
"Staggered File Versioning": "Versions échelonnées",
"Start Browser": "Démarrer le navigateur web",
"Start Browser": "Lancer le navigateur web",
"Statistics": "Statistiques",
"Stopped": "Arrêté",
"Support": "Forum",
@@ -178,8 +179,8 @@
"Syncing": "Synchronisation en cours",
"Syncthing has been shut down.": "Syncthing a été arrêté.",
"Syncthing includes the following software or portions thereof:": "Syncthing intègre les logiciels suivants (ou des éléments provenant de ces logiciels) :",
"Syncthing is restarting.": "Syncthing est cours de redémarrage.",
"Syncthing is upgrading.": "Syncthing est cours de mise à jour.",
"Syncthing is restarting.": "Syncthing redémarre.",
"Syncthing is upgrading.": "Syncthing se met à jour.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing semble être arrêté, ou il y a un problème avec votre connexion Internet. Nouvelle tentative ...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing semble avoir un problème pour traiter votre demande. S'il vous plaît, rafraîchissez la page ou redémarrez Syncthing si le problème persiste.",
"The Syncthing admin interface is configured to allow remote access without a password.": "L'interface d'administration de Syncthing est paramétrée pour autoriser les accès à distance sans mot de passe !!!",
@@ -203,12 +204,12 @@
"The number of old versions to keep, per file.": "Le nombre d'anciennes versions à garder, par fichier.",
"The number of versions must be a number and cannot be blank.": "Le nombre de versions doit être numérique, et ne peut pas être vide.",
"The path cannot be blank.": "Le chemin ne peut pas être vide.",
"The rate limit must be a non-negative number (0: no limit)": "La limite de débit ne doit pas être négative (0: Aucune limite)",
"The rate limit must be a non-negative number (0: no limit)": "La limite de débit ne doit pas être négative (0 = pas de limite)",
"The rescan interval must be a non-negative number of seconds.": "L'intervalle d'analyse ne doit pas être un nombre négatif de secondes.",
"They are retried automatically and will be synced when the error is resolved.": "Ils seront automatiquement retentés et synchronisés quand l'erreur sera résolue.",
"This Device": "Cet appareil",
"This can easily give hackers access to read and change any files on your computer.": "Ceci peut aisément permettre à un intrus de lire et modifier n'importe quel fichier de votre ordinateur.",
"This is a major version upgrade.": "Ceci est une mise à jour majeure.",
"This is a major version upgrade.": "Il s'agit d'une mise à jour majeure.",
"Trash Can File Versioning": "Style poubelle",
"Unknown": "Inconnu",
"Unshared": "Non partagé",
@@ -226,12 +227,14 @@
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Les versions seront supprimées automatiquement, si elles dépassent la durée maximum de conservation, ou si leur nombre est supérieur à la valeur autorisée dans l'intervalle.",
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Attention, ce chemin est un sous-répertoire du partage existant \"{{otherFolder}}\". Ceci peut causer des problèmes tels que duplications de fichiers ou suppressions intempestives sur les autres appareils.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "Lorsque vous ajoutez un appareil, gardez à l'esprit que le votre doit aussi être ajouté de l'autre coté.",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Lorsqu'un nouveau partage est ajouté, gardez à l'esprit que son ID est utilisée pour lier les répertoires à travers les appareils. L'ID est sensible à la casse et sera forcément la même sur toutes les appareils participant à ce partage.",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Lorsqu'un nouveau partage est ajouté, gardez à l'esprit que son ID est utilisée pour lier les répertoires à travers les appareils. L'ID est sensible à la casse et sera forcément la même sur tous les appareils participant à ce partage.",
"Yes": "Oui",
"You must keep at least one version.": "Vous devez garder au minimum une version.",
"days": "Jours",
"directories": "répertoires",
"files": "Fichiers",
"full documentation": "documentation complète",
"items": "éléments",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} vous invite au partage \"{{folderLabel}}\".",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} vous invite au partage \"{{folderLabel}}\" ({{folder}})."
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} vous invite au partage \"{{folder}}\".",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} vous invite au partage \"{{folderlabel}}\" ({{folder}})."
}

View File

@@ -31,7 +31,7 @@
"Command": "Kommando",
"Comment, when used at the start of a line": "Kommentaar, wannear as brûkt by it begjin fan in rige",
"Compression": "Kompresje",
"Configured": "Configured",
"Configured": "Konfigureart",
"Connection Error": "Ferbiningsflater",
"Connection Type": "Ferbiningstype",
"Copied from elsewhere": "Oernommen fan earne oars",
@@ -45,7 +45,7 @@
"Device Name": "Apparaatnamme",
"Devices": "Apparaten",
"Disconnected": "Ferbining ferbrutsen",
"Discovered": "Discovered",
"Discovered": "Untdekt",
"Discovery": "Untdekking",
"Documentation": "Dokumintaasje",
"Download Rate": "Ynlaadfluggens",
@@ -135,6 +135,7 @@
"Quick guide to supported patterns": "Fluch-paadwizer foar stipe patroanen",
"RAM Utilization": "RAM-brûken",
"Random": "Willekeurich",
"Reduced by ignore patterns": "Ferlytse troch negear-patroanen",
"Release Notes": "Utjeftenotysjes",
"Remote Devices": "Apparaten op Ofstân",
"Remove": "Fuortsmite",
@@ -230,6 +231,8 @@
"Yes": "Ja",
"You must keep at least one version.": "Jo moatte minstens ien ferzje bewarje.",
"days": "dagen",
"directories": "triemtafels",
"files": "triemmen",
"full documentation": "komplete dokumintaasje",
"items": "items",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wol map \"{{folder}}\" diele.",

View File

@@ -56,7 +56,7 @@
"Enable NAT traversal": "NAT bejárás engedélyezése",
"Enable Relaying": "Közvetítés engedélyezése",
"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. ",
"Enter ignore patterns, one per line.": "Figyelmen kívül hagyáshoz ide írhatod a mintákat, soronként egyet",
"Enter ignore patterns, one per line.": "Add meg a kihagyási mintákat, soronként egyet.",
"Error": "Hiba",
"External File Versioning": "Külső fájl verziókövetés",
"Failed Items": "Hibás elemek",
@@ -83,7 +83,7 @@
"Help": "Súgó",
"Home page": "Főoldal",
"Ignore": "Figyelmen kívül hagyás",
"Ignore Patterns": "Minták figyelmen kívül hagyása",
"Ignore Patterns": "Kihagyási minták",
"Ignore Permissions": "Jogosultságok figyelmen kívül hagyása",
"Incoming Rate Limit (KiB/s)": "Bejövő sebesség korlát (KIB/mp)",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Helytelen konfiguráció esetén károsodhat a mappák tartalma és működésképtelenné válhat a Syncthing.",
@@ -135,6 +135,7 @@
"Quick guide to supported patterns": "Rövid útmutató a használható mintákról",
"RAM Utilization": "Memória használat",
"Random": "Véletlenszerű",
"Reduced by ignore patterns": "Kihagyási mintákkal csökkentve",
"Release Notes": "Kiadási megjegyzések",
"Remote Devices": "Távoli eszközök",
"Remove": "Eltávolítás",
@@ -230,6 +231,8 @@
"Yes": "Igen",
"You must keep at least one version.": "Legalább egy verziót meg kell tartanod.",
"days": "nap",
"directories": "könyvtárak",
"files": "fájlok",
"full documentation": "teljes dokumentáció",
"items": "elem",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} meg szeretné osztani a \"{{folder}}\" nevű mappát.",

View File

@@ -135,6 +135,7 @@
"Quick guide to supported patterns": "Quick guide to supported patterns",
"RAM Utilization": "RAM Utilization",
"Random": "Random",
"Reduced by ignore patterns": "Reduced by ignore patterns",
"Release Notes": "Release Notes",
"Remote Devices": "Remote Devices",
"Remove": "Remove",
@@ -230,6 +231,8 @@
"Yes": "Yes",
"You must keep at least one version.": "You must keep at least one version.",
"days": "days",
"directories": "directories",
"files": "files",
"full documentation": "full documentation",
"items": "items",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\".",

View File

@@ -135,6 +135,7 @@
"Quick guide to supported patterns": "Guida veloce agli schemi supportati",
"RAM Utilization": "Utilizzo RAM",
"Random": "Casuale",
"Reduced by ignore patterns": "Ridotto da schemi di esclusione",
"Release Notes": "Note di Rilascio",
"Remote Devices": "Dispositivi Remoti",
"Remove": "Rimuovi",
@@ -230,6 +231,8 @@
"Yes": "Sì",
"You must keep at least one version.": "È necessario mantenere almeno una versione.",
"days": "giorni",
"directories": "cartelle",
"files": "file",
"full documentation": "documentazione completa",
"items": "elementi",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} vuole condividere la cartella \"{{folder}}\".",

View File

@@ -135,6 +135,7 @@
"Quick guide to supported patterns": "サポートされているパターンの簡易ガイド",
"RAM Utilization": "メモリ使用量",
"Random": "ランダム",
"Reduced by ignore patterns": "Reduced by ignore patterns",
"Release Notes": "リリースノート",
"Remote Devices": "他のデバイス",
"Remove": "除去",
@@ -230,6 +231,8 @@
"Yes": "はい",
"You must keep at least one version.": "少なくとも一つのバージョンを保存してください。",
"days": "日",
"directories": "directories",
"files": "files",
"full documentation": "詳細なマニュアル",
"items": "項目",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} がフォルダー \"{{folder}}\" を共有するよう求めています。",

View File

@@ -31,7 +31,7 @@
"Command": "커맨드",
"Comment, when used at the start of a line": "명령행에서 시작을 할수 있어요.",
"Compression": "압축",
"Configured": "Configured",
"Configured": "설정됨",
"Connection Error": "연결 에러",
"Connection Type": "연결 종류",
"Copied from elsewhere": "다른 곳에서 복사됨",
@@ -45,7 +45,7 @@
"Device Name": "기기 이름",
"Devices": "기기",
"Disconnected": "연결 끊김",
"Discovered": "Discovered",
"Discovered": "탐색됨",
"Discovery": "탐색",
"Documentation": "문서",
"Download Rate": "다운로드 속도",
@@ -135,6 +135,7 @@
"Quick guide to supported patterns": "지원하는 패턴에 대한 빠른 도움말",
"RAM Utilization": "RAM 사용량",
"Random": "무작위",
"Reduced by ignore patterns": "Reduced by ignore patterns",
"Release Notes": "릴리즈 노트",
"Remote Devices": "원격 기기",
"Remove": "삭제",
@@ -230,6 +231,8 @@
"Yes": "예",
"You must keep at least one version.": "최소 한 개의 버전은 유지해야 합니다.",
"days": "일",
"directories": "디렉토리",
"files": "파일",
"full documentation": "전체 문서",
"items": "항목",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} 에서 폴더 \\\"{{folder}}\\\" 를 공유하길 원합니다.",

View File

@@ -31,7 +31,7 @@
"Command": "Komanda",
"Comment, when used at the start of a line": "Komentaras naudojamas naujoje eilutėje",
"Compression": "Kompresija",
"Configured": "Configured",
"Configured": "Sukonfigūruotas",
"Connection Error": "Susijungimo klaida",
"Connection Type": "Ryšio tipas",
"Copied from elsewhere": "Nukopijuota iš kitur",
@@ -45,7 +45,7 @@
"Device Name": "Įrenginio pavadinimas",
"Devices": "Įrenginiai",
"Disconnected": "Atsijungęs",
"Discovered": "Discovered",
"Discovered": "Atrastas",
"Discovery": "Lokacija",
"Documentation": "Aprašymas",
"Download Rate": "Parsisiuntimo greitis",
@@ -135,6 +135,7 @@
"Quick guide to supported patterns": "Trumpas leistinų šablonų vadovas",
"RAM Utilization": "Atminties naudojimas",
"Random": "Atsitiktinė",
"Reduced by ignore patterns": "Reduced by ignore patterns",
"Release Notes": "Laidos Informacija",
"Remote Devices": "Nuotoliniai įrenginiai",
"Remove": "Pašalinti",
@@ -230,6 +231,8 @@
"Yes": "Taip",
"You must keep at least one version.": "Būtina saugoti bent vieną versiją.",
"days": "dienos",
"directories": "papkės",
"files": "failai",
"full documentation": "pilna dokumentacija",
"items": "įrašai",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} nori dalintis aplanku \"{{folder}}\"",

View File

@@ -135,6 +135,7 @@
"Quick guide to supported patterns": "Kjapp innføring i godkjente mønster",
"RAM Utilization": "RAM-utnyttelse",
"Random": "Tilfeldig",
"Reduced by ignore patterns": "Reduced by ignore patterns",
"Release Notes": "Utgivelsesnotat",
"Remote Devices": "Andre enheter",
"Remove": "Fjern",
@@ -230,6 +231,8 @@
"Yes": "Ja",
"You must keep at least one version.": "Du må beholde minst én versjon",
"days": "dager",
"directories": "directories",
"files": "files",
"full documentation": "all dokumentasjon",
"items": "elementer",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} ønsker å dele mappen \"{{folder}}\".",

View File

@@ -1,6 +1,6 @@
{
"A device with that ID is already added.": "Apparaat-ID reeds toegevoegd.",
"A negative number of days doesn't make sense.": "Een negatief aantal dagen is niet logisch.",
"A negative number of days doesn't make sense.": "Een negatief aantal dagen is niet zinvol.",
"A new major version may not be compatible with previous versions.": "Een nieuwe major version is misschien niet compatibel met eerdere versies.",
"API Key": "API-sleutel",
"About": "Over",
@@ -135,6 +135,7 @@
"Quick guide to supported patterns": "Snelgids voor ondersteunde patronen",
"RAM Utilization": "Geheugengebruik",
"Random": "Willekeurig",
"Reduced by ignore patterns": "Reduced by ignore patterns",
"Release Notes": "Release notes",
"Remote Devices": "Externe apparaten",
"Remove": "Verwijderen",
@@ -230,6 +231,8 @@
"Yes": "Ja",
"You must keep at least one version.": "Minstens 1 versie moet bewaard blijven.",
"days": "dagen",
"directories": "directories",
"files": "files",
"full documentation": "volledige documentatie",
"items": "objecten",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wil de map \"{{folder}}\" delen.",

View File

@@ -135,6 +135,7 @@
"Quick guide to supported patterns": "Kjapp innføring i godkjente mønster",
"RAM Utilization": "Minnebruk",
"Random": "Tilfeldig",
"Reduced by ignore patterns": "Reduced by ignore patterns",
"Release Notes": "Utgivingsnotat",
"Remote Devices": "Eksterne Einingar",
"Remove": "Fjern",
@@ -230,6 +231,8 @@
"Yes": "Ja",
"You must keep at least one version.": "Du må behalda minst ein versjon.",
"days": "dagar",
"directories": "directories",
"files": "files",
"full documentation": "all dokumentasjon",
"items": "element",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} ønskjer å dela mappa \"{{folder}}\".",

View File

@@ -135,6 +135,7 @@
"Quick guide to supported patterns": "Krótki przewodnik po obsługiwanych wzorcach",
"RAM Utilization": "Użycie pamięci RAM",
"Random": "Losowo",
"Reduced by ignore patterns": "Reduced by ignore patterns",
"Release Notes": "Informacje o wydaniu",
"Remote Devices": "Urządzenia zdalne",
"Remove": "Usuń",
@@ -230,6 +231,8 @@
"Yes": "Tak",
"You must keep at least one version.": "Musisz posiadać przynajmniej jedną wersję",
"days": "dni",
"directories": "directories",
"files": "files",
"full documentation": "pełna dokumentacja",
"items": "pozycji",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} chce udostępnić folder \"{{folder}}\"",

View File

@@ -135,6 +135,7 @@
"Quick guide to supported patterns": "Guia rápido dos padrões suportados",
"RAM Utilization": "Uso de RAM",
"Random": "Aleatória",
"Reduced by ignore patterns": "Reduced by ignore patterns",
"Release Notes": "Notas de lançamento",
"Remote Devices": "Dispositivos remotos",
"Remove": "Remover",
@@ -230,6 +231,8 @@
"Yes": "Sim",
"You must keep at least one version.": "Você deve manter pelo menos uma versão.",
"days": "dias",
"directories": "directories",
"files": "files",
"full documentation": "documentação completa",
"items": "itens",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} quer compartilhar a pasta \"{{folder}}\".",

View File

@@ -31,7 +31,7 @@
"Command": "Comando",
"Comment, when used at the start of a line": "Comentário, quando usado no início de uma linha",
"Compression": "Compressão",
"Configured": "Configured",
"Configured": "Configurado",
"Connection Error": "Erro de ligação",
"Connection Type": "Tipo de ligação",
"Copied from elsewhere": "Copiado doutro sítio",
@@ -45,7 +45,7 @@
"Device Name": "Nome do dispositivo",
"Devices": "Dispositivos",
"Disconnected": "Desconectado",
"Discovered": "Discovered",
"Discovered": "Descoberto",
"Discovery": "Pesquisa",
"Documentation": "Documentação",
"Download Rate": "Velocidade de recepção",
@@ -72,7 +72,7 @@
"Folder Path": "Caminho da pasta",
"Folder Type": "Tipo de pasta",
"Folders": "Pastas",
"GUI": "GUI",
"GUI": "Interface gráfica",
"GUI Authentication Password": "Senha da autenticação na interface gráfica",
"GUI Authentication User": "Utilizador da autenticação na interface gráfica",
"GUI Listen Addresses": "Endereço de escuta da interface gráfica",
@@ -135,6 +135,7 @@
"Quick guide to supported patterns": "Guia rápido dos padrões suportados",
"RAM Utilization": "Utilização da RAM",
"Random": "Aleatória",
"Reduced by ignore patterns": "Reduzido pelos padrões de exclusão",
"Release Notes": "Notas de lançamento",
"Remote Devices": "Dispositivos remotos",
"Remove": "Remover",
@@ -230,6 +231,8 @@
"Yes": "Sim",
"You must keep at least one version.": "Tem que manter pelo menos uma versão.",
"days": "dias",
"directories": "pastas",
"files": "ficheiros",
"full documentation": "documentação completa",
"items": "itens",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} quer partilhar a pasta \"{{folder}}\".",

View File

@@ -135,6 +135,7 @@
"Quick guide to supported patterns": "Краткое руководство по поддерживаемым шаблонам",
"RAM Utilization": "Использование памяти",
"Random": "Случайно",
"Reduced by ignore patterns": "Reduced by ignore patterns",
"Release Notes": "Примечания к выпуску",
"Remote Devices": "Удалённые устройства",
"Remove": "Удалить",
@@ -230,6 +231,8 @@
"Yes": "Да",
"You must keep at least one version.": "Вы должны хранить как минимум одну версию.",
"days": "дней",
"directories": "directories",
"files": "files",
"full documentation": "полная документация",
"items": "элементы",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} хочет поделиться папкой «{{folder}}».",

View File

@@ -1,5 +1,5 @@
{
"A device with that ID is already added.": "En enhet med det ID:t är redan tillagt.",
"A device with that ID is already added.": "En enhet med det ID är redan tillagt.",
"A negative number of days doesn't make sense.": "Ett negativt antal dagar är inte rimligt.",
"A new major version may not be compatible with previous versions.": "En ny huvudversion kan eventuellt vara inkompatibel med tidigare versioner.",
"API Key": "API-nyckel",
@@ -24,7 +24,7 @@
"Automatic upgrades": "Automatiska uppgraderingar",
"Be careful!": "Var aktsam!",
"Bugs": "Buggar",
"CPU Utilization": "CPU-användning",
"CPU Utilization": "CPU användning",
"Changelog": "Ändringslogg",
"Clean out after": "Rensa efteråt",
"Close": "Stäng",
@@ -40,8 +40,8 @@
"Danger!": "Fara!",
"Deleted": "Borttaget",
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Enhet \"{{name}}\" ({{device}} på {{address}}) vill ansluta. Lägg till ny enhet?",
"Device ID": "Enhets ID",
"Device Identification": "Enhets identifikation",
"Device ID": "Enhet-ID",
"Device Identification": "Enhet identifikation",
"Device Name": "Enhets namn",
"Devices": "Enheter",
"Disconnected": "Frånkopplad",
@@ -62,9 +62,9 @@
"Failed Items": "Misslyckade objekt",
"File Pull Order": "Filhämtningsprioritering",
"File Versioning": "Filversionshantering",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Filrättigheter ignoreras vid sökning efter förändringar. Används på FAT-filsystem.",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Filer flyttas till katalogen .stversions om de ersätts eller raderas av Syncthing.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Filer flyttas till datummärkta versioner i en .stversions-mapp när de ersatts eller raderats av Syncthing.",
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "Filrättigheter ignoreras under sökning efter förändringar. Används på FAT-filsystem.",
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Filer flyttas till .stversions katalogen när de ersätts eller raderas av Syncthing.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "Filer flyttas till datummärkta versioner i en .stversions mapp när de ersätts eller raderas av 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.": "Filer skyddas från ändringar gjorda på andra enheter, men ändringar som görs på den här noden skickas till de andra klustermedlemmarna.",
"Folder": "Katalog",
"Folder ID": "Katalog-ID",
@@ -133,8 +133,9 @@
"Preview": "Förhandsgranska",
"Preview Usage Report": "Förhandsgranska statistik",
"Quick guide to supported patterns": "Snabb handledning till mönster som stöds",
"RAM Utilization": "RAM-användning",
"RAM Utilization": "RAM användning",
"Random": "Slumpmässig",
"Reduced by ignore patterns": "Minskas med ignorera mönster",
"Release Notes": "Versionsanteckningar",
"Remote Devices": "Fjärrenheter",
"Remove": "Ta bort",
@@ -161,8 +162,8 @@
"Shared With": "Delad med",
"Show ID": "Visa ID",
"Show QR": "Visa QR",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Visas i stället för enhets ID i samlingsstatusen. Skickas till andra enheter som namn på denna enhet.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Visas i stället för enhets ID i samlingsstatusen. Sätts till namnet på den andra enheten vid första anslutning om det lämnas tomt.",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Visas istället för enhet-ID i klusterstatusen. Skickas till andra enheter som ett alternativt förvalt namn.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Visas i stället för enhet-ID i klusterstatusen. Kommer att uppdateras till namnet enheten annonserar om det lämnas tomt.",
"Shutdown": "Stäng av",
"Shutdown Complete": "Avstängning klar",
"Simple File Versioning": "Enkel filversionshantering",
@@ -185,13 +186,13 @@
"The Syncthing admin interface is configured to allow remote access without a password.": "Syncthing administratör gränssnittet är konfigurerat för att tillåta fjärrtillträde utan ett lösenord.",
"The aggregated statistics are publicly available at the URL below.": "Den aggregerade statistiken är offentligt tillgängliga på webbadressen nedan.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Konfigurationen har sparats men inte aktiverats. Syncthing måste startas om för att aktivera den nya konfigurationen.",
"The device ID cannot be blank.": "Enhets ID:t kan inte vara tomt.",
"The device ID to enter here can be found in the \"Actions > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "Enhets ID:t som behövs här kan du hitta i \"Åtgärder > Visa ID\"-dialogrutan på den andra enheten. Mellanrum och bindestreck är valfria (ignoreras).",
"The device ID cannot be blank.": "Enhet-ID kan inte vara tomt.",
"The device ID to enter here can be found in the \"Actions > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "Enhet-ID som behövs här kan du hitta i \"Åtgärder > Visa ID\"-dialogrutan på den andra enheten. Mellanrum och bindestreck är valfria (ignoreras).",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Den krypterade användarstatistiken skickas dagligen. Den används för att spåra vanliga plattformar, katalogstorlekar och versioner. Om datan som rapporteras ändras så kommer du att bli tillfrågad igen.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "Det inmatade enhets ID:t verkar inte korrekt. Det ska vara en 52 eller 56 teckens sträng bestående av siffror och bokstäver, eventuellt med mellanrum och bindestreck.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "Det inmatade enhet-ID verkar inte vara korrekt. Det ska vara en 52 eller 56 teckensträng bestående av siffror och bokstäver, eventuellt med mellanrum och bindestreck.",
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "Den första kommandoparametern är sökvägen till mappen och den andra parametern är den relativa sökvägen i mappen.",
"The folder ID cannot be blank.": "Katalogens ID får inte vara tomt.",
"The folder ID must be unique.": "Katalogens ID måste vara unikt.",
"The folder ID cannot be blank.": "Katalog-ID får inte vara tomt.",
"The folder ID must be unique.": "Katalog-ID måste vara unikt.",
"The folder path cannot be blank.": "Katalogsökvägen kan inte vara tom.",
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "De följande intervallen används: varje 30 sekunder under den första timmen; varje timme under den första dagen; varje dag för de första 30 dagarna; varje vecka tills den maximala åldersgränsen uppnås.",
"The following items could not be synchronized.": "Följande objekt kunde inte synkroniseras.",
@@ -226,10 +227,12 @@
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Versioner tas bort automatiskt när de är äldre än den maximala åldersgränsen eller överstiger frekvensen i sitt interval.",
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Varning, denna sökväg är en underkatalog till en befintlig katalog \"{{otherFolder}}\".",
"When adding a new device, keep in mind that this device must be added on the other side too.": "När du lägger till en ny enhet, kom ihåg att den här enheten måste läggas till på den andra enheten också.",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "När du lägger till ny katalog, tänk på att katalogens ID knyter ihop katalogen mellan olika noder. De måste vara exakt desamma mellan noder och stora eller små bokstäver har betydelse.",
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "När du lägger till ny katalog, tänk på att katalog-ID knyter ihop kataloger mellan olika enheter. De skiftlägeskänsliga och måste matcha precis mellan alla enheter.",
"Yes": "Ja",
"You must keep at least one version.": "Du måste behålla åtminstone en version.",
"days": "dagar",
"directories": "kataloger",
"files": "filer",
"full documentation": "fullständig dokumentation",
"items": "objekt",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} vill dela katalog \"{{folder}}\".",

View File

@@ -135,6 +135,7 @@
"Quick guide to supported patterns": "Desteklenen kalıplar için hızlı rehber",
"RAM Utilization": "RAM Kullanımı",
"Random": "Rastgele",
"Reduced by ignore patterns": "Reduced by ignore patterns",
"Release Notes": "Sürüm Notları",
"Remote Devices": "Remote Devices",
"Remove": "Kaldır",
@@ -230,6 +231,8 @@
"Yes": "Evet",
"You must keep at least one version.": "En az bir sürümü tutmalısınız.",
"days": "günler",
"directories": "directories",
"files": "files",
"full documentation": "belgelendirme içeriğinin tümü",
"items": "öğel",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} \"{{folder}}\" klasörünü paylaşmak istiyor.",

View File

@@ -135,6 +135,7 @@
"Quick guide to supported patterns": "Швидкий посібник по шаблонам, що підтримуються",
"RAM Utilization": "Використання RAM",
"Random": "Випадково",
"Reduced by ignore patterns": "Reduced by ignore patterns",
"Release Notes": "Примітки до випуску",
"Remote Devices": "Віддалені пристрої",
"Remove": "Видалити",
@@ -230,6 +231,8 @@
"Yes": "Так",
"You must keep at least one version.": "Ви повинні зберігати щонайменше одну версію.",
"days": "днів",
"directories": "directories",
"files": "files",
"full documentation": "повна документація",
"items": "елементи",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} хоче поділитися директорією \"{{folder}}\".",

View File

@@ -135,6 +135,7 @@
"Quick guide to supported patterns": "H.dẫn sơ lược về các q.luật được hỗ trợ",
"RAM Utilization": "Mức s.dụng RAM",
"Random": "Ngẫu nhiên",
"Reduced by ignore patterns": "Reduced by ignore patterns",
"Release Notes": "Lịch sử phát hành",
"Remote Devices": "Các thiết bị từ xa",
"Remove": "Xoá",
@@ -230,6 +231,8 @@
"Yes": "Phải",
"You must keep at least one version.": "Bạn phải giữ ít nhất một phiên bản.",
"days": "ngày",
"directories": "directories",
"files": "files",
"full documentation": "tài liệu đầy đủ",
"items": "nội dung",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} muốn chia sẻ thư mục \"{{folder}}\".",

View File

@@ -78,7 +78,7 @@
"GUI Listen Addresses": "图形管理界面监听地址",
"Generate": "生成",
"Global Discovery": "在互联网上寻找设备\n",
"Global Discovery Servers": "全发现服务器",
"Global Discovery Servers": "全发现服务器",
"Global State": "全局状态",
"Help": "帮助",
"Home page": "主页",
@@ -112,7 +112,7 @@
"Newest First": "新文件优先",
"No": "否",
"No File Versioning": "不启用版本控制",
"Normal": "正常",
"Normal": "普通",
"Notice": "提示",
"OK": "确定",
"Off": "关闭",
@@ -126,7 +126,7 @@
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "文件夹在本地的路径。如果不存在,则会被创建。波浪线符号(~)是如下路径的缩略符:",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "用来存储历史版本的文件夹(留空则将默认会存储在.stversions文件夹中",
"Pause": "暂停",
"Paused": "暂停",
"Paused": "暂停",
"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": "请稍候",
@@ -135,6 +135,7 @@
"Quick guide to supported patterns": "支持的通配符的简单教程:",
"RAM Utilization": "内存使用量",
"Random": "随机顺序",
"Reduced by ignore patterns": "Reduced by ignore patterns",
"Release Notes": "发布说明",
"Remote Devices": "远程设备",
"Remove": "移除",
@@ -230,6 +231,8 @@
"Yes": "是",
"You must keep at least one version.": "您必须保留至少一个版本",
"days": "天",
"directories": "目录",
"files": "文件",
"full documentation": "完整文档",
"items": "条目",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} 想将 “{{folder}}” 文件夹共享给您",

View File

@@ -135,6 +135,7 @@
"Quick guide to supported patterns": "可支援樣式的快速指南",
"RAM Utilization": "記憶體使用",
"Random": "隨機",
"Reduced by ignore patterns": "Reduced by ignore patterns",
"Release Notes": "版本資訊",
"Remote Devices": "遠端裝置",
"Remove": "移除",
@@ -230,6 +231,8 @@
"Yes": "是",
"You must keep at least one version.": "您必須保留至少一個版本。",
"days": "日",
"directories": "directories",
"files": "files",
"full documentation": "完整說明文件",
"items": "個項目",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} 想要分享資料夾 \"{{folder}}\"。",

View File

@@ -1 +1 @@
var langPrettyprint = {"bg":"Bulgarian","ca":"Catalan","ca@valencia":"Catalan (Valencian)","cs":"Czech","da":"Danish","de":"German","el":"Greek","en":"English","en-GB":"English (United Kingdom)","es":"Spanish","es-ES":"Spanish (Spain)","fi":"Finnish","fr":"French","fr-CA":"French (Canada)","fy":"Western Frisian","hu":"Hungarian","id":"Indonesian","it":"Italian","ja":"Japanese","ko-KR":"Korean (Korea)","lt":"Lithuanian","nb":"Norwegian Bokmål","nl":"Dutch","nn":"Norwegian Nynorsk","pl":"Polish","pt-BR":"Portuguese (Brazil)","pt-PT":"Portuguese (Portugal)","ru":"Russian","sv":"Swedish","tr":"Turkish","uk":"Ukrainian","vi":"Vietnamese","zh-CN":"Chinese (China)","zh-TW":"Chinese (Taiwan)"}
var langPrettyprint = {"bg":"Bulgarian","ca":"Catalan","ca@valencia":"Catalan (Valencian)","cs":"Czech","da":"Danish","de":"German","el":"Greek","en":"English","en-GB":"English (United Kingdom)","es":"Spanish","es-ES":"Spanish (Spain)","eu":"Basque","fi":"Finnish","fr":"French","fr-CA":"French (Canada)","fy":"Western Frisian","hu":"Hungarian","id":"Indonesian","it":"Italian","ja":"Japanese","ko-KR":"Korean (Korea)","lt":"Lithuanian","nb":"Norwegian Bokmål","nl":"Dutch","nn":"Norwegian Nynorsk","pl":"Polish","pt-BR":"Portuguese (Brazil)","pt-PT":"Portuguese (Portugal)","ru":"Russian","sv":"Swedish","tr":"Turkish","uk":"Ukrainian","vi":"Vietnamese","zh-CN":"Chinese (China)","zh-TW":"Chinese (Taiwan)"}

View File

@@ -1 +1 @@
var validLangs = ["bg","ca","ca@valencia","cs","da","de","el","en","en-GB","es","es-ES","fi","fr","fr-CA","fy","hu","id","it","ja","ko-KR","lt","nb","nl","nn","pl","pt-BR","pt-PT","ru","sv","tr","uk","vi","zh-CN","zh-TW"]
var validLangs = ["bg","ca","ca@valencia","cs","da","de","el","en","en-GB","es","es-ES","eu","fi","fr","fr-CA","fy","hu","id","it","ja","ko-KR","lt","nb","nl","nn","pl","pt-BR","pt-PT","ru","sv","tr","uk","vi","zh-CN","zh-TW"]

View File

@@ -236,7 +236,10 @@
</h3>
</div>
<div class="panel-body">
<p ng-repeat="err in errorList()"><small>{{err.when | date:"yyyy-MM-dd HH:mm:ss"}}:</small> {{friendlyDevices(err.message)}}</p>
<p ng-repeat="err in errorList()">
<small>{{err.when | date:"yyyy-MM-dd HH:mm:ss"}}:</small>
<span ng-bind-html="friendlyDevices(err.message) | linky: '_blank'"></span>
</p>
</div>
<div class="panel-footer">
<button type="button" class="btn btn-sm btn-default pull-right" ng-click="clearErrors()">
@@ -310,16 +313,29 @@
</tr>
<tr>
<th><span class="fa fa-fw fa-globe"></span>&nbsp;<span translate>Global State</span></th>
<td class="text-right">{{model[folder.id].globalFiles | alwaysNumber}} <span translate>items</span>, ~{{model[folder.id].globalBytes | binary}}B</td>
<td class="text-right">
<span tooltip data-original-title="{{model[folder.id].globalFiles | alwaysNumber}} {{'files' | translate}}, {{model[folder.id].globalDirectories | alwaysNumber}} {{'directories' | translate}}, ~{{model[folder.id].globalBytes | binary}}B">
<span class="fa fa-files-o"></span>&nbsp;{{model[folder.id].globalFiles | alwaysNumber}}&ensp;
<span class="fa fa-folder-o"></span>&nbsp;{{model[folder.id].globalDirectories | alwaysNumber}}&ensp;
<span class="fa fa-hdd-o"></span>&nbsp;~{{model[folder.id].globalBytes | binary}}B
</span>
</td>
</tr>
<tr>
<th><span class="fa fa-fw fa-home"></span>&nbsp;<span translate>Local State</span></th>
<td class="text-right">{{model[folder.id].localFiles | alwaysNumber}} <span translate>items</span>, ~{{model[folder.id].localBytes | binary}}B</td>
<td class="text-right">
<span tooltip data-original-title="{{model[folder.id].localFiles | alwaysNumber}} {{'files' | translate}}, {{model[folder.id].localDirectories | alwaysNumber}} {{'directories' | translate}}, ~{{model[folder.id].localBytes | binary}}B">
<span class="fa fa-files-o"></span>&nbsp;{{model[folder.id].localFiles | alwaysNumber}}&ensp;
<span class="fa fa-folder-o"></span>&nbsp;{{model[folder.id].localDirectories | alwaysNumber}}&ensp;
<span class="fa fa-hdd-o"></span>&nbsp;~{{model[folder.id].localBytes | binary}}B
<span ng-if="model[folder.id].ignorePatterns"><br/><i><small translate class="text-muted">Reduced by ignore patterns</small></i></span>
</span>
</td>
</tr>
<tr ng-if="model[folder.id].needFiles > 0">
<tr ng-if="neededItems(folder.id) > 0">
<th><span class="fa fa-fw fa-cloud-download"></span>&nbsp;<span translate>Out of Sync Items</span></th>
<td class="text-right">
<a href="" ng-click="showNeed(folder.id)">{{model[folder.id].needFiles | alwaysNumber}} <span translate>items</span>, ~{{model[folder.id].needBytes | binary}}B</a>
<a href="" ng-click="showNeed(folder.id)">{{neededItems(folder.id) | alwaysNumber}} <span translate>items</span>, ~{{model[folder.id].needBytes | binary}}B</a>
</td>
</tr>
<tr ng-if="folderStatus(folder) === 'scanning' && scanRate(folder.id) > 0">
@@ -328,16 +344,12 @@
<span tooltip data-original-title="{{scanRate(folder.id) | binary}}B/s">~ {{scanRemaining(folder.id)}}</span>
</td>
</tr>
<tr ng-if="folder.type != 'readonly' && (folderStatus(folder) === 'outofsync' || hasFailedFiles(folder.id))">
<tr ng-if="hasFailedFiles(folder.id)">
<th><span class="fa fa-fw fa-exclamation-circle"></span>&nbsp;<span translate>Failed Items</span></th>
<!-- Show the number of failed items as a link to bring up the list. -->
<td ng-if="hasFailedFiles(folder.id)" class="text-right">
<td class="text-right">
<a href="" ng-click="showFailed(folder.id)">{{failed[folder.id].length | alwaysNumber}}&nbsp;<span translate>items</span></a>
</td>
<!-- The list of failed items hasn't loaded yet; show a spinner for the time being. -->
<td ng-if="!hasFailedFiles(folder.id)" class="text-right">
<span class="fa fa-spinner fa-pulse"></span>
</td>
</tr>
<tr ng-if="folder.type != 'readwrite'">
<th><span class="fa fa-fw fa-lock"></span>&nbsp;<span translate>Folder Type</span></th>
@@ -346,12 +358,6 @@
<span ng-if="folder.type != 'readonly'">{{ folder.type.charAt(0).toUpperCase() + folder.type.slice(1) }}</span>
</td>
</tr>
<tr ng-if="model[folder.id].ignorePatterns">
<th><span class="fa fa-fw fa-eye-slash"></span>&nbsp;<span translate>Ignore Patterns</span></th>
<td class="text-right">
<span translate>Yes</span>
</td>
</tr>
<tr ng-if="folder.ignorePerms">
<th><span class="fa fa-fw fa-minus-square-o"></span>&nbsp;<span translate>Ignore Permissions</span></th>
<td class="text-right">
@@ -655,6 +661,7 @@
<!-- vendor scripts -->
<script src="vendor/jquery/jquery-2.2.2.js"></script>
<script src="vendor/angular/angular.js"></script>
<script src="vendor/angular/angular-sanitize.js"></script>
<script src="vendor/angular/angular-translate.js"></script>
<script src="vendor/angular/angular-translate-loader-static-files.js"></script>
<script src="vendor/angular/angular-dirPagination.js"></script>

View File

@@ -10,7 +10,7 @@
var syncthing = angular.module('syncthing', [
'angularUtils.directives.dirPagination',
'pascalprecht.translate',
'pascalprecht.translate', 'ngSanitize',
'syncthing.core'
]);

View File

@@ -12,7 +12,7 @@
<p translate>Copyright &copy; 2014-2016 the following Contributors:</p>
<div class="row">
<div class="col-md-12" id="contributor-list">
Jakob Borg, Audrius Butkevicius, Alexander Graf, Anderson Mesquita, Antony Male, Ben Schulz, Caleb Callaway, Daniel Harte, Lars K.W. Gohlke, Lode Hoste, Michael Ploujnikov, Philippe Schommers, Ryan Sullivan, Sergey Mishin, Stefan Tatschner, Aaron Bieber, Adam Piggott, Alessandro G., Alexandre Viau, Andrew Dunham, Andrey D, Antoine Lamielle, Arthur Axel fREW Schmidt, Bart De Vries, Ben Curthoys, Ben Sidhom, Benny Ng, Brandon Philips, Brendan Long, Brian R. Becker, Carsten Hagemann, Cathryne Linenweaver, Cedric Staniewski, Chris Howie, Chris Joel, Colin Kennedy, Daniel Bergmann, Daniel Martí, David Rimmer, Denis A., Dennis Wilson, Dominik Heidler, Elias Jarlebring, Emil Hessman, Erik Meitner, Federico Castagnini, Felix Ableitner, Felix Unterpaintner, Francois-Xavier Gsell, Frank Isemann, Gilli Sigurdsson, Jaakko Hannikainen, Jacek Szafarkiewicz, Jake Peterson, James Patterson, Jaroslav Malec, Jens Diemer, Jochen Voss, Johan Vromans, Karol Różycki, Kelong Cong, Ken'ichi Kamada, Kevin Allen, Laurent Etiemble, Lord Landon Agahnim, Majed Abdulaziz, Marc Laporte, Marc Pujol, Marcin Dziadus, Mateusz Naściszewski, Matt Burke, Max Schulze, Michael Jephcote, Michael Tilli, Nate Morrison, Pascal Jungblut, Peter Hoeg, Phill Luby, Piotr Bejda, Scott Klupfel, Stefan Kuntz, Tim Abell, Tim Howes, Tobias Nygren, Tomas Cerveny, Tully Robinson, Tyler Brazier, Veeti Paananen, Victor Buinsky, Vil Brekin, William A. Kennington III, Wulf Weich, Xavier O., Yannic A.
Jakob Borg, Audrius Butkevicius, Alexander Graf, Anderson Mesquita, Antony Male, Ben Schulz, Caleb Callaway, Daniel Harte, Lars K.W. Gohlke, Lode Hoste, Michael Ploujnikov, Philippe Schommers, Ryan Sullivan, Sergey Mishin, Stefan Tatschner, Aaron Bieber, Adam Piggott, Alessandro G., Alexandre Viau, Andrew Dunham, Andrey D, Antoine Lamielle, Arthur Axel fREW Schmidt, Bart De Vries, Ben Curthoys, Ben Sidhom, Benny Ng, Brandon Philips, Brendan Long, Brian R. Becker, Carsten Hagemann, Cathryne Linenweaver, Cedric Staniewski, Chris Howie, Chris Joel, Colin Kennedy, Daniel Bergmann, Daniel Martí, David Rimmer, Denis A., Dennis Wilson, Dominik Heidler, Elias Jarlebring, Emil Hessman, Erik Meitner, Federico Castagnini, Felix Ableitner, Felix Unterpaintner, Francois-Xavier Gsell, Frank Isemann, Gilli Sigurdsson, Jaakko Hannikainen, Jacek Szafarkiewicz, Jake Peterson, James Patterson, Jaroslav Malec, Jens Diemer, Jochen Voss, Johan Vromans, Karol Różycki, Kelong Cong, Ken'ichi Kamada, Kevin Allen, Kevin White, Jr., Laurent Etiemble, Leo Arias, Lord Landon Agahnim, Majed Abdulaziz, Marc Laporte, Marc Pujol, Marcin Dziadus, Mateusz Naściszewski, Matt Burke, Max Schulze, Michael Jephcote, Michael Tilli, Nate Morrison, Pascal Jungblut, Peter Hoeg, Phill Luby, Piotr Bejda, Roman Zaynetdinov, Scott Klupfel, Simon Frei, Stefan Kuntz, Tim Abell, Tim Howes, Tobias Nygren, Tomas Cerveny, Tully Robinson, Tyler Brazier, Unrud, Veeti Paananen, Victor Buinsky, Vil Brekin, William A. Kennington III, Wulf Weich, Xavier O., Yannic A.
</div>
</div>
<hr/>

View File

@@ -690,6 +690,15 @@ angular.module('syncthing.core')
return 'info';
};
$scope.neededItems = function (folderID) {
if (!$scope.model[folderID]) {
return 0
}
return $scope.model[folderID].needFiles + $scope.model[folderID].needDirectories +
$scope.model[folderID].needSymlinks + $scope.model[folderID].needDeletes;
};
$scope.syncPercentage = function (folder) {
if (typeof $scope.model[folder] === 'undefined') {
return 100;
@@ -1309,6 +1318,7 @@ angular.module('syncthing.core')
rescanIntervalS: 60,
minDiskFreePct: 1,
maxConflicts: 10,
fsync: true,
order: "random",
fileVersioningSelector: "none",
trashcanClean: 0,
@@ -1336,6 +1346,7 @@ angular.module('syncthing.core')
rescanIntervalS: 60,
minDiskFreePct: 1,
maxConflicts: 10,
fsync: true,
order: "random",
fileVersioningSelector: "none",
trashcanClean: 0,

View File

@@ -84,7 +84,7 @@
<div class="col-md-6">
<div class="form-group">
<label translate>Folder Type</label>
&nbsp;<a href="http://docs.syncthing.net/users/foldermaster.html" target="_blank"><span class="fa fa-book"></span>&nbsp;<span translate>Help</span></a>
&nbsp;<a href="https://docs.syncthing.net/users/foldermaster.html" target="_blank"><span class="fa fa-book"></span>&nbsp;<span translate>Help</span></a>
<select class="form-control" ng-model="currentFolder.type">
<option value="readwrite" translate>Normal</option>
<option value="readonly" translate>Master</option>
@@ -115,7 +115,7 @@
</select>
</div>
<div class="form-group">
<label translate>File Versioning</label>&emsp;<a href="http://docs.syncthing.net/users/versioning.html" target="_blank"><span class="fa fa-book"></span>&nbsp;<span translate>Help</span></a>
<label translate>File Versioning</label>&emsp;<a href="https://docs.syncthing.net/users/versioning.html" target="_blank"><span class="fa fa-book"></span>&nbsp;<span translate>Help</span></a>
<select class="form-control" ng-model="currentFolder.fileVersioningSelector">
<option value="none" translate>No File Versioning</option>
<option value="trashcan" translate>Trash Can File Versioning</option>

View File

@@ -5,7 +5,7 @@
<hr/>
<p class="small"><span translate>Quick guide to supported patterns</span> (<a href="http://docs.syncthing.net/users/ignoring.html" target="_blank" translate>full documentation</a>):</p>
<p class="small"><span translate>Quick guide to supported patterns</span> (<a href="https://docs.syncthing.net/users/ignoring.html" target="_blank" translate>full documentation</a>):</p>
<dl class="dl-horizontal dl-narrow small">
<dt><code>!</code></dt> <dd><span translate>Inversion of the given condition (i.e. do not exclude)</span></dd>
<dt><code>*</code></dt> <dd><span translate>Single level wildcard (matches within a directory only)</span></dd>

View File

@@ -10,7 +10,8 @@
</div>
<div class="form-group">
<label translate for="ListenAddressesStr">Sync Protocol Listen Addresses</label>
<label translate for="ListenAddressesStr">Sync Protocol Listen Addresses</label>&emsp;<a href="https://docs.syncthing.net/users/config.html#listen-addresses" target="_blank"><span class="fa fa-fw fa-book"></span>&nbsp;<span translate>Help</span></a>
<input id="ListenAddressesStr" class="form-control" type="text" ng-model="tmpOptions._listenAddressesStr">
</div>
@@ -153,5 +154,5 @@
<span class="fa fa-times"></span>&nbsp;<span translate>Close</span>
</button>
</div>
</modal>

View File

@@ -0,0 +1,647 @@
/**
* @license AngularJS v1.2.27
* (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, angular, undefined) {'use strict';
var $sanitizeMinErr = angular.$$minErr('$sanitize');
/**
* @ngdoc module
* @name ngSanitize
* @description
*
* # ngSanitize
*
* The `ngSanitize` module provides functionality to sanitize HTML.
*
*
* <div doc-module-components="ngSanitize"></div>
*
* See {@link ngSanitize.$sanitize `$sanitize`} for usage.
*/
/*
* HTML Parser By Misko Hevery (misko@hevery.com)
* based on: HTML Parser By John Resig (ejohn.org)
* Original code by Erik Arvidsson, Mozilla Public License
* http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
*
* // Use like so:
* htmlParser(htmlString, {
* start: function(tag, attrs, unary) {},
* end: function(tag) {},
* chars: function(text) {},
* comment: function(text) {}
* });
*
*/
/**
* @ngdoc service
* @name $sanitize
* @kind function
*
* @description
* The input is sanitized by parsing the html into tokens. All safe tokens (from a whitelist) are
* then serialized back to properly escaped html string. This means that no unsafe input can make
* it into the returned string, however, since our parser is more strict than a typical browser
* parser, it's possible that some obscure input, which would be recognized as valid HTML by a
* browser, won't make it through the sanitizer.
* The whitelist is configured using the functions `aHrefSanitizationWhitelist` and
* `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider `$compileProvider`}.
*
* @param {string} html Html input.
* @returns {string} Sanitized html.
*
* @example
<example module="sanitizeExample" deps="angular-sanitize.js">
<file name="index.html">
<script>
angular.module('sanitizeExample', ['ngSanitize'])
.controller('ExampleController', ['$scope', '$sce', function($scope, $sce) {
$scope.snippet =
'<p style="color:blue">an html\n' +
'<em onmouseover="this.textContent=\'PWN3D!\'">click here</em>\n' +
'snippet</p>';
$scope.deliberatelyTrustDangerousSnippet = function() {
return $sce.trustAsHtml($scope.snippet);
};
}]);
</script>
<div ng-controller="ExampleController">
Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
<table>
<tr>
<td>Directive</td>
<td>How</td>
<td>Source</td>
<td>Rendered</td>
</tr>
<tr id="bind-html-with-sanitize">
<td>ng-bind-html</td>
<td>Automatically uses $sanitize</td>
<td><pre>&lt;div ng-bind-html="snippet"&gt;<br/>&lt;/div&gt;</pre></td>
<td><div ng-bind-html="snippet"></div></td>
</tr>
<tr id="bind-html-with-trust">
<td>ng-bind-html</td>
<td>Bypass $sanitize by explicitly trusting the dangerous value</td>
<td>
<pre>&lt;div ng-bind-html="deliberatelyTrustDangerousSnippet()"&gt;
&lt;/div&gt;</pre>
</td>
<td><div ng-bind-html="deliberatelyTrustDangerousSnippet()"></div></td>
</tr>
<tr id="bind-default">
<td>ng-bind</td>
<td>Automatically escapes</td>
<td><pre>&lt;div ng-bind="snippet"&gt;<br/>&lt;/div&gt;</pre></td>
<td><div ng-bind="snippet"></div></td>
</tr>
</table>
</div>
</file>
<file name="protractor.js" type="protractor">
it('should sanitize the html snippet by default', function() {
expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()).
toBe('<p>an html\n<em>click here</em>\nsnippet</p>');
});
it('should inline raw snippet if bound to a trusted value', function() {
expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()).
toBe("<p style=\"color:blue\">an html\n" +
"<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
"snippet</p>");
});
it('should escape snippet without any filter', function() {
expect(element(by.css('#bind-default div')).getInnerHtml()).
toBe("&lt;p style=\"color:blue\"&gt;an html\n" +
"&lt;em onmouseover=\"this.textContent='PWN3D!'\"&gt;click here&lt;/em&gt;\n" +
"snippet&lt;/p&gt;");
});
it('should update', function() {
element(by.model('snippet')).clear();
element(by.model('snippet')).sendKeys('new <b onclick="alert(1)">text</b>');
expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()).
toBe('new <b>text</b>');
expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()).toBe(
'new <b onclick="alert(1)">text</b>');
expect(element(by.css('#bind-default div')).getInnerHtml()).toBe(
"new &lt;b onclick=\"alert(1)\"&gt;text&lt;/b&gt;");
});
</file>
</example>
*/
function $SanitizeProvider() {
this.$get = ['$$sanitizeUri', function($$sanitizeUri) {
return function(html) {
var buf = [];
htmlParser(html, htmlSanitizeWriter(buf, function(uri, isImage) {
return !/^unsafe/.test($$sanitizeUri(uri, isImage));
}));
return buf.join('');
};
}];
}
function sanitizeText(chars) {
var buf = [];
var writer = htmlSanitizeWriter(buf, angular.noop);
writer.chars(chars);
return buf.join('');
}
// Regular Expressions for parsing tags and attributes
var START_TAG_REGEXP =
/^<((?:[a-zA-Z])[\w:-]*)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*(>?)/,
END_TAG_REGEXP = /^<\/\s*([\w:-]+)[^>]*>/,
ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,
BEGIN_TAG_REGEXP = /^</,
BEGING_END_TAGE_REGEXP = /^<\//,
COMMENT_REGEXP = /<!--(.*?)-->/g,
DOCTYPE_REGEXP = /<!DOCTYPE([^>]*?)>/i,
CDATA_REGEXP = /<!\[CDATA\[(.*?)]]>/g,
SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
// Match everything outside of normal chars and " (quote character)
NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g;
// Good source of info about elements and attributes
// http://dev.w3.org/html5/spec/Overview.html#semantics
// http://simon.html5.org/html-elements
// Safe Void Elements - HTML5
// http://dev.w3.org/html5/spec/Overview.html#void-elements
var voidElements = makeMap("area,br,col,hr,img,wbr");
// Elements that you can, intentionally, leave open (and which close themselves)
// http://dev.w3.org/html5/spec/Overview.html#optional-tags
var optionalEndTagBlockElements = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),
optionalEndTagInlineElements = makeMap("rp,rt"),
optionalEndTagElements = angular.extend({},
optionalEndTagInlineElements,
optionalEndTagBlockElements);
// Safe Block Elements - HTML5
var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap("address,article," +
"aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5," +
"h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul"));
// Inline Elements - HTML5
var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b," +
"bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s," +
"samp,small,span,strike,strong,sub,sup,time,tt,u,var"));
// Special Elements (can contain anything)
var specialElements = makeMap("script,style");
var validElements = angular.extend({},
voidElements,
blockElements,
inlineElements,
optionalEndTagElements);
//Attributes that have href and hence need to be sanitized
var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap");
var validAttrs = angular.extend({}, uriAttrs, makeMap(
'abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,'+
'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,'+
'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,'+
'scope,scrolling,shape,size,span,start,summary,target,title,type,'+
'valign,value,vspace,width'));
function makeMap(str) {
var obj = {}, items = str.split(','), i;
for (i = 0; i < items.length; i++) obj[items[i]] = true;
return obj;
}
/**
* @example
* htmlParser(htmlString, {
* start: function(tag, attrs, unary) {},
* end: function(tag) {},
* chars: function(text) {},
* comment: function(text) {}
* });
*
* @param {string} html string
* @param {object} handler
*/
function htmlParser( html, handler ) {
if (typeof html !== 'string') {
if (html === null || typeof html === 'undefined') {
html = '';
} else {
html = '' + html;
}
}
var index, chars, match, stack = [], last = html, text;
stack.last = function() { return stack[ stack.length - 1 ]; };
while ( html ) {
text = '';
chars = true;
// Make sure we're not in a script or style element
if ( !stack.last() || !specialElements[ stack.last() ] ) {
// Comment
if ( html.indexOf("<!--") === 0 ) {
// comments containing -- are not allowed unless they terminate the comment
index = html.indexOf("--", 4);
if ( index >= 0 && html.lastIndexOf("-->", index) === index) {
if (handler.comment) handler.comment( html.substring( 4, index ) );
html = html.substring( index + 3 );
chars = false;
}
// DOCTYPE
} else if ( DOCTYPE_REGEXP.test(html) ) {
match = html.match( DOCTYPE_REGEXP );
if ( match ) {
html = html.replace( match[0], '');
chars = false;
}
// end tag
} else if ( BEGING_END_TAGE_REGEXP.test(html) ) {
match = html.match( END_TAG_REGEXP );
if ( match ) {
html = html.substring( match[0].length );
match[0].replace( END_TAG_REGEXP, parseEndTag );
chars = false;
}
// start tag
} else if ( BEGIN_TAG_REGEXP.test(html) ) {
match = html.match( START_TAG_REGEXP );
if ( match ) {
// We only have a valid start-tag if there is a '>'.
if ( match[4] ) {
html = html.substring( match[0].length );
match[0].replace( START_TAG_REGEXP, parseStartTag );
}
chars = false;
} else {
// no ending tag found --- this piece should be encoded as an entity.
text += '<';
html = html.substring(1);
}
}
if ( chars ) {
index = html.indexOf("<");
text += index < 0 ? html : html.substring( 0, index );
html = index < 0 ? "" : html.substring( index );
if (handler.chars) handler.chars( decodeEntities(text) );
}
} else {
html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'),
function(all, text){
text = text.replace(COMMENT_REGEXP, "$1").replace(CDATA_REGEXP, "$1");
if (handler.chars) handler.chars( decodeEntities(text) );
return "";
});
parseEndTag( "", stack.last() );
}
if ( html == last ) {
throw $sanitizeMinErr('badparse', "The sanitizer was unable to parse the following block " +
"of html: {0}", html);
}
last = html;
}
// Clean up any remaining tags
parseEndTag();
function parseStartTag( tag, tagName, rest, unary ) {
tagName = angular.lowercase(tagName);
if ( blockElements[ tagName ] ) {
while ( stack.last() && inlineElements[ stack.last() ] ) {
parseEndTag( "", stack.last() );
}
}
if ( optionalEndTagElements[ tagName ] && stack.last() == tagName ) {
parseEndTag( "", tagName );
}
unary = voidElements[ tagName ] || !!unary;
if ( !unary )
stack.push( tagName );
var attrs = {};
rest.replace(ATTR_REGEXP,
function(match, name, doubleQuotedValue, singleQuotedValue, unquotedValue) {
var value = doubleQuotedValue
|| singleQuotedValue
|| unquotedValue
|| '';
attrs[name] = decodeEntities(value);
});
if (handler.start) handler.start( tagName, attrs, unary );
}
function parseEndTag( tag, tagName ) {
var pos = 0, i;
tagName = angular.lowercase(tagName);
if ( tagName )
// Find the closest opened tag of the same type
for ( pos = stack.length - 1; pos >= 0; pos-- )
if ( stack[ pos ] == tagName )
break;
if ( pos >= 0 ) {
// Close all the open elements, up the stack
for ( i = stack.length - 1; i >= pos; i-- )
if (handler.end) handler.end( stack[ i ] );
// Remove the open elements from the stack
stack.length = pos;
}
}
}
var hiddenPre=document.createElement("pre");
var spaceRe = /^(\s*)([\s\S]*?)(\s*)$/;
/**
* decodes all entities into regular string
* @param value
* @returns {string} A string with decoded entities.
*/
function decodeEntities(value) {
if (!value) { return ''; }
// Note: IE8 does not preserve spaces at the start/end of innerHTML
// so we must capture them and reattach them afterward
var parts = spaceRe.exec(value);
var spaceBefore = parts[1];
var spaceAfter = parts[3];
var content = parts[2];
if (content) {
hiddenPre.innerHTML=content.replace(/</g,"&lt;");
// innerText depends on styling as it doesn't display hidden elements.
// Therefore, it's better to use textContent not to cause unnecessary
// reflows. However, IE<9 don't support textContent so the innerText
// fallback is necessary.
content = 'textContent' in hiddenPre ?
hiddenPre.textContent : hiddenPre.innerText;
}
return spaceBefore + content + spaceAfter;
}
/**
* Escapes all potentially dangerous characters, so that the
* resulting string can be safely inserted into attribute or
* element text.
* @param value
* @returns {string} escaped text
*/
function encodeEntities(value) {
return value.
replace(/&/g, '&amp;').
replace(SURROGATE_PAIR_REGEXP, function (value) {
var hi = value.charCodeAt(0);
var low = value.charCodeAt(1);
return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';';
}).
replace(NON_ALPHANUMERIC_REGEXP, function(value){
return '&#' + value.charCodeAt(0) + ';';
}).
replace(/</g, '&lt;').
replace(/>/g, '&gt;');
}
/**
* create an HTML/XML writer which writes to buffer
* @param {Array} buf use buf.jain('') to get out sanitized html string
* @returns {object} in the form of {
* start: function(tag, attrs, unary) {},
* end: function(tag) {},
* chars: function(text) {},
* comment: function(text) {}
* }
*/
function htmlSanitizeWriter(buf, uriValidator){
var ignore = false;
var out = angular.bind(buf, buf.push);
return {
start: function(tag, attrs, unary){
tag = angular.lowercase(tag);
if (!ignore && specialElements[tag]) {
ignore = tag;
}
if (!ignore && validElements[tag] === true) {
out('<');
out(tag);
angular.forEach(attrs, function(value, key){
var lkey=angular.lowercase(key);
var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background');
if (validAttrs[lkey] === true &&
(uriAttrs[lkey] !== true || uriValidator(value, isImage))) {
out(' ');
out(key);
out('="');
out(encodeEntities(value));
out('"');
}
});
out(unary ? '/>' : '>');
}
},
end: function(tag){
tag = angular.lowercase(tag);
if (!ignore && validElements[tag] === true) {
out('</');
out(tag);
out('>');
}
if (tag == ignore) {
ignore = false;
}
},
chars: function(chars){
if (!ignore) {
out(encodeEntities(chars));
}
}
};
}
// define ngSanitize module and register $sanitize service
angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
/* global sanitizeText: false */
/**
* @ngdoc filter
* @name linky
* @kind function
*
* @description
* Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and
* plain email address links.
*
* Requires the {@link ngSanitize `ngSanitize`} module to be installed.
*
* @param {string} text Input text.
* @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in.
* @returns {string} Html-linkified text.
*
* @usage
<span ng-bind-html="linky_expression | linky"></span>
*
* @example
<example module="linkyExample" deps="angular-sanitize.js">
<file name="index.html">
<script>
angular.module('linkyExample', ['ngSanitize'])
.controller('ExampleController', ['$scope', function($scope) {
$scope.snippet =
'Pretty text with some links:\n'+
'http://angularjs.org/,\n'+
'mailto:us@somewhere.org,\n'+
'another@somewhere.org,\n'+
'and one more: ftp://127.0.0.1/.';
$scope.snippetWithTarget = 'http://angularjs.org/';
}]);
</script>
<div ng-controller="ExampleController">
Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
<table>
<tr>
<td>Filter</td>
<td>Source</td>
<td>Rendered</td>
</tr>
<tr id="linky-filter">
<td>linky filter</td>
<td>
<pre>&lt;div ng-bind-html="snippet | linky"&gt;<br>&lt;/div&gt;</pre>
</td>
<td>
<div ng-bind-html="snippet | linky"></div>
</td>
</tr>
<tr id="linky-target">
<td>linky target</td>
<td>
<pre>&lt;div ng-bind-html="snippetWithTarget | linky:'_blank'"&gt;<br>&lt;/div&gt;</pre>
</td>
<td>
<div ng-bind-html="snippetWithTarget | linky:'_blank'"></div>
</td>
</tr>
<tr id="escaped-html">
<td>no filter</td>
<td><pre>&lt;div ng-bind="snippet"&gt;<br>&lt;/div&gt;</pre></td>
<td><div ng-bind="snippet"></div></td>
</tr>
</table>
</file>
<file name="protractor.js" type="protractor">
it('should linkify the snippet with urls', function() {
expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()).
toBe('Pretty text with some links: http://angularjs.org/, us@somewhere.org, ' +
'another@somewhere.org, and one more: ftp://127.0.0.1/.');
expect(element.all(by.css('#linky-filter a')).count()).toEqual(4);
});
it('should not linkify snippet without the linky filter', function() {
expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()).
toBe('Pretty text with some links: http://angularjs.org/, mailto:us@somewhere.org, ' +
'another@somewhere.org, and one more: ftp://127.0.0.1/.');
expect(element.all(by.css('#escaped-html a')).count()).toEqual(0);
});
it('should update', function() {
element(by.model('snippet')).clear();
element(by.model('snippet')).sendKeys('new http://link.');
expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()).
toBe('new http://link.');
expect(element.all(by.css('#linky-filter a')).count()).toEqual(1);
expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText())
.toBe('new http://link.');
});
it('should work with the target property', function() {
expect(element(by.id('linky-target')).
element(by.binding("snippetWithTarget | linky:'_blank'")).getText()).
toBe('http://angularjs.org/');
expect(element(by.css('#linky-target a')).getAttribute('target')).toEqual('_blank');
});
</file>
</example>
*/
angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
var LINKY_URL_REGEXP =
/((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"]/,
MAILTO_REGEXP = /^mailto:/;
return function(text, target) {
if (!text) return text;
var match;
var raw = text;
var html = [];
var url;
var i;
while ((match = raw.match(LINKY_URL_REGEXP))) {
// We can not end in these as they are sometimes found at the end of the sentence
url = match[0];
// if we did not match ftp/http/mailto then assume mailto
if (match[2] == match[3]) url = 'mailto:' + url;
i = match.index;
addText(raw.substr(0, i));
addLink(url, match[0].replace(MAILTO_REGEXP, ''));
raw = raw.substring(i + match[0].length);
}
addText(raw);
return $sanitize(html.join(''));
function addText(text) {
if (!text) {
return;
}
html.push(sanitizeText(text));
}
function addLink(url, text) {
html.push('<a ');
if (angular.isDefined(target)) {
html.push('target="');
html.push(target);
html.push('" ');
}
html.push('href="');
html.push(url);
html.push('">');
addText(text);
html.push('</a>');
}
};
}]);
})(window, window.angular);

View File

@@ -49,3 +49,9 @@ go run build.go -goarch armel deb
go run build.go -goarch armhf deb
mv *.deb "$WORKSPACE"
go run build.go -goarch amd64 snap
go run build.go -goarch armhf snap
go run build.go -goarch arm64 snap
mv *.snap "$WORKSPACE"

View File

@@ -20,7 +20,7 @@ function init {
export GOPATH=$(pwd)
export WORKSPACE="${WORKSPACE:-$GOPATH}"
go version
rm -f *.tar.gz *.zip *.deb
rm -f *.tar.gz *.zip *.deb *.snap
cd src/github.com/syncthing/syncthing
version=$(go run build.go version)

View File

@@ -43,7 +43,7 @@ func (validationError) String() string {
func TestReplaceCommit(t *testing.T) {
w := Wrap("/dev/null", Configuration{Version: 0})
if w.Raw().Version != 0 {
if w.RawCopy().Version != 0 {
t.Fatal("Config incorrect")
}
@@ -57,7 +57,7 @@ func TestReplaceCommit(t *testing.T) {
if w.RequiresRestart() {
t.Fatal("Should not require restart")
}
if w.Raw().Version != CurrentVersion {
if w.RawCopy().Version != CurrentVersion {
t.Fatal("Config should have changed")
}
@@ -76,7 +76,7 @@ func TestReplaceCommit(t *testing.T) {
if !w.RequiresRestart() {
t.Fatal("Should require restart")
}
if w.Raw().Version != CurrentVersion {
if w.RawCopy().Version != CurrentVersion {
t.Fatal("Config should have changed")
}
@@ -92,7 +92,7 @@ func TestReplaceCommit(t *testing.T) {
if !w.RequiresRestart() {
t.Fatal("Should still require restart")
}
if w.Raw().Version != CurrentVersion {
if w.RawCopy().Version != CurrentVersion {
t.Fatal("Config should not have changed")
}
}

View File

@@ -26,7 +26,7 @@ import (
const (
OldestHandledVersion = 10
CurrentVersion = 16
CurrentVersion = 17
MaxRescanIntervalS = 365 * 24 * 60 * 60
)
@@ -254,6 +254,9 @@ func (cfg *Configuration) clean() error {
if cfg.Version == 15 {
convertV15V16(cfg)
}
if cfg.Version == 16 {
convertV16V17(cfg)
}
// Build a list of available devices
existingDevices := make(map[protocol.DeviceID]bool)
@@ -327,6 +330,14 @@ func convertV15V16(cfg *Configuration) {
cfg.Version = 16
}
func convertV16V17(cfg *Configuration) {
for i := range cfg.Folders {
cfg.Folders[i].Fsync = true
}
cfg.Version = 17
}
func convertV13V14(cfg *Configuration) {
// Not using the ignore cache is the new default. Disable it on existing
// configurations.

View File

@@ -104,6 +104,7 @@ func TestDeviceConfig(t *testing.T) {
AutoNormalize: true,
MinDiskFreePct: 1,
MaxConflicts: -1,
Fsync: true,
Versioning: VersioningConfiguration{
Params: map[string]string{},
},
@@ -456,7 +457,7 @@ func TestNewSaveLoad(t *testing.T) {
t.Error(err)
}
if diff, equal := messagediff.PrettyDiff(cfg.Raw(), cfg2.Raw()); !equal {
if diff, equal := messagediff.PrettyDiff(cfg.RawCopy(), cfg2.RawCopy()); !equal {
t.Errorf("Configs are not equal. Diff:\n%s", diff)
}
@@ -482,7 +483,7 @@ func TestCopy(t *testing.T) {
if err != nil {
t.Fatal(err)
}
cfg := wrapper.Raw()
cfg := wrapper.RawCopy()
bsOrig, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
@@ -548,7 +549,7 @@ func TestPullOrder(t *testing.T) {
// Serialize and deserialize again to verify it survives the transformation
buf := new(bytes.Buffer)
cfg := wrapper.Raw()
cfg := wrapper.RawCopy()
cfg.WriteXML(buf)
t.Logf("%s", buf.Bytes())
@@ -611,7 +612,7 @@ func TestDuplicateDevices(t *testing.T) {
t.Fatal(err)
}
if l := len(wrapper.Raw().Devices); l != 3 {
if l := len(wrapper.RawCopy().Devices); l != 3 {
t.Errorf("Incorrect number of devices, %d != 3", l)
}
@@ -755,7 +756,7 @@ func TestSharesRemovedOnDeviceRemoval(t *testing.T) {
t.Errorf("Failed: %s", err)
}
raw := wrapper.Raw()
raw := wrapper.RawCopy()
raw.Devices = raw.Devices[:len(raw.Devices)-1]
if len(raw.Folders[0].Devices) <= len(raw.Devices) {
@@ -767,7 +768,7 @@ func TestSharesRemovedOnDeviceRemoval(t *testing.T) {
t.Errorf("Failed: %s", err)
}
raw = wrapper.Raw()
raw = wrapper.RawCopy()
if len(raw.Folders[0].Devices) > len(raw.Devices) {
t.Error("Unexpected extra device")
}

View File

@@ -9,12 +9,14 @@ package config
import "github.com/syncthing/syncthing/lib/protocol"
type DeviceConfiguration struct {
DeviceID protocol.DeviceID `xml:"id,attr" json:"deviceID"`
Name string `xml:"name,attr,omitempty" json:"name"`
Addresses []string `xml:"address,omitempty" json:"addresses"`
Compression protocol.Compression `xml:"compression,attr" json:"compression"`
CertName string `xml:"certName,attr,omitempty" json:"certName"`
Introducer bool `xml:"introducer,attr" json:"introducer"`
DeviceID protocol.DeviceID `xml:"id,attr" json:"deviceID"`
Name string `xml:"name,attr,omitempty" json:"name"`
Addresses []string `xml:"address,omitempty" json:"addresses"`
Compression protocol.Compression `xml:"compression,attr" json:"compression"`
CertName string `xml:"certName,attr,omitempty" json:"certName"`
Introducer bool `xml:"introducer,attr" json:"introducer"`
SkipIntroductionRemovals bool `xml:"skipIntroductionRemovals,attr" json:"skipIntroductionRemovals"`
IntroducedBy protocol.DeviceID `xml:"introducedBy,attr" json:"introducedBy"`
}
func NewDeviceConfiguration(id protocol.DeviceID, name string) DeviceConfiguration {

View File

@@ -7,6 +7,7 @@
package config
import (
"fmt"
"os"
"path/filepath"
"runtime"
@@ -38,6 +39,7 @@ type FolderConfiguration struct {
MaxConflicts int `xml:"maxConflicts" json:"maxConflicts"`
DisableSparseFiles bool `xml:"disableSparseFiles" json:"disableSparseFiles"`
DisableTempIndexes bool `xml:"disableTempIndexes" json:"disableTempIndexes"`
Fsync bool `xml:"fsync" json:"fsync"`
cachedPath string
@@ -45,7 +47,8 @@ type FolderConfiguration struct {
}
type FolderDeviceConfiguration struct {
DeviceID protocol.DeviceID `xml:"id,attr" json:"deviceID"`
DeviceID protocol.DeviceID `xml:"id,attr" json:"deviceID"`
IntroducedBy protocol.DeviceID `xml:"introducedBy,attr" json:"introducedBy"`
}
func NewFolderConfiguration(id, path string) FolderConfiguration {
@@ -84,6 +87,9 @@ func (f *FolderConfiguration) CreateMarker() error {
return err
}
fd.Close()
if err := osutil.SyncDir(filepath.Dir(marker)); err != nil {
l.Infof("fsync %q failed: %v", filepath.Dir(marker), err)
}
osutil.HideFile(marker)
}
@@ -98,6 +104,10 @@ func (f *FolderConfiguration) HasMarker() bool {
return true
}
func (f FolderConfiguration) Description() string {
return fmt.Sprintf("%q (%s)", f.Label, f.ID)
}
func (f *FolderConfiguration) DeviceIDs() []protocol.DeviceID {
deviceIDs := make([]protocol.DeviceID, len(f.Devices))
for i, n := range f.Devices {

15
lib/config/testdata/v17.xml vendored Normal file
View File

@@ -0,0 +1,15 @@
<configuration version="17">
<folder id="test" path="testdata" type="readonly" ignorePerms="false" rescanIntervalS="600" autoNormalize="true">
<device id="AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR"></device>
<device id="P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2"></device>
<minDiskFreePct>1</minDiskFreePct>
<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>

View File

@@ -120,9 +120,11 @@ func (w *Wrapper) Unsubscribe(c Committer) {
w.mut.Unlock()
}
// Raw returns the currently wrapped Configuration object.
func (w *Wrapper) Raw() Configuration {
return w.cfg
// RawCopy returns a copy of the currently wrapped Configuration object.
func (w *Wrapper) RawCopy() Configuration {
w.mut.Lock()
defer w.mut.Unlock()
return w.cfg.Copy()
}
// Replace swaps the current configuration object for the given one.
@@ -159,7 +161,7 @@ func (w *Wrapper) replaceLocked(to Configuration) error {
func (w *Wrapper) notifyListeners(from, to Configuration) {
for _, sub := range w.subs {
go w.notifyListener(sub, from, to)
go w.notifyListener(sub, from.Copy(), to.Copy())
}
}
@@ -207,6 +209,27 @@ func (w *Wrapper) SetDevice(dev DeviceConfiguration) error {
return w.replaceLocked(newCfg)
}
// RemoveDevice removes the device from the configuration
func (w *Wrapper) RemoveDevice(id protocol.DeviceID) error {
w.mut.Lock()
defer w.mut.Unlock()
newCfg := w.cfg.Copy()
removed := false
for i := range newCfg.Devices {
if newCfg.Devices[i].DeviceID == id {
newCfg.Devices = append(newCfg.Devices[:i], newCfg.Devices[i+1:]...)
removed = true
break
}
}
if !removed {
return nil
}
return w.replaceLocked(newCfg)
}
// Folders returns a map of folders. Folder structures should not be changed,
// other than for the purpose of updating via SetFolder().
func (w *Wrapper) Folders() map[string]FolderConfiguration {

View File

@@ -44,6 +44,7 @@ func (t *relayListener) Serve() {
t.mut.Unlock()
clnt, err := client.NewClient(t.uri, t.tlsCfg.Certificates, nil, 10*time.Second)
invitations := clnt.Invitations()
if err != nil {
t.mut.Lock()
t.err = err
@@ -62,7 +63,7 @@ func (t *relayListener) Serve() {
for {
select {
case inv, ok := <-t.client.Invitations():
case inv, ok := <-invitations:
if !ok {
return
}

View File

@@ -59,9 +59,10 @@ type Service struct {
natService *nat.Service
natServiceToken *suture.ServiceToken
listenersMut sync.RWMutex
listeners map[string]genericListener
listenerTokens map[string]suture.ServiceToken
listenersMut sync.RWMutex
listeners map[string]genericListener
listenerTokens map[string]suture.ServiceToken
listenerSupervisor *suture.Supervisor
curConMut sync.Mutex
currentConnection map[protocol.DeviceID]Connection
@@ -71,7 +72,11 @@ func NewService(cfg *config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *
bepProtocolName string, tlsDefaultCommonName string, lans []*net.IPNet) *Service {
service := &Service{
Supervisor: suture.NewSimple("connections.Service"),
Supervisor: suture.New("connections.Service", suture.Spec{
Log: func(line string) {
l.Infoln(line)
},
}),
cfg: cfg,
myID: myID,
model: mdl,
@@ -87,6 +92,18 @@ func NewService(cfg *config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *
listeners: make(map[string]genericListener),
listenerTokens: make(map[string]suture.ServiceToken),
// A listener can fail twice, rapidly. Any more than that and it
// will be put on suspension for ten minutes. Restarts and changes
// due to config are done by removing and adding services, so are
// not subject to these limitations.
listenerSupervisor: suture.New("c.S.listenerSupervisor", suture.Spec{
Log: func(line string) {
l.Infoln(line)
},
FailureThreshold: 2,
FailureBackoff: 600 * time.Second,
}),
curConMut: sync.NewMutex(),
currentConnection: make(map[protocol.DeviceID]Connection),
}
@@ -111,8 +128,9 @@ func NewService(cfg *config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *
service.Add(serviceFunc(service.connect))
service.Add(serviceFunc(service.handle))
service.Add(service.listenerSupervisor)
raw := cfg.Raw()
raw := cfg.RawCopy()
// Actually starts the listeners and NAT service
service.CommitConfiguration(raw, raw)
@@ -276,7 +294,7 @@ func (s *Service) connect() {
var sleep time.Duration
for {
cfg := s.cfg.Raw()
cfg := s.cfg.RawCopy()
bestDialerPrio := 1<<31 - 1 // worse prio won't build on 32 bit
for _, df := range dialers {
@@ -417,7 +435,7 @@ func (s *Service) createListener(factory listenerFactory, uri *url.URL) bool {
listener := factory.New(uri, s.cfg, s.tlsCfg, s.conns, s.natService)
listener.OnAddressesChanged(s.logListenAddressesChangedEvent)
s.listeners[uri.String()] = listener
s.listenerTokens[uri.String()] = s.Add(listener)
s.listenerTokens[uri.String()] = s.listenerSupervisor.Add(listener)
return true
}
@@ -481,7 +499,7 @@ func (s *Service) CommitConfiguration(from, to config.Configuration) bool {
for addr, listener := range s.listeners {
if _, ok := seen[addr]; !ok || !listener.Factory().Enabled(to) {
l.Debugln("Stopping listener", addr)
s.Remove(s.listenerTokens[addr])
s.listenerSupervisor.Remove(s.listenerTokens[addr])
delete(s.listenerTokens, addr)
delete(s.listeners, addr)
}

View File

@@ -51,11 +51,17 @@ type FileIntf interface {
// continue iteration, false to stop.
type Iterator func(f FileIntf) bool
type Counts struct {
Files int
Directories int
Symlinks int
Deleted int
Bytes int64
}
type sizeTracker struct {
files int
deleted int
bytes int64
mut stdsync.Mutex
Counts
mut stdsync.Mutex
}
func (s *sizeTracker) addFile(f FileIntf) {
@@ -64,12 +70,17 @@ func (s *sizeTracker) addFile(f FileIntf) {
}
s.mut.Lock()
if f.IsDeleted() {
s.deleted++
} else {
s.files++
switch {
case f.IsDeleted():
s.Deleted++
case f.IsDirectory():
s.Directories++
case f.IsSymlink():
s.Symlinks++
default:
s.Files++
}
s.bytes += f.FileSize()
s.Bytes += f.FileSize()
s.mut.Unlock()
}
@@ -79,22 +90,27 @@ func (s *sizeTracker) removeFile(f FileIntf) {
}
s.mut.Lock()
if f.IsDeleted() {
s.deleted--
} else {
s.files--
switch {
case f.IsDeleted():
s.Deleted--
case f.IsDirectory():
s.Directories--
case f.IsSymlink():
s.Symlinks--
default:
s.Files--
}
s.bytes -= f.FileSize()
if s.deleted < 0 || s.files < 0 {
s.Bytes -= f.FileSize()
if s.Deleted < 0 || s.Files < 0 || s.Directories < 0 || s.Symlinks < 0 {
panic("bug: removed more than added")
}
s.mut.Unlock()
}
func (s *sizeTracker) Size() (files, deleted int, bytes int64) {
func (s *sizeTracker) Size() Counts {
s.mut.Lock()
defer s.mut.Unlock()
return s.files, s.deleted, s.bytes
return s.Counts
}
func NewFileSet(folder string, db *Instance) *FileSet {
@@ -259,11 +275,11 @@ func (s *FileSet) Sequence(device protocol.DeviceID) int64 {
return s.remoteSequence[device]
}
func (s *FileSet) LocalSize() (files, deleted int, bytes int64) {
func (s *FileSet) LocalSize() Counts {
return s.localSize.Size()
}
func (s *FileSet) GlobalSize() (files, deleted int, bytes int64) {
func (s *FileSet) GlobalSize() Counts {
return s.globalSize.Size()
}

View File

@@ -168,27 +168,33 @@ func TestGlobalSet(t *testing.T) {
t.Errorf("Global incorrect;\n A: %v !=\n E: %v", g, expectedGlobal)
}
globalFiles, globalDeleted, globalBytes := 0, 0, int64(0)
globalFiles, globalDirectories, globalDeleted, globalBytes := 0, 0, 0, int64(0)
for _, f := range g {
if f.IsInvalid() {
continue
}
if f.IsDeleted() {
switch {
case f.IsDeleted():
globalDeleted++
} else {
case f.IsDirectory():
globalDirectories++
default:
globalFiles++
}
globalBytes += f.FileSize()
}
gsFiles, gsDeleted, gsBytes := m.GlobalSize()
if gsFiles != globalFiles {
t.Errorf("Incorrect GlobalSize files; %d != %d", gsFiles, globalFiles)
gs := m.GlobalSize()
if gs.Files != globalFiles {
t.Errorf("Incorrect GlobalSize files; %d != %d", gs.Files, globalFiles)
}
if gsDeleted != globalDeleted {
t.Errorf("Incorrect GlobalSize deleted; %d != %d", gsDeleted, globalDeleted)
if gs.Directories != globalDirectories {
t.Errorf("Incorrect GlobalSize directories; %d != %d", gs.Directories, globalDirectories)
}
if gsBytes != globalBytes {
t.Errorf("Incorrect GlobalSize bytes; %d != %d", gsBytes, globalBytes)
if gs.Deleted != globalDeleted {
t.Errorf("Incorrect GlobalSize deleted; %d != %d", gs.Deleted, globalDeleted)
}
if gs.Bytes != globalBytes {
t.Errorf("Incorrect GlobalSize bytes; %d != %d", gs.Bytes, globalBytes)
}
h := fileList(haveList(m, protocol.LocalDeviceID))
@@ -198,27 +204,33 @@ func TestGlobalSet(t *testing.T) {
t.Errorf("Have incorrect;\n A: %v !=\n E: %v", h, localTot)
}
haveFiles, haveDeleted, haveBytes := 0, 0, int64(0)
haveFiles, haveDirectories, haveDeleted, haveBytes := 0, 0, 0, int64(0)
for _, f := range h {
if f.IsInvalid() {
continue
}
if f.IsDeleted() {
switch {
case f.IsDeleted():
haveDeleted++
} else {
case f.IsDirectory():
haveDirectories++
default:
haveFiles++
}
haveBytes += f.FileSize()
}
lsFiles, lsDeleted, lsBytes := m.LocalSize()
if lsFiles != haveFiles {
t.Errorf("Incorrect LocalSize files; %d != %d", lsFiles, haveFiles)
ls := m.LocalSize()
if ls.Files != haveFiles {
t.Errorf("Incorrect LocalSize files; %d != %d", ls.Files, haveFiles)
}
if lsDeleted != haveDeleted {
t.Errorf("Incorrect LocalSize deleted; %d != %d", lsDeleted, haveDeleted)
if ls.Directories != haveDirectories {
t.Errorf("Incorrect LocalSize directories; %d != %d", ls.Directories, haveDirectories)
}
if lsBytes != haveBytes {
t.Errorf("Incorrect LocalSize bytes; %d != %d", lsBytes, haveBytes)
if ls.Deleted != haveDeleted {
t.Errorf("Incorrect LocalSize deleted; %d != %d", ls.Deleted, haveDeleted)
}
if ls.Bytes != haveBytes {
t.Errorf("Incorrect LocalSize bytes; %d != %d", ls.Bytes, haveBytes)
}
h = fileList(haveList(m, remoteDevice0))

View File

@@ -5,7 +5,7 @@
// You can obtain one at http://mozilla.org/MPL/2.0/.
//go:generate go run ../../script/protofmt.go structs.proto
//go:generate protoc --proto_path=../../../../../:../../../../gogo/protobuf/protobuf:. --gogofast_out=. structs.proto
//go:generate protoc -I ../../../../../ -I ../../../../gogo/protobuf/protobuf -I . --gogofast_out=. structs.proto
package db

View File

@@ -912,32 +912,32 @@ var (
)
var fileDescriptorStructs = []byte{
// 419 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x51, 0xcd, 0xaa, 0xd3, 0x40,
0x18, 0x4d, 0xda, 0xdc, 0x36, 0xfd, 0x62, 0xaf, 0x3a, 0xc8, 0x25, 0x14, 0x4c, 0x2f, 0x05, 0x41,
0x04, 0x53, 0xbd, 0xe2, 0xc6, 0x65, 0x17, 0x05, 0x41, 0x44, 0x46, 0xa9, 0xcb, 0xd2, 0x64, 0xa6,
0xe9, 0x40, 0x32, 0x13, 0x33, 0x93, 0x42, 0x7d, 0x12, 0x97, 0x7d, 0x9c, 0x2e, 0x7d, 0x02, 0xd1,
0xfa, 0x12, 0x2e, 0x9d, 0x4e, 0x7e, 0xcc, 0xd2, 0x45, 0xe0, 0x3b, 0x73, 0xce, 0xf9, 0xce, 0x99,
0x0c, 0x8c, 0xa5, 0x2a, 0xca, 0x58, 0xc9, 0x30, 0x2f, 0x84, 0x12, 0xa8, 0x47, 0xa2, 0xc9, 0xf3,
0x84, 0xa9, 0x5d, 0x19, 0x85, 0xb1, 0xc8, 0xe6, 0x89, 0x48, 0xc4, 0xdc, 0x50, 0x51, 0xb9, 0x35,
0xc8, 0x00, 0x33, 0x55, 0x96, 0xc9, 0xeb, 0x8e, 0x5c, 0x1e, 0x78, 0xac, 0x76, 0x8c, 0x27, 0x9d,
0x29, 0x65, 0x51, 0xb5, 0x21, 0x16, 0xe9, 0x3c, 0xa2, 0x79, 0x65, 0x9b, 0x7d, 0x06, 0x6f, 0xc9,
0x52, 0xba, 0xa2, 0x85, 0x64, 0x82, 0xa3, 0x17, 0x30, 0xdc, 0x57, 0xa3, 0x6f, 0xdf, 0xda, 0x4f,
0xbd, 0xbb, 0x07, 0x61, 0x63, 0x0a, 0x57, 0x34, 0x56, 0xa2, 0x58, 0x38, 0xa7, 0x1f, 0x53, 0x0b,
0x37, 0x32, 0x74, 0x03, 0x03, 0x42, 0xf7, 0x2c, 0xa6, 0x7e, 0x4f, 0x1b, 0xee, 0xe1, 0x1a, 0xcd,
0x96, 0xe0, 0xd5, 0x4b, 0xdf, 0x31, 0xa9, 0xd0, 0x4b, 0x70, 0x6b, 0x87, 0xd4, 0x9b, 0xfb, 0x7a,
0xf3, 0xfd, 0x90, 0x44, 0x61, 0x27, 0xbb, 0x5e, 0xdc, 0xca, 0xde, 0x38, 0xdf, 0x8e, 0x53, 0x6b,
0xf6, 0xa7, 0x07, 0x0f, 0x2f, 0xaa, 0xb7, 0x7c, 0x2b, 0x3e, 0x15, 0x25, 0x8f, 0x37, 0x8a, 0x12,
0x84, 0xc0, 0xe1, 0x9b, 0x8c, 0x9a, 0x92, 0x23, 0x6c, 0x66, 0xf4, 0x0c, 0x1c, 0x75, 0xc8, 0xab,
0x1e, 0xd7, 0x77, 0x37, 0xff, 0x8a, 0xb7, 0x76, 0xcd, 0x62, 0xa3, 0xb9, 0xf8, 0x25, 0xfb, 0x4a,
0xfd, 0xbe, 0xd6, 0xf6, 0xb1, 0x99, 0xd1, 0x2d, 0x78, 0x39, 0x2d, 0x32, 0x26, 0xab, 0x96, 0x8e,
0xa6, 0xc6, 0xb8, 0x7b, 0x84, 0x1e, 0x03, 0x64, 0x82, 0xb0, 0x2d, 0xa3, 0x64, 0x2d, 0xfd, 0x2b,
0xe3, 0x1d, 0x35, 0x27, 0x1f, 0x91, 0x0f, 0x43, 0x42, 0x53, 0xaa, 0xfb, 0xf9, 0x03, 0xcd, 0xb9,
0xb8, 0x81, 0x17, 0x86, 0xf1, 0xfd, 0x26, 0x65, 0xc4, 0x1f, 0x56, 0x4c, 0x0d, 0xd1, 0x13, 0xb8,
0xe6, 0x62, 0xdd, 0xcd, 0x75, 0x8d, 0x60, 0xcc, 0xc5, 0x87, 0x4e, 0x72, 0xe7, 0x5d, 0x46, 0xff,
0xf7, 0x2e, 0x13, 0x70, 0x25, 0xfd, 0x52, 0x52, 0xae, 0x5f, 0x06, 0x4c, 0xd3, 0x16, 0xa3, 0x29,
0x78, 0xed, 0x3d, 0x74, 0xa2, 0xa7, 0xe9, 0x2b, 0xdc, 0x5e, 0xed, 0x7d, 0xfd, 0xeb, 0x17, 0x8f,
0x4e, 0xbf, 0x02, 0xeb, 0x74, 0x0e, 0xec, 0xef, 0xfa, 0xfb, 0x79, 0x0e, 0xac, 0xe3, 0xef, 0xc0,
0x8e, 0x06, 0x26, 0xf8, 0xd5, 0xdf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x2a, 0xae, 0x24, 0x77, 0xb3,
0x02, 0x00, 0x00,
// 425 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x51, 0x4f, 0x8b, 0xd3, 0x40,
0x1c, 0xed, 0xb4, 0xd9, 0xfe, 0xf9, 0xc5, 0xae, 0x3a, 0xc8, 0x32, 0x14, 0x4c, 0x43, 0x41, 0x08,
0x82, 0xa9, 0x56, 0xbc, 0x78, 0xdc, 0xc3, 0x82, 0x20, 0x22, 0x51, 0xd6, 0xe3, 0xd2, 0xcc, 0xfc,
0xda, 0x1d, 0x48, 0x67, 0x62, 0x66, 0x52, 0xa8, 0x9f, 0xc4, 0xe3, 0x7e, 0x9c, 0x1e, 0xfd, 0x04,
0xa2, 0xf5, 0x4b, 0x78, 0x94, 0x4e, 0xd2, 0x98, 0xe3, 0xde, 0xde, 0x9b, 0xdf, 0x7b, 0xbf, 0xf7,
0x66, 0x06, 0xc6, 0xc6, 0x16, 0x25, 0xb7, 0x26, 0xce, 0x0b, 0x6d, 0x35, 0xed, 0x8a, 0x74, 0xf2,
0x62, 0x2d, 0xed, 0x6d, 0x99, 0xc6, 0x5c, 0x6f, 0xe6, 0x6b, 0xbd, 0xd6, 0x73, 0x37, 0x4a, 0xcb,
0x95, 0x63, 0x8e, 0x38, 0x54, 0x59, 0x26, 0x6f, 0x5a, 0x72, 0xb3, 0x53, 0xdc, 0xde, 0x4a, 0xb5,
0x6e, 0xa1, 0x4c, 0xa6, 0xd5, 0x06, 0xae, 0xb3, 0x79, 0x8a, 0x79, 0x65, 0x9b, 0x7d, 0x01, 0xff,
0x4a, 0x66, 0x78, 0x8d, 0x85, 0x91, 0x5a, 0xd1, 0x97, 0x30, 0xd8, 0x56, 0x90, 0x91, 0x90, 0x44,
0xfe, 0xe2, 0x51, 0x7c, 0x32, 0xc5, 0xd7, 0xc8, 0xad, 0x2e, 0x2e, 0xbd, 0xfd, 0xcf, 0x69, 0x27,
0x39, 0xc9, 0xe8, 0x05, 0xf4, 0x05, 0x6e, 0x25, 0x47, 0xd6, 0x0d, 0x49, 0xf4, 0x20, 0xa9, 0xd9,
0xec, 0x0a, 0xfc, 0x7a, 0xe9, 0x7b, 0x69, 0x2c, 0x7d, 0x05, 0xc3, 0xda, 0x61, 0x18, 0x09, 0x7b,
0x91, 0xbf, 0x78, 0x18, 0x8b, 0x34, 0x6e, 0x65, 0xd7, 0x8b, 0x1b, 0xd9, 0x5b, 0xef, 0xfb, 0xdd,
0xb4, 0x33, 0xfb, 0xdb, 0x85, 0xc7, 0x47, 0xd5, 0x3b, 0xb5, 0xd2, 0x9f, 0x8b, 0x52, 0xf1, 0xa5,
0x45, 0x41, 0x29, 0x78, 0x6a, 0xb9, 0x41, 0x57, 0x72, 0x94, 0x38, 0x4c, 0x9f, 0x83, 0x67, 0x77,
0x79, 0xd5, 0xe3, 0x7c, 0x71, 0xf1, 0xbf, 0x78, 0x63, 0xdf, 0xe5, 0x98, 0x38, 0xcd, 0xd1, 0x6f,
0xe4, 0x37, 0x64, 0xbd, 0x90, 0x44, 0xbd, 0xc4, 0x61, 0x1a, 0x82, 0x9f, 0x63, 0xb1, 0x91, 0xa6,
0x6a, 0xe9, 0x85, 0x24, 0x1a, 0x27, 0xed, 0x23, 0xfa, 0x14, 0x60, 0xa3, 0x85, 0x5c, 0x49, 0x14,
0x37, 0x86, 0x9d, 0x39, 0xef, 0xe8, 0x74, 0xf2, 0x89, 0x32, 0x18, 0x08, 0xcc, 0xd0, 0xa2, 0x60,
0xfd, 0x90, 0x44, 0xc3, 0xe4, 0x44, 0x8f, 0x13, 0xa9, 0xb6, 0xcb, 0x4c, 0x0a, 0x36, 0xa8, 0x26,
0x35, 0xa5, 0xcf, 0xe0, 0x5c, 0xe9, 0x9b, 0x76, 0xee, 0xd0, 0x09, 0xc6, 0x4a, 0x7f, 0x6c, 0x25,
0xb7, 0xfe, 0x65, 0x74, 0xbf, 0x7f, 0x99, 0xc0, 0xd0, 0xe0, 0xd7, 0x12, 0x15, 0x47, 0x06, 0xae,
0x69, 0xc3, 0xe9, 0x14, 0xfc, 0xe6, 0x1e, 0xca, 0x30, 0x3f, 0x24, 0xd1, 0x59, 0xd2, 0x5c, 0xed,
0x43, 0xfd, 0xf4, 0x97, 0x4f, 0xf6, 0xbf, 0x83, 0xce, 0xfe, 0x10, 0x90, 0x1f, 0x87, 0x80, 0xfc,
0x3a, 0x04, 0x9d, 0xbb, 0x3f, 0x01, 0x49, 0xfb, 0x2e, 0xf8, 0xf5, 0xbf, 0x00, 0x00, 0x00, 0xff,
0xff, 0x2a, 0xae, 0x24, 0x77, 0xb3, 0x02, 0x00, 0x00,
}

View File

@@ -5,12 +5,11 @@
// You can obtain one at http://mozilla.org/MPL/2.0/.
//go:generate go run ../../script/protofmt.go local.proto
//go:generate protoc --proto_path=../../../../../:../../../../gogo/protobuf/protobuf:. --gogofast_out=. local.proto
//go:generate protoc -I ../../../../../ -I ../../../../gogo/protobuf/protobuf -I . --gogofast_out=. local.proto
package discover
import (
"bytes"
"encoding/binary"
"encoding/hex"
"io"
@@ -115,7 +114,7 @@ func (c *localClient) Error() error {
func (c *localClient) announcementPkt() Announce {
return Announce{
ID: c.myID[:],
ID: c.myID,
Addresses: c.addrList.AllAddresses(),
InstanceID: rand.Int63(),
}
@@ -173,10 +172,10 @@ func (c *localClient) recvAnnouncements(b beacon.Interface) {
continue
}
l.Debugf("discover: Received local announcement from %s for %s", addr, protocol.DeviceIDFromBytes(pkt.ID))
l.Debugf("discover: Received local announcement from %s for %s", addr, pkt.ID)
var newDevice bool
if !bytes.Equal(pkt.ID, c.myID[:]) {
if pkt.ID != c.myID {
newDevice = c.registerDevice(addr, pkt)
}
@@ -192,20 +191,17 @@ func (c *localClient) recvAnnouncements(b beacon.Interface) {
}
func (c *localClient) registerDevice(src net.Addr, device Announce) bool {
var id protocol.DeviceID
copy(id[:], device.ID)
// Remember whether we already had a valid cache entry for this device.
// If the instance ID has changed the remote device has restarted since
// we last heard from it, so we should treat it as a new device.
ce, existsAlready := c.Get(id)
ce, existsAlready := c.Get(device.ID)
isNewDevice := !existsAlready || time.Since(ce.when) > CacheLifeTime || ce.instanceID != device.InstanceID
// Any empty or unspecified addresses should be set to the source address
// of the announcement. We also skip any addresses we can't parse.
l.Debugln("discover: Registering addresses for", id)
l.Debugln("discover: Registering addresses for", device.ID)
var validAddresses []string
for _, addr := range device.Addresses {
u, err := url.Parse(addr)
@@ -248,7 +244,7 @@ func (c *localClient) registerDevice(src net.Addr, device Announce) bool {
}
}
c.Set(id, CacheEntry{
c.Set(device.ID, CacheEntry{
Addresses: validAddresses,
when: time.Now(),
found: true,
@@ -257,7 +253,7 @@ func (c *localClient) registerDevice(src net.Addr, device Announce) bool {
if isNewDevice {
events.Default.Log(events.DeviceDiscovered, map[string]interface{}{
"device": id.String(),
"device": device.ID.String(),
"addrs": validAddresses,
})
}

View File

@@ -18,6 +18,8 @@ import fmt "fmt"
import math "math"
import _ "github.com/gogo/protobuf/gogoproto"
import github_com_syncthing_syncthing_lib_protocol "github.com/syncthing/syncthing/lib/protocol"
import io "io"
// Reference imports to suppress errors if they are not otherwise used.
@@ -30,9 +32,9 @@ var _ = math.Inf
const _ = proto.GoGoProtoPackageIsVersion1
type Announce struct {
ID []byte `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
Addresses []string `protobuf:"bytes,2,rep,name=addresses" json:"addresses,omitempty"`
InstanceID int64 `protobuf:"varint,3,opt,name=instance_id,json=instanceId,proto3" json:"instance_id,omitempty"`
ID github_com_syncthing_syncthing_lib_protocol.DeviceID `protobuf:"bytes,1,opt,name=id,proto3,customtype=github.com/syncthing/syncthing/lib/protocol.DeviceID" json:"id"`
Addresses []string `protobuf:"bytes,2,rep,name=addresses" json:"addresses,omitempty"`
InstanceID int64 `protobuf:"varint,3,opt,name=instance_id,json=instanceId,proto3" json:"instance_id,omitempty"`
}
func (m *Announce) Reset() { *m = Announce{} }
@@ -58,12 +60,14 @@ func (m *Announce) MarshalTo(data []byte) (int, error) {
_ = i
var l int
_ = l
if len(m.ID) > 0 {
data[i] = 0xa
i++
i = encodeVarintLocal(data, i, uint64(len(m.ID)))
i += copy(data[i:], m.ID)
data[i] = 0xa
i++
i = encodeVarintLocal(data, i, uint64(m.ID.ProtoSize()))
n1, err := m.ID.MarshalTo(data[i:])
if err != nil {
return 0, err
}
i += n1
if len(m.Addresses) > 0 {
for _, s := range m.Addresses {
data[i] = 0x12
@@ -117,10 +121,8 @@ func encodeVarintLocal(data []byte, offset int, v uint64) int {
func (m *Announce) ProtoSize() (n int) {
var l int
_ = l
l = len(m.ID)
if l > 0 {
n += 1 + l + sovLocal(uint64(l))
}
l = m.ID.ProtoSize()
n += 1 + l + sovLocal(uint64(l))
if len(m.Addresses) > 0 {
for _, s := range m.Addresses {
l = len(s)
@@ -201,9 +203,8 @@ func (m *Announce) Unmarshal(data []byte) error {
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.ID = append(m.ID[:0], data[iNdEx:postIndex]...)
if m.ID == nil {
m.ID = []byte{}
if err := m.ID.Unmarshal(data[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
case 2:
@@ -381,18 +382,21 @@ var (
)
var fileDescriptorLocal = []byte{
// 194 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0xce, 0xc9, 0x4f, 0x4e,
0xcc, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x48, 0xc9, 0x2c, 0x4e, 0xce, 0x2f, 0x4b,
0x2d, 0x92, 0xd2, 0x4d, 0xcf, 0x2c, 0xc9, 0x28, 0x4d, 0xd2, 0x4b, 0xce, 0xcf, 0xd5, 0x4f, 0xcf,
0x4f, 0xcf, 0xd7, 0x07, 0x2b, 0x48, 0x2a, 0x4d, 0x03, 0xf3, 0xc0, 0x1c, 0x30, 0x0b, 0xa2, 0x51,
0xa9, 0x90, 0x8b, 0xc3, 0x31, 0x2f, 0x2f, 0xbf, 0x34, 0x2f, 0x39, 0x55, 0x48, 0x8c, 0x8b, 0x29,
0x33, 0x45, 0x82, 0x51, 0x81, 0x51, 0x83, 0xc7, 0x89, 0xed, 0xd1, 0x3d, 0x79, 0x26, 0x4f, 0x97,
0x20, 0xa0, 0x88, 0x90, 0x0c, 0x17, 0x67, 0x62, 0x4a, 0x4a, 0x51, 0x6a, 0x71, 0x71, 0x6a, 0xb1,
0x04, 0x93, 0x02, 0xb3, 0x06, 0x67, 0x10, 0x42, 0x40, 0x48, 0x9f, 0x8b, 0x3b, 0x33, 0xaf, 0xb8,
0x24, 0x11, 0x68, 0x42, 0x3c, 0x50, 0x3b, 0x33, 0x50, 0x3b, 0xb3, 0x13, 0x1f, 0x50, 0x3b, 0x97,
0x27, 0x54, 0x18, 0x68, 0x0c, 0x17, 0x4c, 0x89, 0x67, 0x8a, 0x93, 0xc8, 0x89, 0x87, 0x72, 0x0c,
0x27, 0x1e, 0xc9, 0x31, 0x5e, 0x00, 0xe2, 0x07, 0x8f, 0xe4, 0x18, 0x16, 0x3c, 0x96, 0x63, 0x4c,
0x62, 0x03, 0xbb, 0xc7, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x91, 0x3f, 0x96, 0x25, 0xd7, 0x00,
0x00, 0x00,
// 241 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x4c, 0x8e, 0x4f, 0x4e, 0x84, 0x30,
0x14, 0xc6, 0x29, 0x24, 0x66, 0xa6, 0x63, 0x5c, 0x10, 0x17, 0xc4, 0x98, 0x42, 0x5c, 0xb1, 0x11,
0x16, 0x7a, 0x01, 0x09, 0x9b, 0x6e, 0xb9, 0x80, 0x81, 0xb6, 0x32, 0x2f, 0xc1, 0x3e, 0x43, 0x61,
0x12, 0x6f, 0xe3, 0x05, 0xbc, 0x07, 0x4b, 0xd7, 0x2e, 0x1a, 0xad, 0x17, 0x31, 0xe9, 0x68, 0x86,
0xdd, 0xf7, 0xfd, 0xf2, 0x7b, 0x7f, 0xe8, 0x6e, 0x40, 0xd1, 0x0e, 0xc5, 0xcb, 0x88, 0x13, 0xc6,
0x1b, 0x09, 0x46, 0xe0, 0x41, 0x8d, 0x57, 0xb7, 0x3d, 0x4c, 0xfb, 0xb9, 0x2b, 0x04, 0x3e, 0x97,
0x3d, 0xf6, 0x58, 0x7a, 0xa1, 0x9b, 0x9f, 0x7c, 0xf3, 0xc5, 0xa7, 0xe3, 0xe0, 0xcd, 0x3b, 0xa1,
0x9b, 0x07, 0xad, 0x71, 0xd6, 0x42, 0xc5, 0x0d, 0x0d, 0x41, 0x26, 0x24, 0x23, 0xf9, 0x79, 0x55,
0x2d, 0x36, 0x0d, 0x3e, 0x6d, 0x7a, 0xbf, 0xda, 0x67, 0x5e, 0xb5, 0x98, 0xf6, 0xa0, 0xfb, 0x55,
0x1a, 0xa0, 0x3b, 0x9e, 0x10, 0x38, 0x14, 0xb5, 0x3a, 0x80, 0x50, 0xbc, 0x76, 0x36, 0x0d, 0x79,
0xdd, 0x84, 0x20, 0xe3, 0x6b, 0xba, 0x6d, 0xa5, 0x1c, 0x95, 0x31, 0xca, 0x24, 0x61, 0x16, 0xe5,
0xdb, 0xe6, 0x04, 0xe2, 0x92, 0xee, 0x40, 0x9b, 0xa9, 0xd5, 0x42, 0x3d, 0x82, 0x4c, 0xa2, 0x8c,
0xe4, 0x51, 0x75, 0xe1, 0x6c, 0x4a, 0xf9, 0x1f, 0xe6, 0x75, 0x43, 0xff, 0x15, 0x2e, 0xab, 0xcb,
0xe5, 0x9b, 0x05, 0x8b, 0x63, 0xe4, 0xc3, 0x31, 0xf2, 0xe5, 0x58, 0xf0, 0xf6, 0xc3, 0x48, 0x77,
0xe6, 0x3f, 0xb8, 0xfb, 0x0d, 0x00, 0x00, 0xff, 0xff, 0xa4, 0x46, 0x4f, 0x13, 0x14, 0x01, 0x00,
0x00,
}

View File

@@ -9,7 +9,7 @@ option (gogoproto.sizer_all) = false;
option (gogoproto.protosizer_all) = true;
message Announce {
bytes id = 1 [(gogoproto.customname) = "ID"];
bytes id = 1 [(gogoproto.customname) = "ID", (gogoproto.customtype) = "github.com/syncthing/syncthing/lib/protocol.DeviceID", (gogoproto.nullable) = false];
repeated string addresses = 2;
int64 instance_id = 3 [(gogoproto.customname) = "InstanceID"];
}

View File

@@ -40,7 +40,7 @@ func TestLocalInstanceIDShouldTriggerNew(t *testing.T) {
src := &net.UDPAddr{IP: []byte{10, 20, 30, 40}, Port: 50}
new := lc.registerDevice(src, Announce{
ID: []byte{10, 20, 30, 40, 50, 60, 70, 80, 90},
ID: protocol.DeviceID{10, 20, 30, 40, 50, 60, 70, 80, 90},
Addresses: []string{"tcp://0.0.0.0:22000"},
InstanceID: 1234567890,
})
@@ -50,7 +50,7 @@ func TestLocalInstanceIDShouldTriggerNew(t *testing.T) {
}
new = lc.registerDevice(src, Announce{
ID: []byte{10, 20, 30, 40, 50, 60, 70, 80, 90},
ID: protocol.DeviceID{10, 20, 30, 40, 50, 60, 70, 80, 90},
Addresses: []string{"tcp://0.0.0.0:22000"},
InstanceID: 1234567890,
})
@@ -60,7 +60,7 @@ func TestLocalInstanceIDShouldTriggerNew(t *testing.T) {
}
new = lc.registerDevice(src, Announce{
ID: []byte{42, 10, 20, 30, 40, 50, 60, 70, 80, 90},
ID: protocol.DeviceID{42, 10, 20, 30, 40, 50, 60, 70, 80, 90},
Addresses: []string{"tcp://0.0.0.0:22000"},
InstanceID: 1234567890,
})
@@ -70,7 +70,7 @@ func TestLocalInstanceIDShouldTriggerNew(t *testing.T) {
}
new = lc.registerDevice(src, Announce{
ID: []byte{10, 20, 30, 40, 50, 60, 70, 80, 90},
ID: protocol.DeviceID{10, 20, 30, 40, 50, 60, 70, 80, 90},
Addresses: []string{"tcp://0.0.0.0:22000"},
InstanceID: 91234567890,
})

View File

@@ -279,14 +279,14 @@ func parseIgnoreFile(fd io.Reader, currentFile string, seen map[string]bool) ([]
var err error
if strings.HasPrefix(line, "/") {
// Pattern is rooted in the current dir only
pattern.match, err = glob.Compile(line[1:])
pattern.match, err = glob.Compile(line[1:], '/')
if err != nil {
return fmt.Errorf("invalid pattern %q in ignore file (%v)", line, err)
}
patterns = append(patterns, pattern)
} else if strings.HasPrefix(line, "**/") {
// Add the pattern as is, and without **/ so it matches in current dir
pattern.match, err = glob.Compile(line)
pattern.match, err = glob.Compile(line, '/')
if err != nil {
return fmt.Errorf("invalid pattern %q in ignore file (%v)", line, err)
}
@@ -294,7 +294,7 @@ func parseIgnoreFile(fd io.Reader, currentFile string, seen map[string]bool) ([]
line = line[3:]
pattern.pattern = line
pattern.match, err = glob.Compile(line)
pattern.match, err = glob.Compile(line, '/')
if err != nil {
return fmt.Errorf("invalid pattern %q in ignore file (%v)", line, err)
}
@@ -310,7 +310,7 @@ func parseIgnoreFile(fd io.Reader, currentFile string, seen map[string]bool) ([]
} else {
// Path name or pattern, add it so it matches files both in
// current directory and subdirs.
pattern.match, err = glob.Compile(line)
pattern.match, err = glob.Compile(line, '/')
if err != nil {
return fmt.Errorf("invalid pattern %q in ignore file (%v)", line, err)
}
@@ -318,7 +318,7 @@ func parseIgnoreFile(fd io.Reader, currentFile string, seen map[string]bool) ([]
line := "**/" + line
pattern.pattern = line
pattern.match, err = glob.Compile(line)
pattern.match, err = glob.Compile(line, '/')
if err != nil {
return fmt.Errorf("invalid pattern %q in ignore file (%v)", line, err)
}

View File

@@ -737,3 +737,67 @@ func TestIssue3639(t *testing.T) {
t.Error("Should not match 'foo'")
}
}
func TestIssue3674(t *testing.T) {
stignore := `
a*b
a**c
`
testcases := []struct {
file string
matches bool
}{
{"ab", true},
{"asdfb", true},
{"ac", true},
{"asdfc", true},
{"as/db", false},
{"as/dc", true},
}
pats := New(true)
err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
if err != nil {
t.Fatal(err)
}
for _, tc := range testcases {
res := pats.Match(tc.file).IsIgnored()
if res != tc.matches {
t.Errorf("Matches(%q) == %v, expected %v", tc.file, res, tc.matches)
}
}
}
func TestGobwasGlobIssue18(t *testing.T) {
stignore := `
a?b
bb?
`
testcases := []struct {
file string
matches bool
}{
{"ab", false},
{"acb", true},
{"asdb", false},
{"bb", false},
{"bba", true},
{"bbaa", false},
}
pats := New(true)
err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
if err != nil {
t.Fatal(err)
}
for _, tc := range testcases {
res := pats.Match(tc.file).IsIgnored()
if res != tc.matches {
t.Errorf("Matches(%q) == %v, expected %v", tc.file, res, tc.matches)
}
}
}

View File

@@ -8,7 +8,6 @@ package model
import (
"bufio"
"bytes"
"crypto/tls"
"encoding/json"
"errors"
@@ -84,7 +83,7 @@ type Model struct {
folderCfgs map[string]config.FolderConfiguration // folder -> cfg
folderFiles map[string]*db.FileSet // folder -> files
folderDevices map[string][]protocol.DeviceID // folder -> deviceIDs
folderDevices folderDeviceSet // folder -> deviceIDs
deviceFolders map[protocol.DeviceID][]string // deviceID -> folders
deviceStatRefs map[protocol.DeviceID]*stats.DeviceStatisticsReference // deviceID -> statsRef
folderIgnores map[string]*ignore.Matcher // folder -> matcher object
@@ -145,7 +144,7 @@ func NewModel(cfg *config.Wrapper, id protocol.DeviceID, deviceName, clientName,
clientVersion: clientVersion,
folderCfgs: make(map[string]config.FolderConfiguration),
folderFiles: make(map[string]*db.FileSet),
folderDevices: make(map[string][]protocol.DeviceID),
folderDevices: make(folderDeviceSet),
deviceFolders: make(map[protocol.DeviceID][]string),
deviceStatRefs: make(map[protocol.DeviceID]*stats.DeviceStatisticsReference),
folderIgnores: make(map[string]*ignore.Matcher),
@@ -173,8 +172,9 @@ func NewModel(cfg *config.Wrapper, id protocol.DeviceID, deviceName, clientName,
// period.
func (m *Model) StartDeadlockDetector(timeout time.Duration) {
l.Infof("Starting deadlock detector with %v timeout", timeout)
deadlockDetect(m.fmut, timeout)
deadlockDetect(m.pmut, timeout)
detector := newDeadlockDetector(timeout)
detector.Watch("fmut", m.fmut)
detector.Watch("pmut", m.pmut)
}
// StartFolder constructs the folder service and starts it.
@@ -189,12 +189,12 @@ func (m *Model) StartFolder(folder string) {
func (m *Model) startFolderLocked(folder string) config.FolderType {
cfg, ok := m.folderCfgs[folder]
if !ok {
panic("cannot start nonexistent folder " + folder)
panic("cannot start nonexistent folder " + cfg.Description())
}
_, ok = m.folderRunners[folder]
if ok {
panic("cannot start already running folder " + folder)
panic("cannot start already running folder " + cfg.Description())
}
folderFactory, ok := folderFactories[cfg.Type]
@@ -285,7 +285,7 @@ func (m *Model) warnAboutOverwritingProtectedFiles(folder string) {
}
if len(filesAtRisk) > 0 {
l.Warnln("Some protected files may be overwritten and cause issues. See http://docs.syncthing.net/users/config.html#syncing-configuration-files for more information. The at risk files are:", strings.Join(filesAtRisk, ", "))
l.Warnln("Some protected files may be overwritten and cause issues. See https://docs.syncthing.net/users/config.html#syncing-configuration-files for more information. The at risk files are:", strings.Join(filesAtRisk, ", "))
}
}
@@ -303,9 +303,8 @@ func (m *Model) addFolderLocked(cfg config.FolderConfiguration) {
m.folderCfgs[cfg.ID] = cfg
m.folderFiles[cfg.ID] = db.NewFileSet(cfg.ID, m.db)
m.folderDevices[cfg.ID] = make([]protocol.DeviceID, len(cfg.Devices))
for i, device := range cfg.Devices {
m.folderDevices[cfg.ID][i] = device.DeviceID
for _, device := range cfg.Devices {
m.folderDevices.set(device.DeviceID, cfg.ID)
m.deviceFolders[device.DeviceID] = append(m.deviceFolders[device.DeviceID], cfg.ID)
}
@@ -335,7 +334,7 @@ func (m *Model) tearDownFolderLocked(folder string) {
}
// Close connections to affected devices
for _, dev := range m.folderDevices[folder] {
for dev := range m.folderDevices[folder] {
if conn, ok := m.conn[dev]; ok {
closeRawConn(conn)
}
@@ -476,7 +475,7 @@ func (m *Model) Completion(device protocol.DeviceID, folder string) FolderComple
return FolderCompletion{} // Folder doesn't exist, so we hardly have any of it
}
_, _, tot := rf.GlobalSize()
tot := rf.GlobalSize().Bytes
if tot == 0 {
// Folder is empty, so we have all of it
return FolderCompletion{
@@ -531,42 +530,49 @@ func (m *Model) Completion(device protocol.DeviceID, folder string) FolderComple
}
}
func sizeOfFile(f db.FileIntf) (files, deleted int, bytes int64) {
if !f.IsDeleted() {
files++
} else {
deleted++
func addSizeOfFile(s *db.Counts, f db.FileIntf) {
switch {
case f.IsDeleted():
s.Deleted++
case f.IsDirectory():
s.Directories++
case f.IsSymlink():
s.Symlinks++
default:
s.Files++
}
bytes += f.FileSize()
s.Bytes += f.FileSize()
return
}
// GlobalSize returns the number of files, deleted files and total bytes for all
// files in the global model.
func (m *Model) GlobalSize(folder string) (nfiles, deleted int, bytes int64) {
func (m *Model) GlobalSize(folder string) db.Counts {
m.fmut.RLock()
defer m.fmut.RUnlock()
if rf, ok := m.folderFiles[folder]; ok {
nfiles, deleted, bytes = rf.GlobalSize()
return rf.GlobalSize()
}
return
return db.Counts{}
}
// LocalSize returns the number of files, deleted files and total bytes for all
// files in the local folder.
func (m *Model) LocalSize(folder string) (nfiles, deleted int, bytes int64) {
func (m *Model) LocalSize(folder string) db.Counts {
m.fmut.RLock()
defer m.fmut.RUnlock()
if rf, ok := m.folderFiles[folder]; ok {
nfiles, deleted, bytes = rf.LocalSize()
return rf.LocalSize()
}
return
return db.Counts{}
}
// NeedSize returns the number and total size of currently needed files.
func (m *Model) NeedSize(folder string) (nfiles, ndeletes int, bytes int64) {
func (m *Model) NeedSize(folder string) db.Counts {
m.fmut.RLock()
defer m.fmut.RUnlock()
var result db.Counts
if rf, ok := m.folderFiles[folder]; ok {
ignores := m.folderIgnores[folder]
cfg := m.folderCfgs[folder]
@@ -575,16 +581,13 @@ func (m *Model) NeedSize(folder string) (nfiles, ndeletes int, bytes int64) {
return true
}
fs, de, by := sizeOfFile(f)
nfiles += fs
ndeletes += de
bytes += by
addSizeOfFile(&result, f)
return true
})
}
bytes -= m.progressEmitter.BytesCompleted(folder)
l.Debugf("%v NeedSize(%q): %d %d", m, folder, nfiles, bytes)
return
result.Bytes -= m.progressEmitter.BytesCompleted(folder)
l.Debugf("%v NeedSize(%q): %v", m, folder, result)
return result
}
// NeedFolderFiles returns paginated list of currently needed files in
@@ -774,7 +777,7 @@ func (m *Model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
"folderLabel": folder.Label,
"device": deviceID.String(),
})
l.Infof("Unexpected folder ID %q sent from device %q; ensure that the folder exists and that this device is selected under \"Share With\" in the folder configuration.", folder.ID, deviceID)
l.Infof("Unexpected folder %s sent from device %q; ensure that the folder exists and that this device is selected under \"Share With\" in the folder configuration.", folder.Description(), deviceID)
continue
}
if !folder.DisableTempIndexes {
@@ -787,7 +790,7 @@ func (m *Model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
var startSequence int64
for _, dev := range folder.Devices {
if bytes.Equal(dev.ID, m.id[:]) {
if dev.ID == m.id {
// This is the other side's description of what it knows
// about us. Lets check to see if we can start sending index
// updates directly or need to send the index from start...
@@ -803,22 +806,22 @@ func (m *Model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
// the IndexID, or something else weird has
// happened. We send a full index to reset the
// situation.
l.Infof("Device %v folder %q is delta index compatible, but seems out of sync with reality", deviceID, folder.ID)
l.Infof("Device %v folder %s is delta index compatible, but seems out of sync with reality", deviceID, folder.Description())
startSequence = 0
continue
}
l.Debugf("Device %v folder %q is delta index compatible (mlv=%d)", deviceID, folder.ID, dev.MaxSequence)
l.Debugf("Device %v folder %s is delta index compatible (mlv=%d)", deviceID, folder.Description(), dev.MaxSequence)
startSequence = dev.MaxSequence
} else if dev.IndexID != 0 {
// They say they've seen an index ID from us, but it's
// not the right one. Either they are confused or we
// must have reset our database since last talking to
// them. We'll start with a full index transfer.
l.Infof("Device %v folder %q has mismatching index ID for us (%v != %v)", deviceID, folder.ID, dev.IndexID, myIndexID)
l.Infof("Device %v folder %s has mismatching index ID for us (%v != %v)", deviceID, folder.Description(), dev.IndexID, myIndexID)
startSequence = 0
}
} else if bytes.Equal(dev.ID, deviceID[:]) && dev.IndexID != 0 {
} else if dev.ID == deviceID && dev.IndexID != 0 {
// This is the other side's description of themselves. We
// check to see that it matches the IndexID we have on file,
// otherwise we drop our old index data and expect to get a
@@ -837,7 +840,7 @@ func (m *Model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
// will probably send us a full index. We drop any
// information we have and remember this new index ID
// instead.
l.Infof("Device %v folder %q has a new index ID (%v)", deviceID, folder.ID, dev.IndexID)
l.Infof("Device %v folder %s has a new index ID (%v)", deviceID, folder.Description(), dev.IndexID)
fs.Replace(deviceID, nil)
fs.SetIndexID(deviceID, dev.IndexID)
} else {
@@ -854,7 +857,6 @@ func (m *Model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
go sendIndexes(conn, folder.ID, fs, m.folderIgnores[folder.ID], startSequence, dbLocation)
}
m.fmut.Unlock()
// This breaks if we send multiple CM messages during the same connection.
if len(tempIndexFolders) > 0 {
@@ -868,81 +870,167 @@ func (m *Model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
}
}
var changed bool
var changed = false
if deviceCfg := m.cfg.Devices()[deviceID]; deviceCfg.Introducer {
foldersDevices, introduced := m.handleIntroductions(deviceCfg, cm)
if introduced {
changed = true
}
// If permitted, check if the introducer has unshare devices/folders with
// some of the devices/folders that we know were introduced to us by him.
if !deviceCfg.SkipIntroductionRemovals && m.handleDeintroductions(deviceCfg, cm, foldersDevices) {
changed = true
}
}
m.fmut.Unlock()
if m.cfg.Devices()[deviceID].Introducer {
// This device is an introducer. Go through the announced lists of folders
// and devices and add what we are missing.
if changed {
if err := m.cfg.Save(); err != nil {
l.Warnln("Failed to save config", err)
}
}
}
for _, folder := range cm.Folders {
if _, ok := m.folderDevices[folder.ID]; !ok {
continue
// handleIntroductions handles adding devices/shares that are shared by an introducer device
func (m *Model) handleIntroductions(introducerCfg config.DeviceConfiguration, cm protocol.ClusterConfig) (folderDeviceSet, bool) {
// This device is an introducer. Go through the announced lists of folders
// and devices and add what we are missing, remove what we have extra that
// has been introducer by the introducer.
changed := false
foldersDevices := make(folderDeviceSet)
for _, folder := range cm.Folders {
// We don't have this folder, skip.
if _, ok := m.folderDevices[folder.ID]; !ok {
continue
}
// Adds devices which we do not have, but the introducer has
// for the folders that we have in common. Also, shares folders
// with devices that we have in common, yet are currently not sharing
// the folder.
nextDevice:
for _, device := range folder.Devices {
foldersDevices.set(device.ID, folder.ID)
if _, ok := m.cfg.Devices()[device.ID]; !ok {
// The device is currently unknown. Add it to the config.
m.introduceDevice(device, introducerCfg)
changed = true
}
nextDevice:
for _, device := range folder.Devices {
var id protocol.DeviceID
copy(id[:], device.ID)
if _, ok := m.cfg.Devices()[id]; !ok {
// The device is currently unknown. Add it to the config.
addresses := []string{"dynamic"}
for _, addr := range device.Addresses {
if addr != "dynamic" {
addresses = append(addresses, addr)
}
}
l.Infof("Adding device %v to config (vouched for by introducer %v)", id, deviceID)
newDeviceCfg := config.DeviceConfiguration{
DeviceID: id,
Name: device.Name,
Compression: m.cfg.Devices()[deviceID].Compression,
Addresses: addresses,
CertName: device.CertName,
}
// The introducers' introducers are also our introducers.
if device.Introducer {
l.Infof("Device %v is now also an introducer", id)
newDeviceCfg.Introducer = true
}
m.cfg.SetDevice(newDeviceCfg)
changed = true
for _, er := range m.deviceFolders[device.ID] {
if er == folder.ID {
// We already share the folder with this device, so
// nothing to do.
continue nextDevice
}
}
for _, er := range m.deviceFolders[id] {
if er == folder.ID {
// We already share the folder with this device, so
// nothing to do.
continue nextDevice
}
// We don't yet share this folder with this device. Add the device
// to sharing list of the folder.
m.introduceDeviceToFolder(device, folder, introducerCfg)
changed = true
}
}
return foldersDevices, changed
}
// handleIntroductions handles removals of devices/shares that are removed by an introducer device
func (m *Model) handleDeintroductions(introducerCfg config.DeviceConfiguration, cm protocol.ClusterConfig, foldersDevices folderDeviceSet) bool {
changed := false
foldersIntroducedByOthers := make(folderDeviceSet)
// Check if we should unshare some folders, if the introducer has unshared them.
for _, folderCfg := range m.cfg.Folders() {
folderChanged := false
for i := 0; i < len(folderCfg.Devices); i++ {
if folderCfg.Devices[i].IntroducedBy == introducerCfg.DeviceID {
if !foldersDevices.has(folderCfg.Devices[i].DeviceID, folderCfg.ID) {
// We could not find that folder shared on the
// introducer with the device that was introduced to us.
// We should follow and unshare aswell.
l.Infof("Unsharing folder %s with %v as introducer %v no longer shares the folder with that device", folderCfg.Description(), folderCfg.Devices[i].DeviceID, folderCfg.Devices[i].IntroducedBy)
folderCfg.Devices = append(folderCfg.Devices[:i], folderCfg.Devices[i+1:]...)
i--
folderChanged = true
}
} else {
foldersIntroducedByOthers.set(folderCfg.Devices[i].DeviceID, folderCfg.ID)
}
}
// We don't yet share this folder with this device. Add the device
// to sharing list of the folder.
l.Infof("Adding device %v to share %q (vouched for by introducer %v)", id, folder.ID, deviceID)
m.deviceFolders[id] = append(m.deviceFolders[id], folder.ID)
m.folderDevices[folder.ID] = append(m.folderDevices[folder.ID], id)
folderCfg := m.cfg.Folders()[folder.ID]
folderCfg.Devices = append(folderCfg.Devices, config.FolderDeviceConfiguration{
DeviceID: id,
})
m.cfg.SetFolder(folderCfg)
// We've modified the folder, hence update it.
if folderChanged {
m.cfg.SetFolder(folderCfg)
changed = true
}
}
// Check if we should remove some devices, if the introducer no longer
// shares any folder with them. Yet do not remove if we share other
// folders that haven't been introduced by the introducer.
for _, device := range m.cfg.Devices() {
if device.IntroducedBy == introducerCfg.DeviceID {
if !foldersDevices.hasDevice(device.DeviceID) {
if foldersIntroducedByOthers.hasDevice(device.DeviceID) {
l.Infof("Would have removed %v as %v no longer shares any folders, yet there are other folders that are shared with this device that haven't been introduced by this introducer.", device.DeviceID, device.IntroducedBy)
continue
}
// The introducer no longer shares any folder with the
// device, remove the device.
l.Infof("Removing device %v as introducer %v no longer shares any folders with that device", device.DeviceID, device.IntroducedBy)
m.cfg.RemoveDevice(device.DeviceID)
changed = true
}
}
}
if changed {
m.cfg.Save()
return changed
}
func (m *Model) introduceDevice(device protocol.Device, introducerCfg config.DeviceConfiguration) {
addresses := []string{"dynamic"}
for _, addr := range device.Addresses {
if addr != "dynamic" {
addresses = append(addresses, addr)
}
}
l.Infof("Adding device %v to config (vouched for by introducer %v)", device.ID, introducerCfg.DeviceID)
newDeviceCfg := config.DeviceConfiguration{
DeviceID: device.ID,
Name: device.Name,
Compression: introducerCfg.Compression,
Addresses: addresses,
CertName: device.CertName,
IntroducedBy: introducerCfg.DeviceID,
}
// The introducers' introducers are also our introducers.
if device.Introducer {
l.Infof("Device %v is now also an introducer", device.ID)
newDeviceCfg.Introducer = true
newDeviceCfg.SkipIntroductionRemovals = device.SkipIntroductionRemovals
}
m.cfg.SetDevice(newDeviceCfg)
}
func (m *Model) introduceDeviceToFolder(device protocol.Device, folder protocol.Folder, introducerCfg config.DeviceConfiguration) {
l.Infof("Sharing folder %s with %v (vouched for by introducer %v)", folder.Description(), device.ID, introducerCfg.DeviceID)
m.deviceFolders[device.ID] = append(m.deviceFolders[device.ID], folder.ID)
m.folderDevices.set(device.ID, folder.ID)
folderCfg := m.cfg.Folders()[folder.ID]
folderCfg.Devices = append(folderCfg.Devices, config.FolderDeviceConfiguration{
DeviceID: device.ID,
IntroducedBy: introducerCfg.DeviceID,
})
m.cfg.SetFolder(folderCfg)
}
// Closed is called when a connection has been closed
@@ -1468,7 +1556,6 @@ func (m *Model) updateLocalsFromScanning(folder string, fs []protocol.FileInfo)
m.fmut.RLock()
folderCfg := m.folderCfgs[folder]
m.fmut.RUnlock()
// Fire the LocalChangeDetected event to notify listeners about local updates.
m.localChangeDetected(folderCfg, fs)
}
@@ -1648,14 +1735,14 @@ func (m *Model) internalScanFolderSubdirs(folder string, subDirs []string) error
if err := m.CheckFolderHealth(folder); err != nil {
runner.setError(err)
l.Infof("Stopping folder %s due to error: %s", folder, err)
l.Infof("Stopping folder %s due to error: %s", folderCfg.Description(), err)
return err
}
if err := ignores.Load(filepath.Join(folderCfg.Path(), ".stignore")); err != nil && !os.IsNotExist(err) {
err = fmt.Errorf("loading ignores: %v", err)
runner.setError(err)
l.Infof("Stopping folder %s due to error: %s", folder, err)
l.Infof("Stopping folder %s due to error: %s", folderCfg.Description(), err)
return err
}
@@ -1712,7 +1799,7 @@ func (m *Model) internalScanFolderSubdirs(folder string, subDirs []string) error
for f := range fchan {
if len(batch) == batchSizeFiles || blocksHandled > batchSizeBlocks {
if err := m.CheckFolderHealth(folder); err != nil {
l.Infof("Stopping folder %s mid-scan due to folder error: %s", folder, err)
l.Infof("Stopping folder %s mid-scan due to folder error: %s", folderCfg.Description(), err)
return err
}
m.updateLocalsFromScanning(folder, batch)
@@ -1724,7 +1811,7 @@ func (m *Model) internalScanFolderSubdirs(folder string, subDirs []string) error
}
if err := m.CheckFolderHealth(folder); err != nil {
l.Infof("Stopping folder %s mid-scan due to folder error: %s", folder, err)
l.Infof("Stopping folder %s mid-scan due to folder error: %s", folderCfg.Description(), err)
return err
} else if len(batch) > 0 {
m.updateLocalsFromScanning(folder, batch)
@@ -1800,13 +1887,13 @@ func (m *Model) internalScanFolderSubdirs(folder string, subDirs []string) error
})
if iterError != nil {
l.Infof("Stopping folder %s mid-scan due to folder error: %s", folder, iterError)
l.Infof("Stopping folder %s mid-scan due to folder error: %s", folderCfg.Description(), iterError)
return iterError
}
}
if err := m.CheckFolderHealth(folder); err != nil {
l.Infof("Stopping folder %s mid-scan due to folder error: %s", folder, err)
l.Infof("Stopping folder %s mid-scan due to folder error: %s", folderCfg.Description(), err)
return err
} else if len(batch) > 0 {
m.updateLocalsFromScanning(folder, batch)
@@ -1862,7 +1949,12 @@ func (m *Model) generateClusterConfig(device protocol.DeviceID) protocol.Cluster
var message protocol.ClusterConfig
m.fmut.RLock()
for _, folder := range m.deviceFolders[device] {
// The list of folders in the message is sorted, so we always get the
// same order.
folders := m.deviceFolders[device]
sort.Strings(folders)
for _, folder := range folders {
folderCfg := m.cfg.Folders()[folder]
fs := m.folderFiles[folder]
@@ -1875,12 +1967,8 @@ func (m *Model) generateClusterConfig(device protocol.DeviceID) protocol.Cluster
DisableTempIndexes: folderCfg.DisableTempIndexes,
}
for _, device := range m.folderDevices[folder] {
// DeviceID is a value type, but with an underlying array. Copy it
// so we don't grab aliases to the same array later on in device[:]
device := device
// TODO: Set read only bit when relevant, and when we have per device
// access controls.
// Devices are sorted, so we always get the same order.
for _, device := range m.folderDevices.sortedDevices(folder) {
deviceCfg := m.cfg.Devices()[device]
var indexID protocol.IndexID
@@ -1894,7 +1982,7 @@ func (m *Model) generateClusterConfig(device protocol.DeviceID) protocol.Cluster
}
protocolDevice := protocol.Device{
ID: device[:],
ID: device,
Name: deviceCfg.Name,
Addresses: deviceCfg.Addresses,
Compression: deviceCfg.Compression,
@@ -1998,8 +2086,8 @@ func (m *Model) RemoteSequence(folder string) (int64, bool) {
}
var ver int64
for _, n := range m.folderDevices[folder] {
ver += fs.Sequence(n)
for device := range m.folderDevices[folder] {
ver += fs.Sequence(device)
}
return ver, true
@@ -2069,15 +2157,18 @@ func (m *Model) GlobalDirectoryTree(folder, prefix string, levels int, dirsonly
}
func (m *Model) Availability(folder, file string, version protocol.Vector, block protocol.BlockInfo) []Availability {
// Acquire this lock first, as the value returned from foldersFiles can
// get heavily modified on Close()
// The slightly unusual locking sequence here is because we need to hold
// pmut for the duration (as the value returned from foldersFiles can
// get heavily modified on Close()), but also must acquire fmut before
// pmut. (The locks can be *released* in any order.)
m.fmut.RLock()
m.pmut.RLock()
defer m.pmut.RUnlock()
m.fmut.RLock()
fs, ok := m.folderFiles[folder]
devices := m.folderDevices[folder]
m.fmut.RUnlock()
if !ok {
return nil
}
@@ -2090,7 +2181,7 @@ func (m *Model) Availability(folder, file string, version protocol.Vector, block
}
}
for _, device := range devices {
for device := range devices {
if m.deviceDownloads[device].Has(folder, file, version, int32(block.Offset/protocol.BlockSize)) {
availabilities = append(availabilities, Availability{ID: device, FromTemporary: true})
}
@@ -2200,9 +2291,9 @@ func (m *Model) runnerExchangeError(folder config.FolderConfiguration, err error
if err != nil {
if oldErr != nil && oldErr.Error() != err.Error() {
l.Infof("Folder %q error changed: %q -> %q", folder.ID, oldErr, err)
l.Infof("Folder %s error changed: %q -> %q", folder.Description(), oldErr, err)
} else if oldErr == nil {
l.Warnf("Stopping folder %q - %v", folder.ID, err)
l.Warnf("Stopping folder %s - %v", folder.Description(), err)
}
if runnerExists {
runner.setError(err)
@@ -2476,3 +2567,42 @@ func shouldIgnore(file db.FileIntf, matcher *ignore.Matcher, ignoreDelete bool)
return false
}
// folderDeviceSet is a set of (folder, deviceID) pairs
type folderDeviceSet map[string]map[protocol.DeviceID]struct{}
// set adds the (dev, folder) pair to the set
func (s folderDeviceSet) set(dev protocol.DeviceID, folder string) {
devs, ok := s[folder]
if !ok {
devs = make(map[protocol.DeviceID]struct{})
s[folder] = devs
}
devs[dev] = struct{}{}
}
// has returns true if the (dev, folder) pair is in the set
func (s folderDeviceSet) has(dev protocol.DeviceID, folder string) bool {
_, ok := s[folder][dev]
return ok
}
// hasDevice returns true if the device is set on any folder
func (s folderDeviceSet) hasDevice(dev protocol.DeviceID) bool {
for _, devices := range s {
if _, ok := devices[dev]; ok {
return true
}
}
return false
}
// sortedDevices returns the list of devices for a given folder, sorted
func (s folderDeviceSet) sortedDevices(folder string) []protocol.DeviceID {
devs := make([]protocol.DeviceID, 0, len(s[folder]))
for dev := range s[folder] {
devs = append(devs, dev)
}
sort.Sort(protocol.DeviceIDs(devs))
return devs
}

View File

@@ -444,14 +444,14 @@ func TestClusterConfig(t *testing.T) {
if l := len(r.Devices); l != 2 {
t.Errorf("Incorrect number of devices %d != 2", l)
}
if id := r.Devices[0].ID; !bytes.Equal(id, device1[:]) {
t.Errorf("Incorrect device ID %x != %x", id, device1)
if id := r.Devices[0].ID; id != device1 {
t.Errorf("Incorrect device ID %s != %s", id, device1)
}
if !r.Devices[0].Introducer {
t.Error("Device1 should be flagged as Introducer")
}
if id := r.Devices[1].ID; !bytes.Equal(id, device2[:]) {
t.Errorf("Incorrect device ID %x != %x", id, device2)
if id := r.Devices[1].ID; id != device2 {
t.Errorf("Incorrect device ID %s != %s", id, device2)
}
if r.Devices[1].Introducer {
t.Error("Device2 should not be flagged as Introducer")
@@ -464,20 +464,390 @@ func TestClusterConfig(t *testing.T) {
if l := len(r.Devices); l != 2 {
t.Errorf("Incorrect number of devices %d != 2", l)
}
if id := r.Devices[0].ID; !bytes.Equal(id, device1[:]) {
t.Errorf("Incorrect device ID %x != %x", id, device1)
if id := r.Devices[0].ID; id != device1 {
t.Errorf("Incorrect device ID %s != %s", id, device1)
}
if !r.Devices[0].Introducer {
t.Error("Device1 should be flagged as Introducer")
}
if id := r.Devices[1].ID; !bytes.Equal(id, device2[:]) {
t.Errorf("Incorrect device ID %x != %x", id, device2)
if id := r.Devices[1].ID; id != device2 {
t.Errorf("Incorrect device ID %s != %s", id, device2)
}
if r.Devices[1].Introducer {
t.Error("Device2 should not be flagged as Introducer")
}
}
func TestIntroducer(t *testing.T) {
var introducedByAnyone protocol.DeviceID
// LocalDeviceID is a magic value meaning don't check introducer
contains := func(cfg config.FolderConfiguration, id, introducedBy protocol.DeviceID) bool {
for _, dev := range cfg.Devices {
if dev.DeviceID.Equals(id) {
if introducedBy.Equals(introducedByAnyone) {
return true
}
return introducedBy.Equals(introducedBy)
}
}
return false
}
newState := func(cfg config.Configuration) (*config.Wrapper, *Model) {
db := db.OpenMemory()
wcfg := config.Wrap("/tmp/test", cfg)
m := NewModel(wcfg, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
for _, folder := range cfg.Folders {
m.AddFolder(folder)
}
m.ServeBackground()
m.AddConnection(connections.Connection{
IntermediateConnection: connections.IntermediateConnection{
Conn: tls.Client(&fakeConn{}, nil),
},
Connection: &FakeConnection{
id: device1,
},
}, protocol.HelloResult{})
return wcfg, m
}
wcfg, m := newState(config.Configuration{
Devices: []config.DeviceConfiguration{
{
DeviceID: device1,
Introducer: true,
},
},
Folders: []config.FolderConfiguration{
{
ID: "folder1",
Devices: []config.FolderDeviceConfiguration{
{DeviceID: device1},
},
},
{
ID: "folder2",
Devices: []config.FolderDeviceConfiguration{
{DeviceID: device1},
},
},
},
})
m.ClusterConfig(device1, protocol.ClusterConfig{
Folders: []protocol.Folder{
{
ID: "folder1",
Devices: []protocol.Device{
{
ID: device2,
Introducer: true,
SkipIntroductionRemovals: true,
},
},
},
},
})
if newDev, ok := wcfg.Device(device2); !ok || !newDev.Introducer || !newDev.SkipIntroductionRemovals {
t.Error("devie 2 missing or wrong flags")
}
if !contains(wcfg.Folders()["folder1"], device2, device1) {
t.Error("expected folder 1 to have device2 introduced by device 1")
}
wcfg, m = newState(config.Configuration{
Devices: []config.DeviceConfiguration{
{
DeviceID: device1,
Introducer: true,
},
{
DeviceID: device2,
IntroducedBy: device1,
},
},
Folders: []config.FolderConfiguration{
{
ID: "folder1",
Devices: []config.FolderDeviceConfiguration{
{DeviceID: device1},
{DeviceID: device2, IntroducedBy: device1},
},
},
{
ID: "folder2",
Devices: []config.FolderDeviceConfiguration{
{DeviceID: device1},
},
},
},
})
m.ClusterConfig(device1, protocol.ClusterConfig{
Folders: []protocol.Folder{
{
ID: "folder2",
Devices: []protocol.Device{
{
ID: device2,
Introducer: true,
SkipIntroductionRemovals: true,
},
},
},
},
})
// Should not get introducer, as it's already unset, and it's an existing device.
if newDev, ok := wcfg.Device(device2); !ok || newDev.Introducer || newDev.SkipIntroductionRemovals {
t.Error("device 2 missing or changed flags")
}
if contains(wcfg.Folders()["folder1"], device2, introducedByAnyone) {
t.Error("expected device 2 to be removed from folder 1")
}
if !contains(wcfg.Folders()["folder2"], device2, device1) {
t.Error("expected device 2 to be added to folder 2")
}
wcfg, m = newState(config.Configuration{
Devices: []config.DeviceConfiguration{
{
DeviceID: device1,
Introducer: true,
},
{
DeviceID: device2,
IntroducedBy: device1,
},
},
Folders: []config.FolderConfiguration{
{
ID: "folder1",
Devices: []config.FolderDeviceConfiguration{
{DeviceID: device1},
{DeviceID: device2, IntroducedBy: device1},
},
},
{
ID: "folder2",
Devices: []config.FolderDeviceConfiguration{
{DeviceID: device1},
{DeviceID: device2, IntroducedBy: device1},
},
},
},
})
m.ClusterConfig(device1, protocol.ClusterConfig{})
if _, ok := wcfg.Device(device2); ok {
t.Error("device 2 should have been removed")
}
if contains(wcfg.Folders()["folder1"], device2, introducedByAnyone) {
t.Error("expected device 2 to be removed from folder 1")
}
if contains(wcfg.Folders()["folder2"], device2, introducedByAnyone) {
t.Error("expected device 2 to be removed from folder 2")
}
// Two cases when removals should not happen
// 1. Introducer flag no longer set on device
wcfg, m = newState(config.Configuration{
Devices: []config.DeviceConfiguration{
{
DeviceID: device1,
Introducer: false,
},
{
DeviceID: device2,
IntroducedBy: device1,
},
},
Folders: []config.FolderConfiguration{
{
ID: "folder1",
Devices: []config.FolderDeviceConfiguration{
{DeviceID: device1},
{DeviceID: device2, IntroducedBy: device1},
},
},
{
ID: "folder2",
Devices: []config.FolderDeviceConfiguration{
{DeviceID: device1},
{DeviceID: device2, IntroducedBy: device1},
},
},
},
})
m.ClusterConfig(device1, protocol.ClusterConfig{})
if _, ok := wcfg.Device(device2); !ok {
t.Error("device 2 should not have been removed")
}
if !contains(wcfg.Folders()["folder1"], device2, device1) {
t.Error("expected device 2 not to be removed from folder 1")
}
if !contains(wcfg.Folders()["folder2"], device2, device1) {
t.Error("expected device 2 not to be removed from folder 2")
}
// 2. SkipIntroductionRemovals is set
wcfg, m = newState(config.Configuration{
Devices: []config.DeviceConfiguration{
{
DeviceID: device1,
Introducer: true,
SkipIntroductionRemovals: true,
},
{
DeviceID: device2,
IntroducedBy: device1,
},
},
Folders: []config.FolderConfiguration{
{
ID: "folder1",
Devices: []config.FolderDeviceConfiguration{
{DeviceID: device1},
{DeviceID: device2, IntroducedBy: device1},
},
},
{
ID: "folder2",
Devices: []config.FolderDeviceConfiguration{
{DeviceID: device1},
},
},
},
})
m.ClusterConfig(device1, protocol.ClusterConfig{
Folders: []protocol.Folder{
{
ID: "folder2",
Devices: []protocol.Device{
{
ID: device2,
Introducer: true,
SkipIntroductionRemovals: true,
},
},
},
},
})
if _, ok := wcfg.Device(device2); !ok {
t.Error("device 2 should not have been removed")
}
if !contains(wcfg.Folders()["folder1"], device2, device1) {
t.Error("expected device 2 not to be removed from folder 1")
}
if !contains(wcfg.Folders()["folder2"], device2, device1) {
t.Error("expected device 2 not to be added to folder 2")
}
// Test device not being removed as it's shared without an introducer.
wcfg, m = newState(config.Configuration{
Devices: []config.DeviceConfiguration{
{
DeviceID: device1,
Introducer: true,
},
{
DeviceID: device2,
IntroducedBy: device1,
},
},
Folders: []config.FolderConfiguration{
{
ID: "folder1",
Devices: []config.FolderDeviceConfiguration{
{DeviceID: device1},
{DeviceID: device2, IntroducedBy: device1},
},
},
{
ID: "folder2",
Devices: []config.FolderDeviceConfiguration{
{DeviceID: device1},
{DeviceID: device2},
},
},
},
})
m.ClusterConfig(device1, protocol.ClusterConfig{})
if _, ok := wcfg.Device(device2); !ok {
t.Error("device 2 should not have been removed")
}
if contains(wcfg.Folders()["folder1"], device2, introducedByAnyone) {
t.Error("expected device 2 to be removed from folder 1")
}
if !contains(wcfg.Folders()["folder2"], device2, introducedByAnyone) {
t.Error("expected device 2 not to be removed from folder 2")
}
// Test device not being removed as it's shared by a different introducer.
wcfg, m = newState(config.Configuration{
Devices: []config.DeviceConfiguration{
{
DeviceID: device1,
Introducer: true,
},
{
DeviceID: device2,
IntroducedBy: device1,
},
},
Folders: []config.FolderConfiguration{
{
ID: "folder1",
Devices: []config.FolderDeviceConfiguration{
{DeviceID: device1},
{DeviceID: device2, IntroducedBy: device1},
},
},
{
ID: "folder2",
Devices: []config.FolderDeviceConfiguration{
{DeviceID: device1},
{DeviceID: device2, IntroducedBy: protocol.LocalDeviceID},
},
},
},
})
m.ClusterConfig(device1, protocol.ClusterConfig{})
if _, ok := wcfg.Device(device2); !ok {
t.Error("device 2 should not have been removed")
}
if contains(wcfg.Folders()["folder1"], device2, introducedByAnyone) {
t.Error("expected device 2 to be removed from folder 1")
}
if !contains(wcfg.Folders()["folder2"], device2, introducedByAnyone) {
t.Error("expected device 2 not to be removed from folder 2")
}
}
func TestIgnores(t *testing.T) {
arrEqual := func(a, b []string) bool {
if len(a) != len(b) {
@@ -1324,8 +1694,8 @@ func TestIssue3028(t *testing.T) {
// Get a count of how many files are there now
locorigfiles, _, _ := m.LocalSize("default")
globorigfiles, _, _ := m.GlobalSize("default")
locorigfiles := m.LocalSize("default").Files
globorigfiles := m.GlobalSize("default").Files
// Delete and rescan specifically these two
@@ -1336,19 +1706,19 @@ func TestIssue3028(t *testing.T) {
// Verify that the number of files decreased by two and the number of
// deleted files increases by two
locnowfiles, locdelfiles, _ := m.LocalSize("default")
globnowfiles, globdelfiles, _ := m.GlobalSize("default")
if locnowfiles != locorigfiles-2 {
t.Errorf("Incorrect local accounting; got %d current files, expected %d", locnowfiles, locorigfiles-2)
loc := m.LocalSize("default")
glob := m.GlobalSize("default")
if loc.Files != locorigfiles-2 {
t.Errorf("Incorrect local accounting; got %d current files, expected %d", loc.Files, locorigfiles-2)
}
if globnowfiles != globorigfiles-2 {
t.Errorf("Incorrect global accounting; got %d current files, expected %d", globnowfiles, globorigfiles-2)
if glob.Files != globorigfiles-2 {
t.Errorf("Incorrect global accounting; got %d current files, expected %d", glob.Files, globorigfiles-2)
}
if locdelfiles != 2 {
t.Errorf("Incorrect local accounting; got %d deleted files, expected 2", locdelfiles)
if loc.Deleted != 2 {
t.Errorf("Incorrect local accounting; got %d deleted files, expected 2", loc.Deleted)
}
if globdelfiles != 2 {
t.Errorf("Incorrect global accounting; got %d deleted files, expected 2", globdelfiles)
if glob.Deleted != 2 {
t.Errorf("Incorrect global accounting; got %d deleted files, expected 2", glob.Deleted)
}
}
@@ -1565,8 +1935,8 @@ func TestSharedWithClearedOnDisconnect(t *testing.T) {
{
ID: "default",
Devices: []protocol.Device{
{ID: device1[:]},
{ID: device2[:]},
{ID: device1},
{ID: device2},
},
},
},
@@ -1576,8 +1946,8 @@ func TestSharedWithClearedOnDisconnect(t *testing.T) {
{
ID: "default",
Devices: []protocol.Device{
{ID: device1[:]},
{ID: device2[:]},
{ID: device1},
{ID: device2},
},
},
},
@@ -1623,7 +1993,7 @@ func TestSharedWithClearedOnDisconnect(t *testing.T) {
t.Error("folder missing?")
}
for _, id := range fdevs {
for id := range fdevs {
if id == device2 {
t.Error("still there")
}
@@ -1722,14 +2092,14 @@ func TestIssue3496(t *testing.T) {
t.Log(comp)
// Check that NeedSize does the correct thing
files, deletes, bytes := m.NeedSize("default")
if files != 1 || bytes != 1234 {
need := m.NeedSize("default")
if need.Files != 1 || need.Bytes != 1234 {
// The one we added synthetically above
t.Errorf("Incorrect need size; %d, %d != 1, 1234", files, bytes)
t.Errorf("Incorrect need size; %d, %d != 1, 1234", need.Files, need.Bytes)
}
if deletes != len(localFiles)-1 {
if need.Deleted != len(localFiles)-1 {
// The rest
t.Errorf("Incorrect need deletes; %d != %d", deletes, len(localFiles)-1)
t.Errorf("Incorrect need deletes; %d != %d", need.Deleted, len(localFiles)-1)
}
}
@@ -1751,8 +2121,8 @@ func addFakeConn(m *Model, dev protocol.DeviceID) {
{
ID: "default",
Devices: []protocol.Device{
{ID: device1[:]},
{ID: device2[:]},
{ID: device1},
{ID: device2},
},
},
},

View File

@@ -42,7 +42,7 @@ func NewProgressEmitter(cfg *config.Wrapper) *ProgressEmitter {
mut: sync.NewMutex(),
}
t.CommitConfiguration(config.Configuration{}, cfg.Raw())
t.CommitConfiguration(config.Configuration{}, cfg.RawCopy())
cfg.Subscribe(t)
return t

View File

@@ -91,6 +91,7 @@ type rwFolder struct {
allowSparse bool
checkFreeSpace bool
ignoreDelete bool
fsync bool
copiers int
pullers int
@@ -126,6 +127,7 @@ func newRWFolder(model *Model, cfg config.FolderConfiguration, ver versioner.Ver
allowSparse: !cfg.DisableSparseFiles,
checkFreeSpace: cfg.MinDiskFreePct != 0,
ignoreDelete: cfg.IgnoreDelete,
fsync: cfg.Fsync,
queue: newJobQueue(),
pullTimer: time.NewTimer(time.Second),
@@ -1372,12 +1374,50 @@ func (f *rwFolder) dbUpdaterRoutine() {
tick := time.NewTicker(maxBatchTime)
defer tick.Stop()
var changedFiles []string
var changedDirs []string
if f.fsync {
changedFiles = make([]string, 0, maxBatchSize)
changedDirs = make([]string, 0, maxBatchSize)
}
syncFilesOnce := func(files []string, syncFn func(string) error) {
sort.Strings(files)
var lastFile string
for _, file := range files {
if lastFile == file {
continue
}
lastFile = file
if err := syncFn(file); err != nil {
l.Infof("fsync %q failed: %v", file, err)
}
}
}
handleBatch := func() {
found := false
var lastFile protocol.FileInfo
for _, job := range batch {
files = append(files, job.file)
if f.fsync {
// collect changed files and dirs
switch job.jobType {
case dbUpdateHandleFile, dbUpdateShortcutFile:
// fsyncing symlinks is only supported by MacOS
if !job.file.IsSymlink() {
changedFiles = append(changedFiles,
filepath.Join(f.dir, job.file.Name))
}
case dbUpdateHandleDir:
changedDirs = append(changedDirs, filepath.Join(f.dir, job.file.Name))
}
if job.jobType != dbUpdateShortcutFile {
changedDirs = append(changedDirs,
filepath.Dir(filepath.Join(f.dir, job.file.Name)))
}
}
if job.file.IsInvalid() || (job.file.IsDirectory() && !job.file.IsSymlink()) {
continue
}
@@ -1390,6 +1430,14 @@ func (f *rwFolder) dbUpdaterRoutine() {
lastFile = job.file
}
if f.fsync {
// sync files and dirs to disk
syncFilesOnce(changedFiles, osutil.SyncFile)
changedFiles = changedFiles[:0]
syncFilesOnce(changedDirs, osutil.SyncDir)
changedDirs = changedDirs[:0]
}
// All updates to file/folder objects that originated remotely
// (across the network) use this call to updateLocals
f.model.updateLocalsFromPulling(f.folderID, files)

View File

@@ -7,14 +7,32 @@
package model
import (
"fmt"
"sync"
"time"
)
func deadlockDetect(mut sync.Locker, timeout time.Duration) {
type Holdable interface {
Holders() string
}
func newDeadlockDetector(timeout time.Duration) *deadlockDetector {
return &deadlockDetector{
timeout: timeout,
lockers: make(map[string]sync.Locker),
}
}
type deadlockDetector struct {
timeout time.Duration
lockers map[string]sync.Locker
}
func (d *deadlockDetector) Watch(name string, mut sync.Locker) {
d.lockers[name] = mut
go func() {
for {
time.Sleep(timeout / 4)
time.Sleep(d.timeout / 4)
ok := make(chan bool, 2)
go func() {
@@ -24,12 +42,18 @@ func deadlockDetect(mut sync.Locker, timeout time.Duration) {
}()
go func() {
time.Sleep(timeout)
time.Sleep(d.timeout)
ok <- false
}()
if r := <-ok; !r {
panic("deadlock detected")
msg := fmt.Sprintf("deadlock detected at %s", name)
for otherName, otherMut := range d.lockers {
if otherHolder, ok := otherMut.(Holdable); ok {
msg += "\n===" + otherName + "===\n" + otherHolder.Holders()
}
}
panic(msg)
}
}
}()

View File

@@ -77,6 +77,11 @@ func (w *AtomicWriter) Close() error {
// Try to not leave temp file around, but ignore error.
defer os.Remove(w.next.Name())
if err := w.next.Sync(); err != nil {
w.err = err
return err
}
if err := w.next.Close(); err != nil {
w.err = err
return err
@@ -97,6 +102,8 @@ func (w *AtomicWriter) Close() error {
return err
}
SyncDir(filepath.Dir(w.next.Name()))
// Set w.err to return appropriately for any future operations.
w.err = ErrClosed

View File

@@ -172,10 +172,7 @@ func copyFileContents(src, dst string) (err error) {
err = cerr
}
}()
if _, err = io.Copy(out, in); err != nil {
return
}
err = out.Sync()
_, err = io.Copy(out, in)
return
}

37
lib/osutil/sync.go Normal file
View File

@@ -0,0 +1,37 @@
// Copyright (C) 2016 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package osutil
import (
"os"
"runtime"
)
func SyncFile(path string) error {
flag := 0
if runtime.GOOS == "windows" {
flag = os.O_WRONLY
}
fd, err := os.OpenFile(path, flag, 0)
if err != nil {
return err
}
defer fd.Close()
// MacOS and Windows do not flush the disk cache
if err := fd.Sync(); err != nil {
return err
}
return nil
}
func SyncDir(path string) error {
if runtime.GOOS == "windows" {
// not supported by Windows
return nil
}
return SyncFile(path)
}

View File

@@ -255,14 +255,15 @@ func (*Folder) ProtoMessage() {}
func (*Folder) Descriptor() ([]byte, []int) { return fileDescriptorBep, []int{3} }
type Device struct {
ID []byte `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
Addresses []string `protobuf:"bytes,3,rep,name=addresses" json:"addresses,omitempty"`
Compression Compression `protobuf:"varint,4,opt,name=compression,proto3,enum=protocol.Compression" json:"compression,omitempty"`
CertName string `protobuf:"bytes,5,opt,name=cert_name,json=certName,proto3" json:"cert_name,omitempty"`
MaxSequence int64 `protobuf:"varint,6,opt,name=max_sequence,json=maxSequence,proto3" json:"max_sequence,omitempty"`
Introducer bool `protobuf:"varint,7,opt,name=introducer,proto3" json:"introducer,omitempty"`
IndexID IndexID `protobuf:"varint,8,opt,name=index_id,json=indexId,proto3,customtype=IndexID" json:"index_id"`
ID DeviceID `protobuf:"bytes,1,opt,name=id,proto3,customtype=DeviceID" json:"id"`
Name string `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
Addresses []string `protobuf:"bytes,3,rep,name=addresses" json:"addresses,omitempty"`
Compression Compression `protobuf:"varint,4,opt,name=compression,proto3,enum=protocol.Compression" json:"compression,omitempty"`
CertName string `protobuf:"bytes,5,opt,name=cert_name,json=certName,proto3" json:"cert_name,omitempty"`
MaxSequence int64 `protobuf:"varint,6,opt,name=max_sequence,json=maxSequence,proto3" json:"max_sequence,omitempty"`
Introducer bool `protobuf:"varint,7,opt,name=introducer,proto3" json:"introducer,omitempty"`
IndexID IndexID `protobuf:"varint,8,opt,name=index_id,json=indexId,proto3,customtype=IndexID" json:"index_id"`
SkipIntroductionRemovals bool `protobuf:"varint,9,opt,name=skip_introduction_removals,json=skipIntroductionRemovals,proto3" json:"skip_introduction_removals,omitempty"`
}
func (m *Device) Reset() { *m = Device{} }
@@ -621,12 +622,14 @@ func (m *Device) MarshalTo(data []byte) (int, error) {
_ = i
var l int
_ = l
if len(m.ID) > 0 {
data[i] = 0xa
i++
i = encodeVarintBep(data, i, uint64(len(m.ID)))
i += copy(data[i:], m.ID)
data[i] = 0xa
i++
i = encodeVarintBep(data, i, uint64(m.ID.ProtoSize()))
n1, err := m.ID.MarshalTo(data[i:])
if err != nil {
return 0, err
}
i += n1
if len(m.Name) > 0 {
data[i] = 0x12
i++
@@ -679,6 +682,16 @@ func (m *Device) MarshalTo(data []byte) (int, error) {
i++
i = encodeVarintBep(data, i, uint64(m.IndexID))
}
if m.SkipIntroductionRemovals {
data[i] = 0x48
i++
if m.SkipIntroductionRemovals {
data[i] = 1
} else {
data[i] = 0
}
i++
}
return i, nil
}
@@ -828,11 +841,11 @@ func (m *FileInfo) MarshalTo(data []byte) (int, error) {
data[i] = 0x4a
i++
i = encodeVarintBep(data, i, uint64(m.Version.ProtoSize()))
n1, err := m.Version.MarshalTo(data[i:])
n2, err := m.Version.MarshalTo(data[i:])
if err != nil {
return 0, err
}
i += n1
i += n2
if m.Sequence != 0 {
data[i] = 0x50
i++
@@ -1112,11 +1125,11 @@ func (m *FileDownloadProgressUpdate) MarshalTo(data []byte) (int, error) {
data[i] = 0x1a
i++
i = encodeVarintBep(data, i, uint64(m.Version.ProtoSize()))
n2, err := m.Version.MarshalTo(data[i:])
n3, err := m.Version.MarshalTo(data[i:])
if err != nil {
return 0, err
}
i += n2
i += n3
if len(m.BlockIndexes) > 0 {
for _, num := range m.BlockIndexes {
data[i] = 0x20
@@ -1273,10 +1286,8 @@ func (m *Folder) ProtoSize() (n int) {
func (m *Device) ProtoSize() (n int) {
var l int
_ = l
l = len(m.ID)
if l > 0 {
n += 1 + l + sovBep(uint64(l))
}
l = m.ID.ProtoSize()
n += 1 + l + sovBep(uint64(l))
l = len(m.Name)
if l > 0 {
n += 1 + l + sovBep(uint64(l))
@@ -1303,6 +1314,9 @@ func (m *Device) ProtoSize() (n int) {
if m.IndexID != 0 {
n += 1 + sovBep(uint64(m.IndexID))
}
if m.SkipIntroductionRemovals {
n += 2
}
return n
}
@@ -2114,9 +2128,8 @@ func (m *Device) Unmarshal(data []byte) error {
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.ID = append(m.ID[:0], data[iNdEx:postIndex]...)
if m.ID == nil {
m.ID = []byte{}
if err := m.ID.Unmarshal(data[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
case 2:
@@ -2283,6 +2296,26 @@ func (m *Device) Unmarshal(data []byte) error {
break
}
}
case 9:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field SkipIntroductionRemovals", wireType)
}
var v int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowBep
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := data[iNdEx]
iNdEx++
v |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
m.SkipIntroductionRemovals = bool(v != 0)
default:
iNdEx = preIndex
skippy, err := skipBep(data[iNdEx:])
@@ -3954,105 +3987,110 @@ var (
)
var fileDescriptorBep = []byte{
// 1589 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x57, 0x4f, 0x6f, 0xdb, 0x46,
0x16, 0xb7, 0x24, 0xea, 0xdf, 0x48, 0x76, 0xe4, 0x89, 0xe3, 0x68, 0x19, 0xc7, 0xf6, 0x32, 0x09,
0xd6, 0x2b, 0x6c, 0x9c, 0xdd, 0x64, 0x77, 0x03, 0x14, 0x68, 0x01, 0x59, 0xa2, 0x1d, 0x21, 0x32,
0xa5, 0x50, 0x92, 0xd3, 0xf4, 0x50, 0x41, 0x12, 0x47, 0x32, 0x11, 0x8a, 0xa3, 0x92, 0x54, 0x12,
0xf7, 0x23, 0xb4, 0x5f, 0xa0, 0x97, 0x02, 0x41, 0x6e, 0xbd, 0xf7, 0x43, 0xe4, 0x18, 0xe4, 0xd8,
0x43, 0xd0, 0xba, 0x97, 0x7e, 0x81, 0xde, 0xfb, 0xf8, 0x86, 0x94, 0x28, 0xff, 0x29, 0x72, 0xe8,
0xc1, 0xd0, 0xcc, 0x7b, 0xbf, 0x99, 0x37, 0xf3, 0x7b, 0xbf, 0xf7, 0x86, 0x26, 0xd9, 0x3e, 0x9b,
0xec, 0x4e, 0x1c, 0xee, 0x71, 0x9a, 0xc1, 0x9f, 0x01, 0xb7, 0xe4, 0xbb, 0x23, 0xd3, 0x3b, 0x9e,
0xf6, 0x77, 0x07, 0x7c, 0x7c, 0x6f, 0xc4, 0x47, 0xfc, 0x1e, 0x7a, 0xfa, 0xd3, 0x21, 0xce, 0x70,
0x82, 0x23, 0xb1, 0x50, 0x99, 0x90, 0xe4, 0x23, 0x66, 0x59, 0x9c, 0x6e, 0x91, 0x9c, 0xc1, 0x5e,
0x98, 0x03, 0xd6, 0xb5, 0x7b, 0x63, 0x56, 0x8c, 0x6d, 0xc7, 0x76, 0xb2, 0x3a, 0x11, 0x26, 0x0d,
0x2c, 0x3e, 0x60, 0x60, 0x99, 0xcc, 0xf6, 0x04, 0x20, 0x2e, 0x00, 0xc2, 0x84, 0x80, 0x3b, 0x64,
0x25, 0x00, 0xbc, 0x60, 0x8e, 0x6b, 0x72, 0xbb, 0x98, 0x40, 0xcc, 0xb2, 0xb0, 0x1e, 0x09, 0xa3,
0xe2, 0x92, 0xd4, 0x23, 0xd6, 0x33, 0x98, 0x43, 0xff, 0x49, 0x24, 0xef, 0x64, 0x22, 0x62, 0xad,
0xdc, 0xbf, 0xb6, 0x1b, 0xde, 0x61, 0xf7, 0x90, 0xb9, 0x6e, 0x6f, 0xc4, 0xda, 0xe0, 0xd4, 0x11,
0x42, 0x3f, 0x83, 0xe0, 0x7c, 0x3c, 0x71, 0xc0, 0xe1, 0x6f, 0x1c, 0xc7, 0x15, 0x1b, 0xe7, 0x56,
0x54, 0xe6, 0x18, 0x3d, 0xba, 0x40, 0x29, 0x93, 0xe5, 0x8a, 0x35, 0x75, 0x3d, 0xe6, 0x54, 0xb8,
0x3d, 0x34, 0x47, 0xf4, 0xdf, 0x24, 0x3d, 0xe4, 0x16, 0x9c, 0xc2, 0x85, 0xf0, 0x89, 0x9d, 0xdc,
0xfd, 0xc2, 0x7c, 0xb3, 0x7d, 0x74, 0xec, 0x49, 0x6f, 0x3f, 0x6c, 0x2d, 0xe9, 0x21, 0x4c, 0xf9,
0x36, 0x4e, 0x52, 0xc2, 0x43, 0xd7, 0x49, 0xdc, 0x34, 0x04, 0x45, 0x7b, 0xa9, 0xd3, 0x0f, 0x5b,
0xf1, 0x5a, 0x55, 0x07, 0x0b, 0x5d, 0x23, 0x49, 0xab, 0xd7, 0x67, 0x56, 0x40, 0x8e, 0x98, 0xd0,
0x1b, 0x24, 0xeb, 0xc0, 0x85, 0xbb, 0xdc, 0xb6, 0x4e, 0x90, 0x92, 0x8c, 0x9e, 0xf1, 0x0d, 0x0d,
0x98, 0xd3, 0xbb, 0x84, 0x9a, 0x23, 0x9b, 0x3b, 0xac, 0x3b, 0x61, 0xce, 0xd8, 0xc4, 0xd3, 0xba,
0x45, 0x09, 0x51, 0xab, 0xc2, 0xd3, 0x9c, 0x3b, 0xe8, 0x2d, 0xb2, 0x1c, 0xc0, 0x0d, 0x66, 0x31,
0x8f, 0x15, 0x93, 0x88, 0xcc, 0x0b, 0x63, 0x15, 0x6d, 0x70, 0xb7, 0x35, 0xc3, 0x74, 0x7b, 0x7d,
0x8b, 0x75, 0x3d, 0x36, 0x9e, 0x74, 0x4d, 0xdb, 0x60, 0xaf, 0x98, 0x5b, 0x4c, 0x21, 0x96, 0x06,
0xbe, 0x36, 0xb8, 0x6a, 0xc2, 0xe3, 0xb3, 0x21, 0x32, 0xed, 0x16, 0x0b, 0x67, 0xd9, 0xa8, 0xa2,
0x23, 0x64, 0x23, 0x80, 0x29, 0x6f, 0x80, 0x0d, 0xe1, 0x89, 0xb0, 0x91, 0x5f, 0x60, 0x83, 0x12,
0x29, 0xa2, 0x14, 0x1c, 0xd3, 0x0d, 0x92, 0xed, 0x19, 0x86, 0x9f, 0x15, 0x08, 0x95, 0x80, 0x50,
0x59, 0x7d, 0x6e, 0xa0, 0x0f, 0x17, 0xb3, 0x2c, 0x9d, 0xd5, 0xc5, 0x65, 0xe9, 0xf5, 0x29, 0x1e,
0x30, 0x27, 0x50, 0x66, 0x12, 0xe3, 0x65, 0x7c, 0x03, 0xea, 0xf2, 0xef, 0x24, 0x3f, 0xee, 0xbd,
0xea, 0xba, 0xec, 0xab, 0x29, 0xb3, 0x07, 0x0c, 0x69, 0x48, 0xe8, 0x39, 0xb0, 0xb5, 0x02, 0x13,
0xdd, 0x24, 0xc4, 0xb4, 0x3d, 0x87, 0x1b, 0x53, 0x58, 0x55, 0x4c, 0x23, 0x4f, 0x11, 0x0b, 0xfd,
0x1f, 0xc9, 0x20, 0x89, 0x5d, 0xb8, 0x68, 0x06, 0xbc, 0xd2, 0x9e, 0xec, 0xd3, 0xf1, 0xd3, 0x87,
0xad, 0x34, 0x52, 0x58, 0xab, 0x9e, 0xce, 0x87, 0x7a, 0x1a, 0xb1, 0x35, 0x43, 0x69, 0x90, 0x24,
0xda, 0x80, 0xa2, 0x94, 0x90, 0x51, 0x50, 0x57, 0xc1, 0x8c, 0xee, 0x92, 0xe4, 0xd0, 0xb4, 0x80,
0x8a, 0x38, 0xb2, 0x4e, 0x23, 0x1a, 0x04, 0x73, 0xcd, 0x1e, 0xf2, 0x80, 0x77, 0x01, 0x53, 0x3a,
0x24, 0x87, 0x1b, 0x76, 0x26, 0x46, 0xcf, 0x63, 0x7f, 0xd9, 0xb6, 0x6f, 0x12, 0x24, 0x13, 0x7a,
0x66, 0x69, 0x8b, 0x45, 0xd2, 0x56, 0x0a, 0x2a, 0x55, 0xd4, 0xdd, 0xfa, 0xf9, 0xfd, 0x22, 0xa5,
0x0a, 0xeb, 0x5d, 0xf3, 0x6b, 0x86, 0x4a, 0x4f, 0xe8, 0x38, 0xa6, 0xdb, 0x24, 0x77, 0x56, 0xde,
0xcb, 0x7a, 0xd4, 0x44, 0x6f, 0x12, 0x32, 0xe6, 0x86, 0x39, 0x34, 0x99, 0xd1, 0x75, 0x31, 0x85,
0x09, 0x3d, 0x1b, 0x5a, 0x5a, 0xb4, 0xe8, 0x0b, 0xd4, 0x17, 0xb7, 0x11, 0xa8, 0x38, 0x9c, 0xfa,
0x1e, 0xd3, 0x7e, 0xd1, 0xb3, 0x20, 0x33, 0x22, 0x6f, 0xe1, 0xd4, 0xef, 0x47, 0x36, 0x5f, 0x28,
0xab, 0x0c, 0x02, 0x96, 0x6d, 0x1e, 0x2d, 0x29, 0xd0, 0x7e, 0xd8, 0xaf, 0xb2, 0xe0, 0x5f, 0xd0,
0xfe, 0x11, 0x1b, 0x78, 0x7c, 0xd6, 0x09, 0x02, 0x18, 0x95, 0x49, 0x66, 0x26, 0x26, 0x82, 0x27,
0x9d, 0xcd, 0xfd, 0x2e, 0x39, 0xbb, 0x07, 0x44, 0xcc, 0x81, 0x3b, 0xa9, 0xcf, 0xae, 0xa6, 0xb9,
0xf4, 0x3f, 0x24, 0xb5, 0x67, 0xf1, 0xc1, 0xf3, 0xb0, 0xd2, 0xae, 0xce, 0xa3, 0xa1, 0x3d, 0x92,
0x9d, 0x54, 0x1f, 0x81, 0x9f, 0x48, 0xdf, 0xbd, 0xde, 0x5a, 0x52, 0x9e, 0x90, 0xec, 0x0c, 0xe0,
0x67, 0x9e, 0x0f, 0x87, 0x2e, 0xf3, 0x30, 0x4d, 0x09, 0x3d, 0x98, 0xcd, 0xc8, 0x8f, 0x63, 0x5c,
0x41, 0x3e, 0xd8, 0x8e, 0x7b, 0xee, 0x31, 0x26, 0x24, 0xaf, 0xe3, 0x38, 0xd8, 0xf2, 0x53, 0x92,
0x12, 0x37, 0xa4, 0x0f, 0x48, 0x66, 0xc0, 0xa7, 0xb6, 0x37, 0xef, 0x87, 0xab, 0xd1, 0xb2, 0x43,
0x4f, 0x70, 0xaa, 0x19, 0x50, 0xd9, 0x27, 0xe9, 0xc0, 0x05, 0x5c, 0x87, 0x3d, 0x40, 0xda, 0xbb,
0x16, 0x96, 0x46, 0xeb, 0x98, 0x3b, 0x1e, 0x96, 0x46, 0xa4, 0x41, 0x42, 0x6e, 0xa6, 0xe2, 0x7c,
0x92, 0x2e, 0x26, 0xca, 0x8f, 0x31, 0x92, 0xd6, 0x7d, 0x02, 0x5d, 0x2f, 0xd2, 0x4c, 0x92, 0x0b,
0xcd, 0x64, 0x2e, 0xf5, 0xf8, 0x82, 0xd4, 0x43, 0xb5, 0x26, 0x22, 0x6a, 0x9d, 0x93, 0x23, 0x5d,
0x48, 0x4e, 0xf2, 0x02, 0x72, 0x52, 0x73, 0x72, 0x7c, 0xe1, 0x0c, 0x1d, 0x3e, 0xc6, 0xe6, 0xc9,
0x9d, 0x9e, 0x73, 0x12, 0x28, 0x6b, 0xd9, 0xb7, 0xb6, 0x43, 0xa3, 0xd2, 0x25, 0x19, 0x9d, 0xb9,
0x13, 0xd0, 0x10, 0xbb, 0xf4, 0xd8, 0xb0, 0x3d, 0x54, 0x6a, 0x0f, 0x0f, 0x0d, 0xdb, 0xfb, 0x63,
0xfa, 0x0f, 0x22, 0x0d, 0xb8, 0x21, 0x8e, 0xbc, 0x12, 0xcd, 0xbf, 0xea, 0x38, 0x1c, 0xde, 0x27,
0x03, 0x2a, 0xc9, 0x07, 0xc0, 0xdb, 0x5c, 0xa8, 0xf2, 0x97, 0xb6, 0xc5, 0x7b, 0x46, 0xd3, 0xe1,
0x23, 0xbf, 0xd9, 0x5d, 0x5a, 0xf2, 0x55, 0x92, 0x9e, 0x62, 0x53, 0x08, 0x8b, 0xfe, 0xf6, 0x62,
0x91, 0x9e, 0xdd, 0x48, 0x74, 0x90, 0x50, 0xd9, 0xc1, 0x52, 0xe5, 0x7d, 0x8c, 0xc8, 0x97, 0xa3,
0x69, 0x8d, 0xe4, 0x04, 0xb2, 0x1b, 0x79, 0xb7, 0x77, 0x3e, 0x26, 0x10, 0xf6, 0x07, 0x32, 0x9d,
0x8d, 0x2f, 0x7c, 0x1c, 0x22, 0x95, 0x98, 0xf8, 0xb8, 0x4a, 0x84, 0xe7, 0x10, 0x6b, 0x64, 0xf6,
0xc4, 0x49, 0x70, 0xf7, 0xa4, 0x9e, 0xef, 0x8b, 0x42, 0x41, 0x9b, 0x92, 0x22, 0x52, 0xd3, 0xb4,
0x47, 0xca, 0x16, 0x49, 0x56, 0x2c, 0x8e, 0xc9, 0x4a, 0xc1, 0xfb, 0xeb, 0x42, 0x98, 0x80, 0x43,
0x31, 0x2b, 0xbd, 0x8f, 0x93, 0x5c, 0xe4, 0xd3, 0x03, 0xce, 0xb3, 0x52, 0xa9, 0x77, 0x5a, 0x6d,
0x55, 0xef, 0x56, 0x1a, 0xda, 0x7e, 0xed, 0xa0, 0xb0, 0x24, 0x6f, 0x7c, 0xf3, 0xfd, 0x76, 0x71,
0x3c, 0x07, 0x2d, 0x7e, 0x55, 0x40, 0x88, 0x9a, 0x56, 0x55, 0x3f, 0x2f, 0xc4, 0xe4, 0x35, 0x00,
0x16, 0x22, 0x40, 0xf1, 0x10, 0xfc, 0x8b, 0xe4, 0x11, 0xd0, 0xed, 0x34, 0xab, 0xe5, 0xb6, 0x5a,
0x88, 0xcb, 0x32, 0xe0, 0xd6, 0xcf, 0xe2, 0x02, 0xbe, 0x6f, 0x41, 0x5d, 0xa8, 0x4f, 0x3a, 0x6a,
0xab, 0x5d, 0x48, 0xc8, 0xeb, 0x00, 0xa4, 0x11, 0x60, 0x58, 0x31, 0x77, 0x40, 0x86, 0x6a, 0xab,
0xd9, 0xd0, 0x5a, 0x6a, 0x41, 0x92, 0xaf, 0x03, 0xea, 0xea, 0x02, 0x2a, 0x50, 0xe8, 0xff, 0xc9,
0x6a, 0xb5, 0xf1, 0x54, 0xab, 0x37, 0xca, 0xd5, 0x6e, 0x53, 0x6f, 0x1c, 0xc0, 0x9a, 0x56, 0x21,
0x29, 0x6f, 0x01, 0xfe, 0x46, 0x04, 0x7f, 0x4e, 0x70, 0x37, 0x81, 0xbd, 0x9a, 0x76, 0x50, 0x48,
0xc9, 0x57, 0x01, 0x7a, 0x25, 0x02, 0xf5, 0x49, 0xf5, 0x6f, 0x5c, 0xa9, 0x37, 0x20, 0x74, 0xfa,
0xdc, 0x8d, 0x91, 0xec, 0xd2, 0x97, 0x84, 0x9e, 0xff, 0x38, 0xa3, 0xb7, 0x89, 0xa4, 0x35, 0x34,
0x15, 0x08, 0xc5, 0xfb, 0x9f, 0x47, 0x68, 0xdc, 0x66, 0x54, 0x21, 0x89, 0xfa, 0x17, 0xff, 0x05,
0x32, 0xff, 0x06, 0xa0, 0x6b, 0xe7, 0x41, 0xe0, 0x2c, 0x71, 0x92, 0x8b, 0x6e, 0xac, 0x90, 0xcc,
0xa1, 0xda, 0x2e, 0x03, 0xb9, 0x65, 0xd8, 0x1c, 0x8f, 0x14, 0xba, 0x0f, 0x99, 0xd7, 0xc3, 0x02,
0xdc, 0x20, 0x49, 0x4d, 0x3d, 0x52, 0x75, 0xd8, 0x78, 0x15, 0x00, 0xcb, 0x21, 0x40, 0x63, 0xa0,
0x2b, 0xf8, 0x16, 0x48, 0x95, 0xeb, 0x4f, 0xcb, 0xcf, 0x5a, 0x90, 0x1c, 0x0a, 0xee, 0x95, 0xd0,
0x5d, 0xb6, 0x5e, 0xf6, 0x4e, 0xdc, 0xd2, 0xef, 0x31, 0x92, 0x8f, 0x3e, 0x7b, 0xb0, 0x40, 0xda,
0xaf, 0xd5, 0xd5, 0x30, 0x5c, 0xd4, 0xe7, 0x8f, 0xe9, 0x0e, 0xc9, 0x56, 0x6b, 0xba, 0x5a, 0x69,
0x37, 0xf4, 0x67, 0xe1, 0x5d, 0xa2, 0xa0, 0xaa, 0xe9, 0xa0, 0xb8, 0xfd, 0x8f, 0xc1, 0x7c, 0xeb,
0xd9, 0x61, 0xbd, 0xa6, 0x3d, 0xee, 0xe2, 0x8e, 0x71, 0xf9, 0x06, 0x80, 0xaf, 0x47, 0xc1, 0xad,
0x93, 0xb1, 0x65, 0xda, 0xcf, 0x71, 0xe3, 0x87, 0x64, 0x35, 0x84, 0xcf, 0x03, 0x24, 0xe4, 0x6d,
0x58, 0xb3, 0x71, 0xc1, 0x9a, 0x79, 0x9c, 0x07, 0xe4, 0x4a, 0xb8, 0xb0, 0xa3, 0x3d, 0xd6, 0x40,
0x16, 0xa0, 0x9c, 0x4d, 0x58, 0x26, 0x5f, 0xb0, 0xac, 0x63, 0x3f, 0xb7, 0x41, 0x14, 0xa5, 0x1f,
0x62, 0x24, 0x3b, 0xeb, 0x50, 0x3e, 0xcf, 0x5a, 0xa3, 0xab, 0xea, 0x7a, 0x43, 0x0f, 0x2f, 0x3e,
0x73, 0x6a, 0x1c, 0x87, 0xf0, 0xe1, 0x95, 0x3e, 0x50, 0x35, 0x55, 0xaf, 0x55, 0xc2, 0x7a, 0x98,
0x41, 0x0e, 0x98, 0xcd, 0x1c, 0x73, 0x00, 0xff, 0x02, 0xe4, 0x61, 0x9b, 0x56, 0xa7, 0xf2, 0x28,
0xbc, 0x31, 0x0a, 0x38, 0xb2, 0x55, 0x6b, 0x3a, 0x38, 0xc6, 0xdb, 0x96, 0xfc, 0xd2, 0x39, 0x2a,
0xd7, 0x6b, 0x55, 0x01, 0x4d, 0xc8, 0x45, 0x80, 0xae, 0xcd, 0xa0, 0x35, 0xf1, 0xec, 0xfb, 0xd8,
0x92, 0x41, 0x36, 0xff, 0xbc, 0x17, 0xc1, 0x17, 0x49, 0xaa, 0xdc, 0x6c, 0xaa, 0x5a, 0x35, 0x3c,
0xfd, 0xdc, 0x57, 0x9e, 0x4c, 0x98, 0x6d, 0xf8, 0x88, 0xfd, 0x86, 0x7e, 0xa0, 0xb6, 0xc3, 0xc3,
0xcf, 0x11, 0xfb, 0xdc, 0x19, 0x31, 0x6f, 0x6f, 0xe3, 0xed, 0x2f, 0x9b, 0x4b, 0xef, 0xe0, 0xef,
0xed, 0xe9, 0x66, 0xec, 0x1d, 0xfc, 0xfd, 0x7c, 0xba, 0xb9, 0xf4, 0x1b, 0xfc, 0xbe, 0xfe, 0x75,
0x33, 0xd6, 0x4f, 0x61, 0xef, 0x7a, 0xf0, 0x47, 0x00, 0x00, 0x00, 0xff, 0xff, 0x83, 0x88, 0xa1,
0x6a, 0xa6, 0x0d, 0x00, 0x00,
// 1665 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xac, 0x56, 0x4f, 0x73, 0xdb, 0xc6,
0x15, 0x17, 0x48, 0xf0, 0xdf, 0x23, 0xa5, 0x40, 0x6b, 0x59, 0x41, 0x61, 0x85, 0x42, 0xe0, 0xb8,
0x55, 0x34, 0x8d, 0xe2, 0xc6, 0x69, 0x33, 0xd3, 0x69, 0x3b, 0x43, 0x91, 0x90, 0xcc, 0x09, 0x0d,
0x32, 0x4b, 0xca, 0xae, 0x73, 0x28, 0x06, 0x24, 0x96, 0x14, 0xc6, 0x20, 0x96, 0x05, 0x40, 0xd9,
0xea, 0x47, 0x60, 0xbf, 0x40, 0x2f, 0x9c, 0xc9, 0xf4, 0xd6, 0x7b, 0x3f, 0x84, 0x8f, 0x99, 0x1c,
0x7b, 0xf0, 0x34, 0xea, 0xa5, 0xc7, 0x5e, 0x7a, 0xef, 0x60, 0x17, 0x00, 0x41, 0xfd, 0xe9, 0xe4,
0x90, 0x13, 0x77, 0xdf, 0xfb, 0xed, 0xdb, 0x7d, 0xbf, 0xf7, 0x7e, 0x8f, 0x80, 0xca, 0x90, 0xcc,
0x8e, 0x66, 0x3e, 0x0d, 0x29, 0x2a, 0xb3, 0x9f, 0x11, 0x75, 0x95, 0x4f, 0x26, 0x4e, 0x78, 0x3e,
0x1f, 0x1e, 0x8d, 0xe8, 0xf4, 0xd3, 0x09, 0x9d, 0xd0, 0x4f, 0x99, 0x67, 0x38, 0x1f, 0xb3, 0x1d,
0xdb, 0xb0, 0x15, 0x3f, 0xa8, 0xcd, 0xa0, 0xf0, 0x94, 0xb8, 0x2e, 0x45, 0xfb, 0x50, 0xb5, 0xc9,
0x85, 0x33, 0x22, 0xa6, 0x67, 0x4d, 0x89, 0x2c, 0xa8, 0xc2, 0x41, 0x05, 0x03, 0x37, 0x19, 0xd6,
0x94, 0x44, 0x80, 0x91, 0xeb, 0x10, 0x2f, 0xe4, 0x80, 0x1c, 0x07, 0x70, 0x13, 0x03, 0x3c, 0x82,
0xad, 0x18, 0x70, 0x41, 0xfc, 0xc0, 0xa1, 0x9e, 0x9c, 0x67, 0x98, 0x4d, 0x6e, 0x7d, 0xce, 0x8d,
0x5a, 0x00, 0xc5, 0xa7, 0xc4, 0xb2, 0x89, 0x8f, 0x3e, 0x06, 0x31, 0xbc, 0x9c, 0xf1, 0xbb, 0xb6,
0x3e, 0xbb, 0x7f, 0x94, 0xe4, 0x70, 0xf4, 0x8c, 0x04, 0x81, 0x35, 0x21, 0x83, 0xcb, 0x19, 0xc1,
0x0c, 0x82, 0x7e, 0x07, 0xd5, 0x11, 0x9d, 0xce, 0x7c, 0x12, 0xb0, 0xc0, 0x39, 0x76, 0x62, 0xef,
0xc6, 0x89, 0xe6, 0x0a, 0x83, 0xb3, 0x07, 0xb4, 0x06, 0x6c, 0x36, 0xdd, 0x79, 0x10, 0x12, 0xbf,
0x49, 0xbd, 0xb1, 0x33, 0x41, 0x8f, 0xa1, 0x34, 0xa6, 0xae, 0x4d, 0xfc, 0x40, 0x16, 0xd4, 0xfc,
0x41, 0xf5, 0x33, 0x69, 0x15, 0xec, 0x84, 0x39, 0x8e, 0xc5, 0xb7, 0xef, 0xf6, 0x37, 0x70, 0x02,
0xd3, 0xfe, 0x9c, 0x83, 0x22, 0xf7, 0xa0, 0x5d, 0xc8, 0x39, 0x36, 0xa7, 0xe8, 0xb8, 0x78, 0xf5,
0x6e, 0x3f, 0xd7, 0x6e, 0xe1, 0x9c, 0x63, 0xa3, 0x1d, 0x28, 0xb8, 0xd6, 0x90, 0xb8, 0x31, 0x39,
0x7c, 0x83, 0x1e, 0x40, 0xc5, 0x27, 0x96, 0x6d, 0x52, 0xcf, 0xbd, 0x64, 0x94, 0x94, 0x71, 0x39,
0x32, 0x74, 0x3d, 0xf7, 0x12, 0x7d, 0x02, 0xc8, 0x99, 0x78, 0xd4, 0x27, 0xe6, 0x8c, 0xf8, 0x53,
0x87, 0xbd, 0x36, 0x90, 0x45, 0x86, 0xda, 0xe6, 0x9e, 0xde, 0xca, 0x81, 0x1e, 0xc2, 0x66, 0x0c,
0xb7, 0x89, 0x4b, 0x42, 0x22, 0x17, 0x18, 0xb2, 0xc6, 0x8d, 0x2d, 0x66, 0x43, 0x8f, 0x61, 0xc7,
0x76, 0x02, 0x6b, 0xe8, 0x12, 0x33, 0x24, 0xd3, 0x99, 0xe9, 0x78, 0x36, 0x79, 0x43, 0x02, 0xb9,
0xc8, 0xb0, 0x28, 0xf6, 0x0d, 0xc8, 0x74, 0xd6, 0xe6, 0x9e, 0x88, 0x0d, 0x5e, 0xe9, 0x40, 0x96,
0xae, 0xb3, 0xd1, 0x62, 0x8e, 0x84, 0x8d, 0x18, 0xa6, 0xfd, 0x27, 0x07, 0x45, 0xee, 0x41, 0x3f,
0x4d, 0xd9, 0xa8, 0x1d, 0xef, 0x46, 0xa8, 0x7f, 0xbc, 0xdb, 0x2f, 0x73, 0x5f, 0xbb, 0x95, 0x61,
0x07, 0x81, 0x98, 0xe9, 0x1c, 0xb6, 0x46, 0x7b, 0x50, 0xb1, 0x6c, 0x3b, 0xaa, 0x12, 0x09, 0xe4,
0xbc, 0x9a, 0x3f, 0xa8, 0xe0, 0x95, 0x01, 0x7d, 0xb1, 0x5e, 0x75, 0xf1, 0x7a, 0x9f, 0xdc, 0x55,
0xee, 0x88, 0xf2, 0x11, 0xf1, 0xe3, 0x4e, 0x2d, 0xb0, 0xfb, 0xca, 0x91, 0x81, 0xf5, 0xe9, 0x87,
0x50, 0x9b, 0x5a, 0x6f, 0xcc, 0x80, 0xfc, 0x71, 0x4e, 0xbc, 0x11, 0x61, 0xb4, 0xe4, 0x71, 0x75,
0x6a, 0xbd, 0xe9, 0xc7, 0x26, 0x54, 0x07, 0x70, 0xbc, 0xd0, 0xa7, 0xf6, 0x7c, 0x44, 0x7c, 0xb9,
0xc4, 0x78, 0xcb, 0x58, 0xd0, 0x2f, 0xa1, 0xcc, 0x48, 0x35, 0x1d, 0x5b, 0x2e, 0xab, 0xc2, 0x81,
0x78, 0xac, 0xc4, 0x89, 0x97, 0x18, 0xa5, 0x2c, 0xef, 0x64, 0x89, 0x4b, 0x0c, 0xdb, 0xb6, 0xd1,
0x6f, 0x40, 0x09, 0x5e, 0x39, 0x51, 0x41, 0x78, 0xa4, 0xd0, 0xa1, 0x9e, 0xe9, 0x93, 0x29, 0xbd,
0xb0, 0xdc, 0x40, 0xae, 0xb0, 0x6b, 0xe4, 0x08, 0xd1, 0xce, 0x00, 0x70, 0xec, 0xd7, 0xba, 0x50,
0x60, 0x11, 0xd1, 0x2e, 0x14, 0x79, 0x53, 0xc6, 0x2a, 0x8d, 0x77, 0xe8, 0x08, 0x0a, 0x63, 0xc7,
0x25, 0x81, 0x9c, 0x63, 0x35, 0x44, 0x99, 0x8e, 0x76, 0x5c, 0xd2, 0xf6, 0xc6, 0x34, 0xae, 0x22,
0x87, 0x69, 0x67, 0x50, 0x65, 0x01, 0xcf, 0x66, 0xb6, 0x15, 0x92, 0x1f, 0x2d, 0xec, 0x5f, 0xf3,
0x50, 0x4e, 0x3c, 0x69, 0xd1, 0x85, 0x4c, 0xd1, 0x0f, 0x63, 0xdd, 0x73, 0x15, 0xef, 0xde, 0x8c,
0x97, 0x11, 0x3e, 0x02, 0x31, 0x70, 0xfe, 0x44, 0x98, 0x6e, 0xf2, 0x98, 0xad, 0x91, 0x0a, 0xd5,
0xeb, 0x62, 0xd9, 0xc4, 0x59, 0x13, 0xfa, 0x00, 0x60, 0x4a, 0x6d, 0x67, 0xec, 0x10, 0xdb, 0x0c,
0x58, 0x03, 0xe4, 0x71, 0x25, 0xb1, 0xf4, 0x91, 0x1c, 0xb5, 0x7b, 0x24, 0x15, 0x3b, 0xd6, 0x44,
0xb2, 0x8d, 0x3c, 0x8e, 0x77, 0x61, 0xb9, 0x8e, 0x1d, 0x57, 0x3d, 0xd9, 0x46, 0xd3, 0xcd, 0xa3,
0x6b, 0x22, 0x2d, 0x33, 0xc0, 0xa6, 0x47, 0xb3, 0x02, 0x7d, 0x0c, 0xa5, 0x64, 0xfa, 0x45, 0xf5,
0x5c, 0x53, 0xd2, 0x73, 0x32, 0x0a, 0x69, 0x3a, 0x57, 0x62, 0x18, 0x52, 0xa0, 0x9c, 0xb6, 0x22,
0xb0, 0x97, 0xa6, 0xfb, 0x68, 0xe6, 0xa6, 0x79, 0x78, 0x81, 0x5c, 0x55, 0x85, 0x83, 0x02, 0x4e,
0x53, 0x33, 0x02, 0xf4, 0x0b, 0x28, 0x1e, 0xbb, 0x74, 0xf4, 0x2a, 0xd1, 0xed, 0xbd, 0xd5, 0x6d,
0xcc, 0x9e, 0xa9, 0x4e, 0x71, 0xc8, 0x80, 0xbf, 0x16, 0xff, 0xf2, 0xcd, 0xfe, 0x86, 0xf6, 0x15,
0x54, 0x52, 0x40, 0x54, 0x79, 0x3a, 0x1e, 0x07, 0x24, 0x64, 0x65, 0xca, 0xe3, 0x78, 0x97, 0x92,
0x9f, 0x63, 0xf7, 0x72, 0xf2, 0x11, 0x88, 0xe7, 0x56, 0x70, 0xce, 0x0a, 0x52, 0xc3, 0x6c, 0x1d,
0x87, 0xfc, 0x2d, 0x14, 0x79, 0x86, 0xe8, 0x09, 0x94, 0x47, 0x74, 0xee, 0x85, 0xab, 0xe9, 0xba,
0x9d, 0x15, 0x2d, 0xf3, 0xc4, 0xaf, 0x4a, 0x81, 0xda, 0x09, 0x94, 0x62, 0x17, 0x7a, 0x94, 0x4e,
0x14, 0xf1, 0xf8, 0x7e, 0x22, 0xac, 0xfe, 0x39, 0xf5, 0xc3, 0xb5, 0x81, 0xb2, 0x03, 0x85, 0x0b,
0xcb, 0x9d, 0xf3, 0xf7, 0x89, 0x98, 0x6f, 0xb4, 0xbf, 0x0b, 0x50, 0xc2, 0x11, 0x81, 0x41, 0x98,
0x19, 0xd4, 0x85, 0xb5, 0x41, 0xbd, 0x6a, 0xf5, 0xdc, 0x5a, 0xab, 0x27, 0xdd, 0x9a, 0xcf, 0x74,
0xeb, 0x8a, 0x1c, 0xf1, 0x56, 0x72, 0x0a, 0xb7, 0x90, 0x53, 0x5c, 0x91, 0x13, 0x35, 0xce, 0xd8,
0xa7, 0x53, 0x36, 0x8a, 0xa9, 0x6f, 0xf9, 0x97, 0x71, 0x67, 0x6d, 0x46, 0xd6, 0x41, 0x62, 0xd4,
0x4c, 0x28, 0x63, 0x12, 0xcc, 0xa8, 0x17, 0x90, 0x3b, 0x9f, 0x8d, 0x40, 0xb4, 0xad, 0xd0, 0x62,
0x8f, 0xae, 0x61, 0xb6, 0x46, 0x3f, 0x03, 0x71, 0x44, 0x6d, 0xfe, 0xe4, 0xad, 0x6c, 0xfd, 0x75,
0xdf, 0xa7, 0x7e, 0x93, 0xda, 0x04, 0x33, 0x80, 0x36, 0x03, 0xa9, 0x45, 0x5f, 0x7b, 0x2e, 0xb5,
0xec, 0x9e, 0x4f, 0x27, 0xd1, 0xa8, 0xbc, 0x53, 0xf2, 0x2d, 0x28, 0xcd, 0xd9, 0x50, 0x48, 0x44,
0xff, 0xd1, 0xba, 0x48, 0xaf, 0x07, 0xe2, 0x13, 0x24, 0xe9, 0xec, 0xf8, 0xa8, 0xf6, 0x9d, 0x00,
0xca, 0xdd, 0x68, 0xd4, 0x86, 0x2a, 0x47, 0x9a, 0x99, 0xaf, 0x80, 0x83, 0x1f, 0x72, 0x11, 0x9b,
0x0f, 0x30, 0x4f, 0xd7, 0xb7, 0xfe, 0xb5, 0x64, 0x94, 0x98, 0xff, 0x61, 0x4a, 0x7c, 0x08, 0x9b,
0x4c, 0x23, 0xe9, 0x1f, 0xa6, 0xa8, 0xe6, 0x0f, 0x0a, 0xb8, 0x36, 0xe4, 0x42, 0x61, 0x36, 0xad,
0x08, 0x62, 0xcf, 0xf1, 0x26, 0xda, 0x3e, 0x14, 0x9a, 0x2e, 0x65, 0xc5, 0x2a, 0xfa, 0xc4, 0x0a,
0xa8, 0x97, 0x70, 0xc8, 0x77, 0x87, 0xdf, 0xe5, 0xa0, 0x9a, 0xf9, 0x90, 0x41, 0x8f, 0x61, 0xab,
0xd9, 0x39, 0xeb, 0x0f, 0x74, 0x6c, 0x36, 0xbb, 0xc6, 0x49, 0xfb, 0x54, 0xda, 0x50, 0xf6, 0x16,
0x4b, 0x55, 0x9e, 0xae, 0x40, 0xeb, 0xdf, 0x28, 0xfb, 0x50, 0x68, 0x1b, 0x2d, 0xfd, 0xf7, 0x92,
0xa0, 0xec, 0x2c, 0x96, 0xaa, 0x94, 0x01, 0xf2, 0x3f, 0x82, 0x9f, 0x43, 0x8d, 0x01, 0xcc, 0xb3,
0x5e, 0xab, 0x31, 0xd0, 0xa5, 0x9c, 0xa2, 0x2c, 0x96, 0xea, 0xee, 0x75, 0x5c, 0xcc, 0xf7, 0x43,
0x28, 0x61, 0xfd, 0xab, 0x33, 0xbd, 0x3f, 0x90, 0xf2, 0xca, 0xee, 0x62, 0xa9, 0xa2, 0x0c, 0x30,
0x51, 0xcc, 0x23, 0x28, 0x63, 0xbd, 0xdf, 0xeb, 0x1a, 0x7d, 0x5d, 0x12, 0x95, 0xf7, 0x17, 0x4b,
0xf5, 0xde, 0x1a, 0x2a, 0xee, 0xd0, 0x5f, 0xc1, 0x76, 0xab, 0xfb, 0xc2, 0xe8, 0x74, 0x1b, 0x2d,
0xb3, 0x87, 0xbb, 0xa7, 0x58, 0xef, 0xf7, 0xa5, 0x82, 0xb2, 0xbf, 0x58, 0xaa, 0x0f, 0x32, 0xf8,
0x1b, 0x0d, 0xf7, 0x01, 0x88, 0xbd, 0xb6, 0x71, 0x2a, 0x15, 0x95, 0x7b, 0x8b, 0xa5, 0xfa, 0x5e,
0x06, 0x1a, 0x91, 0x1a, 0x65, 0xdc, 0xec, 0x74, 0xfb, 0xba, 0x54, 0xba, 0x91, 0x31, 0x23, 0xfb,
0xf0, 0x0f, 0x80, 0x6e, 0x7e, 0xea, 0xa1, 0x8f, 0x40, 0x34, 0xba, 0x86, 0x2e, 0x6d, 0xf0, 0xfc,
0x6f, 0x22, 0x0c, 0xea, 0x11, 0xa4, 0x41, 0xbe, 0xf3, 0xf5, 0xe7, 0x92, 0xa0, 0xfc, 0x64, 0xb1,
0x54, 0xef, 0xdf, 0x04, 0x75, 0xbe, 0xfe, 0xfc, 0x90, 0x42, 0x35, 0x1b, 0x58, 0x83, 0xf2, 0x33,
0x7d, 0xd0, 0x68, 0x35, 0x06, 0x0d, 0x69, 0x83, 0x3f, 0x29, 0x71, 0x3f, 0x23, 0xa1, 0xc5, 0x04,
0xb8, 0x07, 0x05, 0x43, 0x7f, 0xae, 0x63, 0x49, 0x50, 0xb6, 0x17, 0x4b, 0x75, 0x33, 0x01, 0x18,
0xe4, 0x82, 0xf8, 0xa8, 0x0e, 0xc5, 0x46, 0xe7, 0x45, 0xe3, 0x65, 0x5f, 0xca, 0x29, 0x68, 0xb1,
0x54, 0xb7, 0x12, 0x77, 0xc3, 0x7d, 0x6d, 0x5d, 0x06, 0x87, 0xff, 0x15, 0xa0, 0x96, 0xfd, 0xdb,
0x43, 0x75, 0x10, 0x4f, 0xda, 0x1d, 0x3d, 0xb9, 0x2e, 0xeb, 0x8b, 0xd6, 0xe8, 0x00, 0x2a, 0xad,
0x36, 0xd6, 0x9b, 0x83, 0x2e, 0x7e, 0x99, 0xe4, 0x92, 0x05, 0xb5, 0x1c, 0x9f, 0x35, 0x77, 0xf4,
0x69, 0x59, 0xeb, 0xbf, 0x7c, 0xd6, 0x69, 0x1b, 0x5f, 0x9a, 0x2c, 0x62, 0x4e, 0x79, 0xb0, 0x58,
0xaa, 0xef, 0x67, 0xc1, 0xfd, 0xcb, 0xa9, 0xeb, 0x78, 0xaf, 0x58, 0xe0, 0x2f, 0x60, 0x3b, 0x81,
0xaf, 0x2e, 0xc8, 0x2b, 0xea, 0x62, 0xa9, 0xee, 0xdd, 0x72, 0x66, 0x75, 0xcf, 0x13, 0x78, 0x2f,
0x39, 0x78, 0x66, 0x7c, 0x69, 0x74, 0x5f, 0x18, 0x92, 0xa8, 0xd4, 0x17, 0x4b, 0x55, 0xb9, 0xe5,
0xd8, 0x99, 0xf7, 0xca, 0xa3, 0xaf, 0xbd, 0xc3, 0xbf, 0x09, 0x50, 0x49, 0x27, 0x54, 0xc4, 0xb3,
0xd1, 0x35, 0x75, 0x8c, 0xbb, 0x38, 0x49, 0x3c, 0x75, 0x1a, 0x94, 0x2d, 0xd1, 0x87, 0x50, 0x3a,
0xd5, 0x0d, 0x1d, 0xb7, 0x9b, 0x89, 0x1e, 0x52, 0xc8, 0x29, 0xf1, 0x88, 0xef, 0x8c, 0xd0, 0xc7,
0x50, 0x33, 0xba, 0x66, 0xff, 0xac, 0xf9, 0x34, 0xc9, 0x98, 0x35, 0x70, 0x26, 0x54, 0x7f, 0x3e,
0x3a, 0x67, 0xd9, 0x1e, 0x46, 0xd2, 0x79, 0xde, 0xe8, 0xb4, 0x5b, 0x1c, 0x9a, 0x57, 0xe4, 0xc5,
0x52, 0xdd, 0x49, 0xa1, 0x6d, 0xfe, 0xb7, 0x1f, 0x61, 0x0f, 0x6d, 0xa8, 0xff, 0xff, 0x59, 0x84,
0x54, 0x28, 0x36, 0x7a, 0x3d, 0xdd, 0x68, 0x25, 0xaf, 0x5f, 0xf9, 0x1a, 0xb3, 0x19, 0xf1, 0xec,
0x08, 0x71, 0xd2, 0xc5, 0xa7, 0xfa, 0x20, 0x79, 0xfc, 0x0a, 0x71, 0x42, 0xfd, 0x09, 0x09, 0x8f,
0xf7, 0xde, 0x7e, 0x5f, 0xdf, 0xf8, 0xf6, 0xfb, 0xfa, 0xc6, 0xdb, 0xab, 0xba, 0xf0, 0xed, 0x55,
0x5d, 0xf8, 0xe7, 0x55, 0x7d, 0xe3, 0xdf, 0x57, 0x75, 0xe1, 0x9b, 0x7f, 0xd5, 0x85, 0x61, 0x91,
0xcd, 0xae, 0x27, 0xff, 0x0b, 0x00, 0x00, 0xff, 0xff, 0x64, 0x17, 0x1e, 0x19, 0xf4, 0x0d, 0x00,
0x00,
}

View File

@@ -1,4 +1,4 @@
// protoc --proto_path=../../../../../:../../../../gogo/protobuf/protobuf:. --gogofast_out=. message.proto
// protoc protoc -I ../../../../../ -I ../../../../gogo/protobuf/protobuf -I . --gogofast_out=. message.proto
syntax = "proto3";
@@ -63,14 +63,15 @@ message Folder {
}
message Device {
bytes id = 1 [(gogoproto.customname) = "ID"];
string name = 2;
repeated string addresses = 3;
Compression compression = 4;
string cert_name = 5;
int64 max_sequence = 6;
bool introducer = 7;
uint64 index_id = 8 [(gogoproto.customname) = "IndexID", (gogoproto.customtype) = "IndexID", (gogoproto.nullable) = false];
bytes id = 1 [(gogoproto.customname) = "ID", (gogoproto.customtype) = "DeviceID", (gogoproto.nullable) = false];
string name = 2;
repeated string addresses = 3;
Compression compression = 4;
string cert_name = 5;
int64 max_sequence = 6;
bool introducer = 7;
uint64 index_id = 8 [(gogoproto.customname) = "IndexID", (gogoproto.customtype) = "IndexID", (gogoproto.nullable) = false];
bool skip_introduction_removals = 9;
}
enum Compression {

View File

@@ -1,7 +1,7 @@
// Copyright (C) 2014 The Protocol Authors.
//go:generate go run ../../script/protofmt.go bep.proto
//go:generate protoc --proto_path=../../../../../:../../../../gogo/protobuf/protobuf:. --gogofast_out=. bep.proto
//go:generate protoc -I ../../../../../ -I ../../../../gogo/protobuf/protobuf -I . --gogofast_out=. bep.proto
package protocol
@@ -134,3 +134,8 @@ func (i *IndexID) Unmarshal(bs []byte) error {
func NewIndexID() IndexID {
return IndexID(rand.Int64())
}
func (f Folder) Description() string {
// used by logging stuff
return fmt.Sprintf("%q (%s)", f.Label, f.ID)
}

View File

@@ -16,10 +16,15 @@ import (
"github.com/calmh/luhn"
)
type DeviceID [32]byte
const DeviceIDLength = 32
type DeviceID [DeviceIDLength]byte
type ShortID uint64
var LocalDeviceID = DeviceID{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
var (
LocalDeviceID = DeviceID{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
EmptyDeviceID = DeviceID{ /* all zeroes */ }
)
// NewDeviceID generates a new device ID from the raw bytes of a certificate
func NewDeviceID(rawCert []byte) DeviceID {
@@ -47,6 +52,9 @@ func DeviceIDFromBytes(bs []byte) DeviceID {
// String returns the canonical string representation of the device ID
func (n DeviceID) String() string {
if n == EmptyDeviceID {
return ""
}
id := base32.StdEncoding.EncodeToString(n[:])
id = strings.Trim(id, "=")
id, err := luhnify(id)
@@ -94,6 +102,9 @@ func (n *DeviceID) UnmarshalText(bs []byte) error {
var err error
switch len(id) {
case 0:
*n = EmptyDeviceID
return nil
case 56:
// New style, with check digits
id, err = unluhnify(id)
@@ -114,6 +125,29 @@ func (n *DeviceID) UnmarshalText(bs []byte) error {
}
}
func (n *DeviceID) ProtoSize() int {
// Used by protobuf marshaller.
return DeviceIDLength
}
func (n *DeviceID) MarshalTo(bs []byte) (int, error) {
// Used by protobuf marshaller.
if len(bs) < DeviceIDLength {
return 0, errors.New("destination too short")
}
copy(bs, (*n)[:])
return DeviceIDLength, nil
}
func (n *DeviceID) Unmarshal(bs []byte) error {
// Used by protobuf marshaller.
if len(bs) < DeviceIDLength {
return errors.New("not enough data")
}
copy((*n)[:], bs)
return nil
}
func luhnify(s string) (string, error) {
if len(s) != 52 {
panic("unsupported string length")
@@ -169,3 +203,18 @@ func untypeoify(s string) string {
s = strings.Replace(s, "8", "B", -1)
return s
}
// DeviceIDs is a sortable slice of DeviceID
type DeviceIDs []DeviceID
func (l DeviceIDs) Len() int {
return len(l)
}
func (l DeviceIDs) Less(a, b int) bool {
return l[a].Compare(l[b]) == -1
}
func (l DeviceIDs) Swap(a, b int) {
l[a], l[b] = l[b], l[a]
}

View File

@@ -1,5 +1,8 @@
// Copyright (C) 2014 The Protocol Authors.
//go:generate go run ../../script/protofmt.go deviceid_test.proto
//go:generate protoc -I ../../../../../ -I ../../../../gogo/protobuf/protobuf -I . --gogofast_out=. deviceid_test.proto
package protocol
import "testing"
@@ -34,7 +37,8 @@ var validateCases = []struct {
s string
ok bool
}{
{"", false},
{"", true}, // Empty device ID, all zeroes
{"a", false},
{"P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2", true},
{"P56IOI7-MZJNU2-IQGDREY-DM2MGT-MGL3BXN-PQ6W5B-TBBZ4TJ-XZWICQ", true},
{"P56IOI7 MZJNU2I QGDREYD M2MGTMGL 3BXNPQ6W 5BTB BZ4T JXZWICQ", true},
@@ -96,3 +100,53 @@ func TestDeviceIDFromBytes(t *testing.T) {
t.Errorf("Wrong device ID, got %q, want %q", id1, formatted)
}
}
func TestNewDeviceIDMarshalling(t *testing.T) {
// The new DeviceID.Unmarshal / DeviceID.MarshalTo serialization should
// be message compatible with how we used to serialize DeviceIDs.
// Create a message with a device ID in old style bytes format
id0, _ := DeviceIDFromString(formatted)
msg0 := TestOldDeviceID{id0[:]}
// Marshal it
bs, err := msg0.Marshal()
if err != nil {
t.Fatal(err)
}
// Unmarshal using the new DeviceID.Unmarshal
var msg1 TestNewDeviceID
if err := msg1.Unmarshal(bs); err != nil {
t.Fatal(err)
}
// Verify it's the same
if msg1.Test != id0 {
t.Error("Mismatch in old -> new direction")
}
// Marshal using the new DeviceID.MarshalTo
bs, err = msg1.Marshal()
if err != nil {
t.Fatal(err)
}
// Create an old style message and and attempt unmarshal
var msg2 TestOldDeviceID
if err := msg2.Unmarshal(bs); err != nil {
t.Fatal(err)
}
// Verify it's the same
if DeviceIDFromBytes(msg2.Test) != id0 {
t.Error("Mismatch in old -> new direction")
}
}

View File

@@ -0,0 +1,442 @@
// Code generated by protoc-gen-gogo.
// source: deviceid_test.proto
// DO NOT EDIT!
/*
Package protocol is a generated protocol buffer package.
It is generated from these files:
deviceid_test.proto
It has these top-level messages:
TestOldDeviceID
TestNewDeviceID
*/
package protocol
import proto "github.com/gogo/protobuf/proto"
import fmt "fmt"
import math "math"
import _ "github.com/gogo/protobuf/gogoproto"
import io "io"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
const _ = proto.GoGoProtoPackageIsVersion1
type TestOldDeviceID struct {
Test []byte `protobuf:"bytes,1,opt,name=test,proto3" json:"test,omitempty"`
}
func (m *TestOldDeviceID) Reset() { *m = TestOldDeviceID{} }
func (m *TestOldDeviceID) String() string { return proto.CompactTextString(m) }
func (*TestOldDeviceID) ProtoMessage() {}
func (*TestOldDeviceID) Descriptor() ([]byte, []int) { return fileDescriptorDeviceidTest, []int{0} }
type TestNewDeviceID struct {
Test DeviceID `protobuf:"bytes,1,opt,name=test,proto3,customtype=DeviceID" json:"test"`
}
func (m *TestNewDeviceID) Reset() { *m = TestNewDeviceID{} }
func (m *TestNewDeviceID) String() string { return proto.CompactTextString(m) }
func (*TestNewDeviceID) ProtoMessage() {}
func (*TestNewDeviceID) Descriptor() ([]byte, []int) { return fileDescriptorDeviceidTest, []int{1} }
func init() {
proto.RegisterType((*TestOldDeviceID)(nil), "protocol.TestOldDeviceID")
proto.RegisterType((*TestNewDeviceID)(nil), "protocol.TestNewDeviceID")
}
func (m *TestOldDeviceID) Marshal() (data []byte, err error) {
size := m.ProtoSize()
data = make([]byte, size)
n, err := m.MarshalTo(data)
if err != nil {
return nil, err
}
return data[:n], nil
}
func (m *TestOldDeviceID) MarshalTo(data []byte) (int, error) {
var i int
_ = i
var l int
_ = l
if len(m.Test) > 0 {
data[i] = 0xa
i++
i = encodeVarintDeviceidTest(data, i, uint64(len(m.Test)))
i += copy(data[i:], m.Test)
}
return i, nil
}
func (m *TestNewDeviceID) Marshal() (data []byte, err error) {
size := m.ProtoSize()
data = make([]byte, size)
n, err := m.MarshalTo(data)
if err != nil {
return nil, err
}
return data[:n], nil
}
func (m *TestNewDeviceID) MarshalTo(data []byte) (int, error) {
var i int
_ = i
var l int
_ = l
data[i] = 0xa
i++
i = encodeVarintDeviceidTest(data, i, uint64(m.Test.ProtoSize()))
n1, err := m.Test.MarshalTo(data[i:])
if err != nil {
return 0, err
}
i += n1
return i, nil
}
func encodeFixed64DeviceidTest(data []byte, offset int, v uint64) int {
data[offset] = uint8(v)
data[offset+1] = uint8(v >> 8)
data[offset+2] = uint8(v >> 16)
data[offset+3] = uint8(v >> 24)
data[offset+4] = uint8(v >> 32)
data[offset+5] = uint8(v >> 40)
data[offset+6] = uint8(v >> 48)
data[offset+7] = uint8(v >> 56)
return offset + 8
}
func encodeFixed32DeviceidTest(data []byte, offset int, v uint32) int {
data[offset] = uint8(v)
data[offset+1] = uint8(v >> 8)
data[offset+2] = uint8(v >> 16)
data[offset+3] = uint8(v >> 24)
return offset + 4
}
func encodeVarintDeviceidTest(data []byte, offset int, v uint64) int {
for v >= 1<<7 {
data[offset] = uint8(v&0x7f | 0x80)
v >>= 7
offset++
}
data[offset] = uint8(v)
return offset + 1
}
func (m *TestOldDeviceID) ProtoSize() (n int) {
var l int
_ = l
l = len(m.Test)
if l > 0 {
n += 1 + l + sovDeviceidTest(uint64(l))
}
return n
}
func (m *TestNewDeviceID) ProtoSize() (n int) {
var l int
_ = l
l = m.Test.ProtoSize()
n += 1 + l + sovDeviceidTest(uint64(l))
return n
}
func sovDeviceidTest(x uint64) (n int) {
for {
n++
x >>= 7
if x == 0 {
break
}
}
return n
}
func sozDeviceidTest(x uint64) (n int) {
return sovDeviceidTest(uint64((x << 1) ^ uint64((int64(x) >> 63))))
}
func (m *TestOldDeviceID) Unmarshal(data []byte) error {
l := len(data)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowDeviceidTest
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := data[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: TestOldDeviceID: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: TestOldDeviceID: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Test", wireType)
}
var byteLen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowDeviceidTest
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := data[iNdEx]
iNdEx++
byteLen |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
if byteLen < 0 {
return ErrInvalidLengthDeviceidTest
}
postIndex := iNdEx + byteLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Test = append(m.Test[:0], data[iNdEx:postIndex]...)
if m.Test == nil {
m.Test = []byte{}
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipDeviceidTest(data[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthDeviceidTest
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *TestNewDeviceID) Unmarshal(data []byte) error {
l := len(data)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowDeviceidTest
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := data[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: TestNewDeviceID: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: TestNewDeviceID: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Test", wireType)
}
var byteLen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowDeviceidTest
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := data[iNdEx]
iNdEx++
byteLen |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
if byteLen < 0 {
return ErrInvalidLengthDeviceidTest
}
postIndex := iNdEx + byteLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
if err := m.Test.Unmarshal(data[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipDeviceidTest(data[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthDeviceidTest
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func skipDeviceidTest(data []byte) (n int, err error) {
l := len(data)
iNdEx := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowDeviceidTest
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := data[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
wireType := int(wire & 0x7)
switch wireType {
case 0:
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowDeviceidTest
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
iNdEx++
if data[iNdEx-1] < 0x80 {
break
}
}
return iNdEx, nil
case 1:
iNdEx += 8
return iNdEx, nil
case 2:
var length int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowDeviceidTest
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := data[iNdEx]
iNdEx++
length |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
iNdEx += length
if length < 0 {
return 0, ErrInvalidLengthDeviceidTest
}
return iNdEx, nil
case 3:
for {
var innerWire uint64
var start int = iNdEx
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowDeviceidTest
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := data[iNdEx]
iNdEx++
innerWire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
innerWireType := int(innerWire & 0x7)
if innerWireType == 4 {
break
}
next, err := skipDeviceidTest(data[start:])
if err != nil {
return 0, err
}
iNdEx = start + next
}
return iNdEx, nil
case 4:
return iNdEx, nil
case 5:
iNdEx += 4
return iNdEx, nil
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
}
panic("unreachable")
}
var (
ErrInvalidLengthDeviceidTest = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowDeviceidTest = fmt.Errorf("proto: integer overflow")
)
var fileDescriptorDeviceidTest = []byte{
// 176 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x12, 0x4e, 0x49, 0x2d, 0xcb,
0x4c, 0x4e, 0xcd, 0x4c, 0x89, 0x2f, 0x49, 0x2d, 0x2e, 0xd1, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17,
0xe2, 0x00, 0x53, 0xc9, 0xf9, 0x39, 0x52, 0xba, 0xe9, 0x99, 0x25, 0x19, 0xa5, 0x49, 0x7a, 0xc9,
0xf9, 0xb9, 0xfa, 0xe9, 0xf9, 0xe9, 0xf9, 0xfa, 0x60, 0x99, 0xa4, 0xd2, 0x34, 0x30, 0x0f, 0xcc,
0x01, 0xb3, 0x20, 0x1a, 0x95, 0x54, 0xb9, 0xf8, 0x43, 0x52, 0x8b, 0x4b, 0xfc, 0x73, 0x52, 0x5c,
0xc0, 0xc6, 0x7a, 0xba, 0x08, 0x09, 0x71, 0xb1, 0x80, 0x4c, 0x96, 0x60, 0x54, 0x60, 0xd4, 0xe0,
0x09, 0x02, 0xb3, 0x95, 0xcc, 0x21, 0xca, 0xfc, 0x52, 0xcb, 0xe1, 0xca, 0x54, 0x90, 0x95, 0x39,
0x09, 0x9c, 0xb8, 0x27, 0xcf, 0x70, 0xeb, 0x9e, 0x3c, 0x07, 0x4c, 0x1e, 0xa2, 0xd1, 0x49, 0xe6,
0xc4, 0x43, 0x39, 0x86, 0x0b, 0x0f, 0xe5, 0x18, 0x4e, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e,
0xf1, 0xc1, 0x23, 0x39, 0x86, 0x17, 0x8f, 0xe4, 0x18, 0x16, 0x3c, 0x96, 0x63, 0x4c, 0x62, 0x03,
0x3b, 0xc2, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x35, 0x9c, 0x00, 0x78, 0xd4, 0x00, 0x00, 0x00,
}

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