Compare commits

...
22 changed files with 699 additions and 416 deletions
-2
View File
@@ -4,10 +4,8 @@ syncthing.exe
*.zip
*.asc
*.sublime*
discosrv
.jshintrc
coverage.out
files/pidx
discosrv.exe
bin
perfstats*.csv
+24 -4
View File
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -22,7 +22,6 @@ check() {
build() {
check
godep go build $* -ldflags "$ldflags" ./cmd/syncthing
godep go build $* -ldflags "$ldflags" ./cmd/discosrv
}
assets() {
@@ -104,8 +103,9 @@ xdr() {
transifex() {
pushd gui
go run ../cmd/transifexdl/main.go
go run ../cmd/transifexdl/main.go > valid-langs.js
popd
assets
}
case "$1" in
-1
View File
@@ -1 +0,0 @@
discosrv
-288
View File
@@ -1,288 +0,0 @@
// Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).
// All rights reserved. Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package main
import (
"encoding/binary"
"encoding/hex"
"flag"
"fmt"
"io"
"log"
"net"
"os"
"sync"
"time"
"github.com/calmh/syncthing/discover"
"github.com/calmh/syncthing/protocol"
"github.com/golang/groupcache/lru"
"github.com/juju/ratelimit"
)
type node struct {
addresses []address
updated time.Time
}
type address struct {
ip []byte
port uint16
}
var (
nodes = make(map[protocol.NodeID]node)
lock sync.Mutex
queries = 0
announces = 0
answered = 0
limited = 0
unknowns = 0
debug = false
lruSize = 1024
limitAvg = 1
limitBurst = 10
limiter *lru.Cache
)
func main() {
var listen string
var timestamp bool
var statsIntv int
var statsFile string
flag.StringVar(&listen, "listen", ":22026", "Listen address")
flag.BoolVar(&debug, "debug", false, "Enable debug output")
flag.BoolVar(&timestamp, "timestamp", true, "Timestamp the log output")
flag.IntVar(&statsIntv, "stats-intv", 0, "Statistics output interval (s)")
flag.StringVar(&statsFile, "stats-file", "/var/log/discosrv.stats", "Statistics file name")
flag.IntVar(&lruSize, "limit-cache", lruSize, "Limiter cache entries")
flag.IntVar(&limitAvg, "limit-avg", limitAvg, "Allowed average package rate, per 10 s")
flag.IntVar(&limitBurst, "limit-burst", limitBurst, "Allowed burst size, packets")
flag.Parse()
limiter = lru.New(lruSize)
log.SetOutput(os.Stdout)
if !timestamp {
log.SetFlags(0)
}
addr, _ := net.ResolveUDPAddr("udp", listen)
conn, err := net.ListenUDP("udp", addr)
if err != nil {
log.Fatal(err)
}
if statsIntv > 0 {
go logStats(statsFile, statsIntv)
}
var buf = make([]byte, 1024)
for {
buf = buf[:cap(buf)]
n, addr, err := conn.ReadFromUDP(buf)
if limit(addr) {
// Rate limit in effect for source
continue
}
if err != nil {
log.Fatal(err)
}
if n < 4 {
log.Printf("Received short packet (%d bytes)", n)
continue
}
buf = buf[:n]
magic := binary.BigEndian.Uint32(buf)
switch magic {
case discover.AnnouncementMagic:
handleAnnounceV2(addr, buf)
case discover.QueryMagic:
handleQueryV2(conn, addr, buf)
default:
lock.Lock()
unknowns++
lock.Unlock()
}
}
}
func limit(addr *net.UDPAddr) bool {
key := addr.IP.String()
lock.Lock()
defer lock.Unlock()
bkt, ok := limiter.Get(key)
if ok {
bkt := bkt.(*ratelimit.Bucket)
if bkt.TakeAvailable(1) != 1 {
// Rate limit exceeded; ignore packet
if debug {
log.Println("Rate limit exceeded for", key)
}
limited++
return true
}
} else {
if debug {
log.Println("New limiter for", key)
}
// One packet per ten seconds average rate, burst ten packets
limiter.Add(key, ratelimit.NewBucket(10*time.Second/time.Duration(limitAvg), int64(limitBurst)))
}
return false
}
func handleAnnounceV2(addr *net.UDPAddr, buf []byte) {
var pkt discover.Announce
err := pkt.UnmarshalXDR(buf)
if err != nil && err != io.EOF {
log.Println("AnnounceV2 Unmarshal:", err)
log.Println(hex.Dump(buf))
return
}
if debug {
log.Printf("<- %v %#v", addr, pkt)
}
lock.Lock()
announces++
lock.Unlock()
ip := addr.IP.To4()
if ip == nil {
ip = addr.IP.To16()
}
var addrs []address
for _, addr := range pkt.This.Addresses {
tip := addr.IP
if len(tip) == 0 {
tip = ip
}
addrs = append(addrs, address{
ip: tip,
port: addr.Port,
})
}
node := node{
addresses: addrs,
updated: time.Now(),
}
var id protocol.NodeID
if len(pkt.This.ID) == 32 {
// Raw node ID
copy(id[:], pkt.This.ID)
} else {
id.UnmarshalText(pkt.This.ID)
}
lock.Lock()
nodes[id] = node
lock.Unlock()
}
func handleQueryV2(conn *net.UDPConn, addr *net.UDPAddr, buf []byte) {
var pkt discover.Query
err := pkt.UnmarshalXDR(buf)
if err != nil {
log.Println("QueryV2 Unmarshal:", err)
log.Println(hex.Dump(buf))
return
}
if debug {
log.Printf("<- %v %#v", addr, pkt)
}
var id protocol.NodeID
if len(pkt.NodeID) == 32 {
// Raw node ID
copy(id[:], pkt.NodeID)
} else {
id.UnmarshalText(pkt.NodeID)
}
lock.Lock()
node, ok := nodes[id]
queries++
lock.Unlock()
if ok && len(node.addresses) > 0 {
ann := discover.Announce{
Magic: discover.AnnouncementMagic,
This: discover.Node{
ID: pkt.NodeID,
},
}
for _, addr := range node.addresses {
ann.This.Addresses = append(ann.This.Addresses, discover.Address{IP: addr.ip, Port: addr.port})
}
if debug {
log.Printf("-> %v %#v", addr, pkt)
}
tb := ann.MarshalXDR()
_, _, err = conn.WriteMsgUDP(tb, nil, addr)
if err != nil {
log.Println("QueryV2 response write:", err)
}
lock.Lock()
answered++
lock.Unlock()
}
}
func next(intv int) time.Time {
d := time.Duration(intv) * time.Second
t0 := time.Now()
t1 := t0.Add(d).Truncate(d)
time.Sleep(t1.Sub(t0))
return t1
}
func logStats(file string, intv int) {
f, err := os.OpenFile(file, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
log.Fatal(err)
}
for {
t := next(intv)
lock.Lock()
var deleted = 0
for id, node := range nodes {
if time.Since(node.updated) > 60*time.Minute {
delete(nodes, id)
deleted++
}
}
fmt.Fprintf(f, "%d Nr:%d Ne:%d Qt:%d Qa:%d A:%d U:%d Lq:%d Lc:%d\n",
t.Unix(), len(nodes), deleted, queries, answered, announces, unknowns, limited, limiter.Len())
f.Sync()
queries = 0
announces = 0
answered = 0
limited = 0
unknowns = 0
lock.Unlock()
}
}
+64 -16
View File
@@ -293,7 +293,9 @@ func main() {
rateBucket = ratelimit.NewBucketWithRate(float64(1000*cfg.Options.MaxSendKbps), int64(5*1000*cfg.Options.MaxSendKbps))
}
removeLegacyIndexes()
// If this is the first time the user runs v0.9, archive the old indexes and config.
archiveLegacyConfig()
db, err := leveldb.OpenFile(filepath.Join(confDir, "index"), nil)
if err != nil {
l.Fatalln("leveldb.OpenFile():", err)
@@ -492,7 +494,7 @@ func setupUPnP(r rand.Source) int {
if len(cfg.Options.ListenAddress) == 1 {
_, portStr, err := net.SplitHostPort(cfg.Options.ListenAddress[0])
if err != nil {
l.Warnln(err)
l.Warnln("Bad listen address:", err)
} else {
// Set up incoming port forwarding, if necessary and possible
port, _ := strconv.Atoi(portStr)
@@ -536,14 +538,39 @@ func resetRepositories() {
os.RemoveAll(idx)
}
func removeLegacyIndexes() {
func archiveLegacyConfig() {
pat := filepath.Join(confDir, "*.idx.gz*")
idxs, err := filepath.Glob(pat)
if err == nil {
for _, idx := range idxs {
l.Infof("Reset: Removing %s", idx)
os.Remove(idx)
if err == nil && len(idxs) > 0 {
// There are legacy indexes. This is probably the first time we run as v0.9.
backupDir := filepath.Join(confDir, "backup-of-v0.8")
err = os.MkdirAll(backupDir, 0700)
if err != nil {
l.Warnln("Cannot archive config/indexes:", err)
return
}
for _, idx := range idxs {
l.Infof("Archiving %s", filepath.Base(idx))
os.Rename(idx, filepath.Join(backupDir, filepath.Base(idx)))
}
src, err := os.Open(filepath.Join(confDir, "config.xml"))
if err != nil {
l.Warnf("Cannot archive config:", err)
return
}
defer src.Close()
dst, err := os.Create(filepath.Join(backupDir, "config.xml"))
if err != nil {
l.Warnf("Cannot archive config:", err)
return
}
defer src.Close()
l.Infoln("Archiving config.xml")
io.Copy(dst, src)
}
}
@@ -562,7 +589,7 @@ func restart() {
}
pgm, err := exec.LookPath(os.Args[0])
if err != nil {
l.Warnln(err)
l.Warnln("Cannot restart:", err)
return
}
proc, err := os.StartProcess(pgm, os.Args, &os.ProcAttr{
@@ -586,26 +613,26 @@ func saveConfigLoop(cfgFile string) {
for _ = range saveConfigCh {
fd, err := os.Create(cfgFile + ".tmp")
if err != nil {
l.Warnln(err)
l.Warnln("Saving config:", err)
continue
}
err = config.Save(fd, cfg)
if err != nil {
l.Warnln(err)
l.Warnln("Saving config:", err)
fd.Close()
continue
}
err = fd.Close()
if err != nil {
l.Warnln(err)
l.Warnln("Saving config:", err)
continue
}
err = osutil.Rename(cfgFile+".tmp", cfgFile)
if err != nil {
l.Warnln(err)
l.Warnln("Saving config:", err)
}
}
}
@@ -633,7 +660,8 @@ next:
conn.Close()
continue
}
remoteID := protocol.NewNodeID(certs[0].Raw)
remoteCert := certs[0]
remoteID := protocol.NewNodeID(remoteCert.Raw)
if remoteID == myID {
l.Infof("Connected to myself (%s) - should not happen", remoteID)
@@ -649,10 +677,30 @@ next:
for _, nodeCfg := range cfg.Nodes {
if nodeCfg.NodeID == remoteID {
// Verify the name on the certificate. By default we set it to
// "syncthing" when generating, but the user may have replaced
// the certificate and used another name.
certName := nodeCfg.CertName
if certName == "" {
certName = "syncthing"
}
err := remoteCert.VerifyHostname(certName)
if err != nil {
// Incorrect certificate name is something the user most
// likely wants to know about, since it's an advanced
// config. Warn instead of Info.
l.Warnf("Bad certificate from %s (%v): %v", remoteID, conn.RemoteAddr(), err)
conn.Close()
continue next
}
// If rate limiting is set, we wrap the write side of the
// connection in a limiter.
var wr io.Writer = conn
if rateBucket != nil {
wr = &limitedWriter{conn, rateBucket}
}
name := fmt.Sprintf("%s-%s", conn.LocalAddr(), conn.RemoteAddr())
protoConn := protocol.NewConnection(remoteID, conn, wr, m, name, nodeCfg.Compression)
@@ -688,7 +736,7 @@ func listenTLS(conns chan *tls.Conn, addr string, tlsCfg *tls.Config) {
for {
conn, err := listener.Accept()
if err != nil {
l.Warnln(err)
l.Warnln("Accepting connection:", err)
continue
}
@@ -702,7 +750,7 @@ func listenTLS(conns chan *tls.Conn, addr string, tlsCfg *tls.Config) {
tc := tls.Server(conn, tlsCfg)
err = tc.Handshake()
if err != nil {
l.Warnln(err)
l.Infoln("TLS handshake:", err)
tc.Close()
continue
}
@@ -774,7 +822,7 @@ func dialTLS(m *model.Model, conns chan *tls.Conn, tlsCfg *tls.Config) {
tc := tls.Client(conn, tlsCfg)
err = tc.Handshake()
if err != nil {
l.Warnln(err)
l.Infoln("TLS handshake:", err)
tc.Close()
continue
}
+103 -7
View File
@@ -1,7 +1,6 @@
package main
import (
"bytes"
"strconv"
"strings"
)
@@ -17,17 +16,114 @@ type githubAsset struct {
Name string `json:"name"`
}
// Returns 1 if a>b, -1 if a<b and 0 if they are equal
func compareVersions(a, b string) int {
return bytes.Compare(versionParts(a), versionParts(b))
arel, apre := versionParts(a)
brel, bpre := versionParts(b)
minlen := len(arel)
if l := len(brel); l < minlen {
minlen = l
}
// First compare major-minor-patch versions
for i := 0; i < minlen; i++ {
if arel[i] < brel[i] {
return -1
}
if arel[i] > brel[i] {
return 1
}
}
// Longer version is newer, when the preceding parts are equal
if len(arel) < len(brel) {
return -1
}
if len(arel) > len(brel) {
return 1
}
// Prerelease versions are older, if the versions are the same
if len(apre) == 0 && len(bpre) > 0 {
return 1
}
if len(apre) > 0 && len(bpre) == 0 {
return -1
}
minlen = len(apre)
if l := len(bpre); l < minlen {
minlen = l
}
// Compare prerelease strings
for i := 0; i < minlen; i++ {
switch av := apre[i].(type) {
case int:
switch bv := bpre[i].(type) {
case int:
if av < bv {
return -1
}
if av > bv {
return 1
}
case string:
return -1
}
case string:
switch bv := bpre[i].(type) {
case int:
return 1
case string:
if av < bv {
return -1
}
if av > bv {
return 1
}
}
}
}
// If all else is equal, longer prerelease string is newer
if len(apre) < len(bpre) {
return -1
}
if len(apre) > len(bpre) {
return 1
}
// Looks like they're actually the same
return 0
}
func versionParts(v string) []byte {
parts := strings.Split(v, "-")
// Split a version into parts.
// "1.2.3-beta.2" -> []int{1, 2, 3}, []interface{}{"beta", 2}
func versionParts(v string) ([]int, []interface{}) {
parts := strings.SplitN(v, "-", 2)
fields := strings.Split(parts[0], ".")
res := make([]byte, len(fields))
release := make([]int, len(fields))
for i, s := range fields {
v, _ := strconv.Atoi(s)
res[i] = byte(v)
release[i] = v
}
return res
var prerelease []interface{}
if len(parts) > 1 {
fields = strings.Split(parts[1], ".")
prerelease = make([]interface{}, len(fields))
for i, s := range fields {
v, err := strconv.Atoi(s)
if err == nil {
prerelease[i] = v
} else {
prerelease[i] = s
}
}
}
return release, prerelease
}
+8
View File
@@ -20,6 +20,14 @@ var testcases = []struct {
{"0.1.10", "0.1.9", 1},
{"0.10.0", "0.2.0", 1},
{"30.10.0", "4.9.0", 1},
{"0.9.0-beta7", "0.9.0-beta6", 1},
{"1.0.0-alpha", "1.0.0-alpha.1", -1},
{"1.0.0-alpha.1", "1.0.0-alpha.beta", -1},
{"1.0.0-alpha.beta", "1.0.0-beta", -1},
{"1.0.0-beta", "1.0.0-beta.2", -1},
{"1.0.0-beta.2", "1.0.0-beta.11", -1},
{"1.0.0-beta.11", "1.0.0-rc.1", -1},
{"1.0.0-rc.1", "1.0.0", -1},
}
func TestCompareVersions(t *testing.T) {
+2
View File
@@ -2,6 +2,7 @@ package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"
@@ -66,6 +67,7 @@ func main() {
}
sort.Strings(langs)
fmt.Print("var validLangs = ")
json.NewEncoder(os.Stdout).Encode(langs)
}
+1
View File
@@ -97,6 +97,7 @@ type NodeConfiguration struct {
Name string `xml:"name,attr,omitempty"`
Addresses []string `xml:"address,omitempty"`
Compression bool `xml:"compression,attr"`
CertName string `xml:"certName,attr,omitempty"`
}
type OptionsConfiguration struct {
-1
View File
@@ -9,7 +9,6 @@
var syncthing = angular.module('syncthing', ['pascalprecht.translate']);
var urlbase = 'rest';
var validLangs = ["de","en","es","fr","pt","sv"];
syncthing.config(function ($httpProvider, $translateProvider) {
$httpProvider.defaults.xsrfHeaderName = 'X-CSRF-Token';
+1
View File
@@ -752,6 +752,7 @@
<script src="angular-translate-loader.min.js"></script>
<script src="jquery-2.0.3.min.js"></script>
<script src="bootstrap/js/bootstrap.min.js"></script>
<script src="valid-langs.js"></script>
<script src="app.js"></script>
</body>
</html>
+1 -1
View File
@@ -40,7 +40,7 @@
"Local Discovery": "Lokale Auffindung",
"Local Discovery Port": "Lokaler Aufindungsport",
"Local Repository": "Lokales Verzeichnis",
"Master Repo": "Veränderungen zugelassen",
"Master Repo": "Keine Veränderungen zugelassen",
"Max File Change Rate (KiB/s)": "Maximale Datenänderungsrate (KiB/s)",
"Max Outstanding Requests": "Max. ausstehende Anfragen",
"No": "Nein",
+112
View File
@@ -0,0 +1,112 @@
{
"API Key": "Κλειδί API",
"About": "Σχετικά",
"Add Node": "Πρόσθεσε Κόμβο",
"Add Repository": "Πρόσθεσε Αποθετήριο",
"Address": "Διεύθυνση",
"Addresses": "Διευθύνσεις",
"Allow Anonymous Usage Reporting?": "Να επιτρέπεται Ανώνυμη Αποστολή Αναφοράς Χρήσης?",
"Announce Server": "Διακομιστής Αναγγελίας",
"Anonymous Usage Reporting": "Ανώνυμη Αναφορά Χρήσης",
"Bugs": "Bugs",
"CPU Utilization": "Χρήση CPU",
"Close": "Τέλος",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Copyright © 2014 Jakob Borg και οι παρακάτω Συνεισφορείς:",
"Delete": "Διαγραφή",
"Disconnected": "Αποσυνδεδεμένος",
"Documentation": "Τεκμηρίωση",
"Download Rate": "Download Rate",
"Edit": "Επεξεργασία",
"Edit Node": "Επεξεργασία Κόμβου",
"Edit Repository": "Επεξεργασία Αποθετηρίου",
"Enable UPnP": "Ενεργοποίηση UPnP",
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Εισήγαγε διαχωρισμένα με κόμμα \"ip:port\" διευθύνσεις ή \"dynamic\", για να πραγματοποιηθεί η αυτόματη εξεύρεση διευθύνσεων.",
"Error": "Σφάλμα",
"File Versioning": "File Versioning",
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "Τα permission bits των αρχείων αγνοούνται όταν γίνεται αναζήτηση για αλλαγές. Χρήση σε FAT filesystems.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.": "Όταν τα αρχεία αντικατασταθούν ή διαγραφούν από το syncthing, μεταφέρονται σε φάκελο .stversions με χρονική σήμανση.",
"Files are protected from changes made on other nodes, but changes made on this node will be sent to the rest of the cluster.": "Τα αρχεία προστατεύονται από αλλαγές που γίνονται σε άλλους κόμβους, αλλά όποιες αλλαγές γίνουν εδώ θα αποσταλούν στο όλο το cluster.",
"Folder": "Κατάλογος",
"GUI Authentication Password": "GUI Authentication Password",
"GUI Authentication User": "GUI Authentication User",
"GUI Listen Addresses": "GUI Listen Addresses",
"Generate": "Δημιουργία",
"Global Discovery": "Global Discovery",
"Global Repository": "Global Repository",
"Idle": "Ανενεργός",
"Ignore Permissions": "Ignore Permissions",
"Keep Versions": "Keep Versions",
"Latest Release": "Τελευταία Έκδοση",
"Local Discovery": "Local Discovery",
"Local Discovery Port": "Local Discovery Port",
"Local Repository": "Τοπικό Αποθετήριο",
"Master Repo": "Master Repo",
"Max File Change Rate (KiB/s)": "Max File Change Rate (KiB/s)",
"Max Outstanding Requests": "Max Outstanding Requests",
"No": "Αριθμός",
"Node ID": "ID Κόμβου",
"Node Name": "Όνομα Κόμβου",
"Notice": "Notice",
"OK": "OK",
"Offline": "Ανεργός",
"Online": "Ενεργός",
"Out Of Sync": "Εκτός Συγχρονισμού",
"Outgoing Rate Limit (KiB/s)": "Outgoing Rate Limit (KiB/s)",
"Override Changes": "Override Changes",
"Path to the repository on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Μονοπάτι του αποθετηρίου στον τοπικό υπολογιστή. Σε περίπτωση που δεν υπάρχει, θα δημιουργηθεί. Ο χαρακτήρας tilde (~) μπορεί να χρησιμοποιηθεί σαν συντόμευση για",
"Please wait": "Παρακαλώ περιμένετε",
"Preview Usage Report": "Preview Usage Report",
"RAM Utilization": "Χρήση RAM",
"Reconnect Interval (s)": "Reconnect Interval (s)",
"Repository ID": "ID Αποθετηρίου",
"Repository Master": "Repository Master",
"Repository Path": "Μονοπάτι Αποθετηρίου",
"Rescan Interval (s)": "Rescan Interval (s)",
"Restart": "Επανεκκίνηση",
"Restart Needed": "Απαιτείται Επανεκκίνηση",
"Save": "Αποθήκευση",
"Scanning": "Scanning",
"Select the nodes to share this repository with.": "Επιλογή των κόμβων με τους οποίους θα διαμοιραστεί αυτό το αποθετήριο.",
"Settings": "Ρυθμίσεις",
"Share With Nodes": "Διαμοιρασμός με Κόμβους",
"Shared With": "Διαμοιρασμός με",
"Short identifier for the repository. Must be the same on all cluster nodes.": "Σύντομη περιγραφή του αποθετηρίου. Θα πρέπει να είναι το ίδιο σε όλους τους κόμβους του cluster.",
"Show ID": "Εμφάνιση ID",
"Shown instead of Node ID in the cluster status.": "Εμφάνιση στη θέση του ID Αποθετηρίου, στην κατάσταση του cluster.",
"Shutdown": "Απενεργοποίηση",
"Source Code": "Πηγαίος Κώδικας",
"Start Browser": "Start Browser",
"Stopped": "Stopped",
"Support / Forum": "Υποστήριξη / Forum",
"Sync Protocol Listen Addresses": "Sync Protocol Listen Addresses",
"Synchronization": "Συγχρονισμός",
"Syncing": "Syncing",
"Syncthing has been shut down.": "Syncthing έχει απενεργοποιηθεί.",
"Syncthing includes the following software or portions thereof:": "Syncthing includes the following software or portions thereof:",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…",
"The aggregated statistics are publicly available at {{url}}": "The aggregated statistics are publicly available at {{url}}",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.",
"The encrypted usage report is sent daily. It is used to track common platforms, repo sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "The encrypted usage report is sent daily. It is used to track common platforms, repo sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.",
"The entered node ID does not look valid. It should be a 52 character string consisting of letters and numbers, with spaces and dashes being optional.": "The entered node ID does not look valid. It should be a 52 character string consisting of letters and numbers, with spaces and dashes being optional.",
"The node ID cannot be blank.": "The node ID cannot be blank.",
"The node ID to enter here can be found in the \"Edit > Show ID\" dialog on the other node. Spaces and dashes are optional (ignored).": "Το ID Κόμβου μπορείτε να βρείτε στο μενού \"Επεξεργασία > Εμφάνιση ID\" του άλλου κόμβου. Κενά και παύλες είναι προαιρετικά (αγνοούνται).",
"The number of old versions to keep, per file.": "The number of old versions to keep, per file.",
"The number of versions must be a number and cannot be blank.": "The number of versions must be a number and cannot be blank.",
"The repository ID cannot be blank.": "The repository ID cannot be blank.",
"The repository ID must be a short identifier (64 characters or less) consisting of letters, numbers and the the dot (.), dash (-) and underscode (_) characters only.": "The repository ID must be a short identifier (64 characters or less) consisting of letters, numbers and the the dot (.), dash (-) and underscode (_) characters only.",
"The repository ID must be unique.": "The repository ID must be unique.",
"The repository path cannot be blank.": "The repository path cannot be blank.",
"Unknown": "Άγνωστο",
"Up to Date": "Up to Date",
"Upgrade To {%version%}": "Αναβάθμιση στην έκδοση {{version}}",
"Upload Rate": "Upload Rate",
"Usage": "Usage",
"Use Compression": "Χρήση συμπίεσης",
"Use HTTPS for GUI": "Use HTTPS for GUI",
"Version": "Έκδοση",
"When adding a new node, keep in mind that this node must be added on the other side too.": "When adding a new node, keep in mind that this node must be added on the other side too.",
"When adding a new repository, keep in mind that the Repository ID is used to tie repositories together between nodes. They are case sensitive and must match exactly between all nodes.": "Κατά την πρόσθεση νέου αποθετηρίου, να γνωρίζεται πως το ID Αποθετηρίου χρησιμοποιείται για να συνδέει Αποθετήρια μεταξύ κόμβων. Τα ID είναι case sensitive και θα πρέπει να είναι ταυτόσημα μεταξύ όλων των κόμβων.",
"Yes": "Ναι",
"You must keep at least one version.": "You must keep at least one version.",
"items": "items"
}
+2 -2
View File
@@ -15,7 +15,7 @@
"Delete": "Supprimer",
"Disconnected": "Déconnecté",
"Documentation": "Documentation",
"Download Rate": "Débit Download",
"Download Rate": "Débit de téléchargement",
"Edit": "Éditer",
"Edit Node": "Éditer le nœud",
"Edit Repository": "Éditer le répertoire",
@@ -99,7 +99,7 @@
"Unknown": "Inconnu",
"Up to Date": "Synchronistation à jour",
"Upgrade To {%version%}": "Upgrader vers {{version}}",
"Upload Rate": "Débit Upload",
"Upload Rate": "Débit d'envoi",
"Usage": "Utilisation",
"Use Compression": "Utiliser la compression",
"Use HTTPS for GUI": "Utiliser l'HTTPS pour le GUI",
+112
View File
@@ -0,0 +1,112 @@
{
"API Key": "Chiave API",
"About": "Informazioni",
"Add Node": "Aggiungi Nodo",
"Add Repository": "Aggiungi Deposito",
"Address": "Indirizzo",
"Addresses": "Indirizzi",
"Allow Anonymous Usage Reporting?": "Abilitare Statistiche Anonime di Utilizzo?",
"Announce Server": "Server di Ricerca Globale dei Nodi",
"Anonymous Usage Reporting": "Statistiche Anonime di Utilizzo",
"Bugs": "Bug",
"CPU Utilization": "Utilizzo della CPU",
"Close": "Chiudi",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Copyright © 2014 Jakob Borg e i seguenti Collaboratori:",
"Delete": "Elimina",
"Disconnected": "Disconnesso",
"Documentation": "Documentazione",
"Download Rate": "Velocità Download",
"Edit": "Modifica",
"Edit Node": "Modifica Nodo",
"Edit Repository": "Modifica Deposito",
"Enable UPnP": "Attiva UPnP",
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Inserisci gli indirizzi \"ip:porta\" separati da una virgola, altrimenti inserisci \"dynamic\" per effettuare il rilevamento automatico dell'indirizzo.",
"Error": "Errore",
"File Versioning": "Controllo Versione dei File",
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "Il software evita i bit dei permessi dei file durante il controllo delle modifiche. Utilizzato nei filesystem FAT.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.": "I file sostituiti o eliminati da syncthing vengono datati e spostati in una cartella .stversions .",
"Files are protected from changes made on other nodes, but changes made on this node will be sent to the rest of the cluster.": "I file sono protetti dalle modifiche effettuate negli altri nodi, ma le modifiche effettuate in questo nodo verranno inviate anche al resto del cluster.",
"Folder": "Cartella",
"GUI Authentication Password": "Password di Autenticazione dell'Utente",
"GUI Authentication User": "Utente dell'Interfaccia Grafica",
"GUI Listen Addresses": "Indirizzi dell'Interfaccia Grafica",
"Generate": "Genera",
"Global Discovery": "Individuazione Globale",
"Global Repository": "Deposito Globale",
"Idle": "Inattivo",
"Ignore Permissions": "Ignora Permessi",
"Keep Versions": "Mantieni le Versioni",
"Latest Release": "Ultimo Rilascio",
"Local Discovery": "Individuazione Locale",
"Local Discovery Port": "Porta di Individuazione Locale",
"Local Repository": "Deposito Locale",
"Master Repo": "Deposito Principale",
"Max File Change Rate (KiB/s)": "Tasso Massimo di Cambiamento dei File (KiB/s)",
"Max Outstanding Requests": "Numero Massimo di Richieste Simultanee per i Blocchi di File",
"No": "No",
"Node ID": "ID Nodo",
"Node Name": "Nome Nodo",
"Notice": "Avviso",
"OK": "OK",
"Offline": "Non in linea",
"Online": "In linea",
"Out Of Sync": "Non Sincronizzati",
"Outgoing Rate Limit (KiB/s)": "Limite di Velocità in Uscita (KiB/s)",
"Override Changes": "Ignora Modifiche",
"Path to the repository on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Percorso del deposito nel computer locale. Verrà creato se non esiste già. Il carattere tilde (~) può essere utilizzato come scorciatoia per",
"Please wait": "Attendere prego",
"Preview Usage Report": "Anteprima Statistiche di Utilizzo",
"RAM Utilization": "Utilizzo della RAM",
"Reconnect Interval (s)": "Intervallo di Riconnessione (s)",
"Repository ID": "ID Deposito",
"Repository Master": "Deposito Principale",
"Repository Path": "Percorso del Deposito",
"Rescan Interval (s)": "Intervallo di Scansione dei File (s)",
"Restart": "Riavvia",
"Restart Needed": "Riavvio Necessario",
"Save": "Salva",
"Scanning": "Scansione in corso",
"Select the nodes to share this repository with.": "Seleziona i nodi con i quali vuoi condividere questo deposito.",
"Settings": "Impostazioni",
"Share With Nodes": "Condividi Con i Nodi",
"Shared With": "Condiviso Con",
"Short identifier for the repository. Must be the same on all cluster nodes.": "Breve identificatore del deposito. Deve essere lo stesso su tutti i nodi del cluster.",
"Show ID": "Mostra ID",
"Shown instead of Node ID in the cluster status.": "Visibile al posto dell'ID Nodo nello stato del cluster.",
"Shutdown": "Arresta",
"Source Code": "Codice Sorgente",
"Start Browser": "Avvia Browser",
"Stopped": "Fermato",
"Support / Forum": "Supporto / Forum",
"Sync Protocol Listen Addresses": "Indirizzi del Protocollo di Sincronizzazione",
"Synchronization": "Sincronizzazione",
"Syncing": "Sincronizzazione in corso",
"Syncthing has been shut down.": "Syncthing è stato arrestato.",
"Syncthing includes the following software or portions thereof:": "Syncthing include i seguenti software o porzioni di questi:",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing sembra inattivo, oppure c'è un problema con la tua connessione a Internet. Nuovo tentativo…",
"The aggregated statistics are publicly available at {{url}}": "Le statistiche aggregate sono disponibili pubblicamente su {{url}}",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "La configurazione è stata salvata ma non attivata. Devi riavviare Syncthing per attivare la nuova configurazione.",
"The encrypted usage report is sent daily. It is used to track common platforms, repo sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Quotidianamente il software invia le statistiche di utilizzo in forma criptata. Questi dati riguardano i sistemi operativi utilizzati, le dimensioni dei depositi e le versioni del software. Se i dati riportati cambiano verrà mostrata di nuovo questa finestra di dialogo.",
"The entered node ID does not look valid. It should be a 52 character string consisting of letters and numbers, with spaces and dashes being optional.": "L'ID del nodo inserito non sembra valido. Dovrebbe essere una stringa di 52 caratteri costituita da lettere e numeri, con spazi e trattini opzionali.",
"The node ID cannot be blank.": "L'ID del nodo non può essere vuoto.",
"The node ID to enter here can be found in the \"Edit > Show ID\" dialog on the other node. Spaces and dashes are optional (ignored).": "Trova l'ID del nodo nella finestra di dialogo \"Modifica > Mostra ID\" dell'altro nodo e poi inseriscilo qui. Gli spazi e i trattini sono opzionali (ignorati).",
"The number of old versions to keep, per file.": "Il numero di vecchie versioni da mantenere, per file.",
"The number of versions must be a number and cannot be blank.": "Il numero di versioni dev'essere un numero e non può essere vuoto.",
"The repository ID cannot be blank.": "L'ID del deposito non può essere vuoto.",
"The repository ID must be a short identifier (64 characters or less) consisting of letters, numbers and the the dot (.), dash (-) and underscode (_) characters only.": "L'ID del deposito dev'essere un identificatore breve (64 caratteri o meno) costituito solamente da lettere, numeri, punti (.), trattini (-) e trattini bassi (_).",
"The repository ID must be unique.": "L'ID del deposito dev'essere unico.",
"The repository path cannot be blank.": "Il percorso del deposito non può essere vuoto.",
"Unknown": "Sconosciuto",
"Up to Date": "Sincronizzato",
"Upgrade To {%version%}": "Aggiorna Alla {{version}}",
"Upload Rate": "Velocità Upload",
"Usage": "Utilizzo",
"Use Compression": "Utilizza Compressione",
"Use HTTPS for GUI": "Utilizza HTTPS per l'interfaccia grafica",
"Version": "Versione",
"When adding a new node, keep in mind that this node must be added on the other side too.": "Quando aggiungi un nuovo nodo, ricordati di aggiungerlo anche dall'altro lato.",
"When adding a new repository, keep in mind that the Repository ID is used to tie repositories together between nodes. They are case sensitive and must match exactly between all nodes.": "Quando aggiungi un nuovo deposito ricordati che gli ID vengono utilizzati per collegare i depositi nei nodi. Distinguono maiuscole e minuscole e devono corrispondere esattamente su tutti i nodi.",
"Yes": "Sì",
"You must keep at least one version.": "È necessario mantenere almeno una versione.",
"items": "elementi"
}
+112
View File
@@ -0,0 +1,112 @@
{
"API Key": "Ключ API",
"About": "О программе",
"Add Node": "Добавить Узел",
"Add Repository": "Добавить репозиторий",
"Address": "Адрес",
"Addresses": "Адреса",
"Allow Anonymous Usage Reporting?": "Разрешить сбор анонимной статистики использования?",
"Announce Server": "Сервер публикации",
"Anonymous Usage Reporting": "Анонимная статистика использования",
"Bugs": "Ошибки",
"CPU Utilization": "Загрузка ЦПУ",
"Close": "Закрыть",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Все права защищены © 2014 Jakob Borg и следующие участники:",
"Delete": "Удалить",
"Disconnected": "Нет соединения",
"Documentation": "Документация",
"Download Rate": "Скорость загрузки",
"Edit": "Изменить",
"Edit Node": "Изменить Узел",
"Edit Repository": "Изменить Репозиторий",
"Enable UPnP": "Включить UPnP",
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": " Введите пары \"IP:PORT\" разделённые запятыми, или слово \"dynamic\" для автоматического обнаружения адреса.",
"Error": "Ошибка",
"File Versioning": "Управление версиями",
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "Права доступа к файлам будут игнорироваться. Используйте на файловых системах типа FAT.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.": "Версии файлов с временнОй меткой перемещаются в директорию .stversions, если они удалены или перемещены в процессе синхронизации.",
"Files are protected from changes made on other nodes, but changes made on this node will be sent to the rest of the cluster.": "Файлы защищены от изменений сторонними узлами, но изменения внесённые текущим узлом будут синхронизированы со всем кластером.",
"Folder": "Папка",
"GUI Authentication Password": "Пароль для доступа к панели управления",
"GUI Authentication User": "Имя пользователя для доступа к панели управления",
"GUI Listen Addresses": "Адрес панели управления",
"Generate": "Сгенерировать",
"Global Discovery": "Глобальное обнаружение",
"Global Repository": "Глобальный репозиторий",
"Idle": "Бездействует",
"Ignore Permissions": "Игнорировать файловые права доступа",
"Keep Versions": "Количество хранимых версий",
"Latest Release": "Последняя версия",
"Local Discovery": "Локальное обнаружение",
"Local Discovery Port": "Порт локального обнаружения",
"Local Repository": "Локальный репозиторий",
"Master Repo": "Главный репозиторий",
"Max File Change Rate (KiB/s)": "Максимальная скорость изменения файлов (KiB/s)",
"Max Outstanding Requests": "Максимальное количество исходящих запросов",
"No": "Нет",
"Node ID": "ID Узла",
"Node Name": "Имя Узла",
"Notice": "Внимание",
"OK": "ОК",
"Offline": "Оффлайн",
"Online": "Онлайн",
"Out Of Sync": "Не синхронизировано",
"Outgoing Rate Limit (KiB/s)": "Предел скорости отдачи (KiB/s)",
"Override Changes": "Перезаписать изменения",
"Path to the repository on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Путь к репозиторию на локальном компьютере. Если не существует, то будет создан. Знак тильды (~) может использоваться как ссылка",
"Please wait": "Пожалуйста, подождите",
"Preview Usage Report": "Посмотреть отчёт об использовании",
"RAM Utilization": "Использование ОЗУ",
"Reconnect Interval (s)": "Интервал переподключений (секунды)",
"Repository ID": "ID Репозитория",
"Repository Master": "Главный Репозиторий",
"Repository Path": "Путь к Репозиторию",
"Rescan Interval (s)": "Интервал между сканированием (сек)",
"Restart": "Перезапуск",
"Restart Needed": "Требуется перезапуск",
"Save": "Сохранить",
"Scanning": "Сканирование",
"Select the nodes to share this repository with.": "Выберите узлы для которых будет доступен данный репозиторий.",
"Settings": "Настройки",
"Share With Nodes": "Предоставить доступ узлам",
"Shared With": "Доступ предоставлен",
"Short identifier for the repository. Must be the same on all cluster nodes.": "Короткий идентификатор репозитория. Должен быть одинаковый на всех узлах кластера.",
"Show ID": "Показать ID",
"Shown instead of Node ID in the cluster status.": "Отображается вместо ID узла в статусе кластера.",
"Shutdown": "Выключить",
"Source Code": "Исходный код",
"Start Browser": "Открыть браузер",
"Stopped": "Остановлено",
"Support / Forum": "Поддержка / Форум",
"Sync Protocol Listen Addresses": "Адрес протокола синхронизации",
"Synchronization": "Синхронизация",
"Syncing": "Синхронизация",
"Syncthing has been shut down.": "Syncthing выключен.",
"Syncthing includes the following software or portions thereof:": "Syncthing включает в себя следующее ПО или его части:",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Кажется, Syncthing не запущен или есть проблемы с подключением к Интернету. Переподключаюсь...",
"The aggregated statistics are publicly available at {{url}}": "Суммарная статистика общедоступна на {{url}}",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Конфигурация была сохранена но не активирована. Для активации новой конфигурации необходимо рестартовать Syncthing.",
"The encrypted usage report is sent daily. It is used to track common platforms, repo sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Зашифрованные отчёты об использовании отправляются ежедневно. Они используются для отслеживания проблем, размеров репозиториев и версий Syncthing. Если набор данных в отчёте будет изменён, то вы получите уведомление об этом в этом диалоге.",
"The entered node ID does not look valid. It should be a 52 character string consisting of letters and numbers, with spaces and dashes being optional.": "Введённый ID узла выглядит неправильно: ID должен быть строкой, длинной 52 символа, обязательно содержащей группы букв и цифр которые могут быть разделены пробелами или тире.",
"The node ID cannot be blank.": "ID узла не может быть пустым.",
"The node ID to enter here can be found in the \"Edit > Show ID\" dialog on the other node. Spaces and dashes are optional (ignored).": "ID узла можно узнать в окне \"Редактировать > Показать ID\" на требуемом узле. Пробелы и тире используются для удобства и не обязательны (игнорируются).",
"The number of old versions to keep, per file.": "Количество хранимых версий файла.",
"The number of versions must be a number and cannot be blank.": "Количество версий должно быть числом и не может быть пустым.",
"The repository ID cannot be blank.": "ID Репозитория не может быть пустым.",
"The repository ID must be a short identifier (64 characters or less) consisting of letters, numbers and the the dot (.), dash (-) and underscode (_) characters only.": "ID репозитория должен быть коротким id (64 символа или меньше) состоящий только из букв, цифр, точек (.), тире (-) и нижнего подчёркивания (_).",
"The repository ID must be unique.": "ID Репозитория должен быть уникальным.",
"The repository path cannot be blank.": "Путь к репозиторию не может быть пустым.",
"Unknown": "Неизвестно",
"Up to Date": "Обновлено",
"Upgrade To {%version%}": "Обновить до {{version}}",
"Upload Rate": "Скорость отдачи",
"Usage": "Справка",
"Use Compression": "Использовать сжатие",
"Use HTTPS for GUI": "Использовать HTTPS для панели управления",
"Version": "Версия",
"When adding a new node, keep in mind that this node must be added on the other side too.": "Добавляя новый узел помните, что новый узел должен быть добавлен и на другой стороне.",
"When adding a new repository, keep in mind that the Repository ID is used to tie repositories together between nodes. They are case sensitive and must match exactly between all nodes.": "Добавляя новый репозиторий помните, что ID репозитория используется для связи всех репозиториев между узлами. ID чувствительны к регистру и должны совпадать на всех узлах.",
"Yes": "Да",
"You must keep at least one version.": "Вы должны хранить как минимум одну версию.",
"items": "элементы"
}
+1
View File
@@ -0,0 +1 @@
var validLangs = ["de","el","en","es","fr","it","pt","ru","sv"]
+63 -60
View File
@@ -563,83 +563,86 @@ func (m *Model) AddConnection(rawConn io.Closer, protoConn protocol.Connection)
func sendIndexes(conn protocol.Connection, repo string, fs *files.Set) {
nodeID := conn.ID()
name := conn.Name()
var err error
if debug {
l.Debugf("sendIndexes for %s-%s@/%q starting", nodeID, name, repo)
}
initial := true
minLocalVer := uint64(0)
var err error
defer func() {
if debug {
l.Debugf("sendIndexes for %s-%s@/%q exiting: %v", nodeID, name, repo, err)
}
}()
minLocalVer, err := sendIndexTo(true, 0, conn, repo, fs)
for err == nil {
if !initial {
time.Sleep(5 * time.Second)
if fs.LocalVersion(protocol.LocalNodeID) <= minLocalVer {
continue
}
time.Sleep(5 * time.Second)
if fs.LocalVersion(protocol.LocalNodeID) <= minLocalVer {
continue
}
batch := make([]protocol.FileInfo, 0, indexBatchSize)
maxLocalVer := uint64(0)
fs.WithHave(protocol.LocalNodeID, func(f protocol.FileInfo) bool {
if f.LocalVersion <= minLocalVer {
return true
}
if f.LocalVersion > maxLocalVer {
maxLocalVer = f.LocalVersion
}
if len(batch) == indexBatchSize {
if initial {
if err = conn.Index(repo, batch); err != nil {
return false
}
if debug {
l.Debugf("sendIndexes for %s-%s/%q: %d files (initial index)", nodeID, name, repo, len(batch))
}
initial = false
} else {
if err = conn.IndexUpdate(repo, batch); err != nil {
return false
}
if debug {
l.Debugf("sendIndexes for %s-%s/%q: %d files (batched update)", nodeID, name, repo, len(batch))
}
}
batch = make([]protocol.FileInfo, 0, indexBatchSize)
}
batch = append(batch, f)
return true
})
if initial {
err = conn.Index(repo, batch)
if debug && err == nil {
l.Debugf("sendIndexes for %s-%s/%q: %d files (small initial index)", nodeID, name, repo, len(batch))
}
initial = false
} else if len(batch) > 0 {
err = conn.IndexUpdate(repo, batch)
if debug && err == nil {
l.Debugf("sendIndexes for %s-%s/%q: %d files (last batch)", nodeID, name, repo, len(batch))
}
}
minLocalVer = maxLocalVer
minLocalVer, err = sendIndexTo(false, minLocalVer, conn, repo, fs)
}
}
func sendIndexTo(initial bool, minLocalVer uint64, conn protocol.Connection, repo string, fs *files.Set) (uint64, error) {
nodeID := conn.ID()
name := conn.Name()
batch := make([]protocol.FileInfo, 0, indexBatchSize)
maxLocalVer := uint64(0)
var err error
fs.WithHave(protocol.LocalNodeID, func(f protocol.FileInfo) bool {
if f.LocalVersion <= minLocalVer {
return true
}
if f.LocalVersion > maxLocalVer {
maxLocalVer = f.LocalVersion
}
if len(batch) == indexBatchSize {
if initial {
if err = conn.Index(repo, batch); err != nil {
return false
}
if debug {
l.Debugf("sendIndexes for %s-%s/%q: %d files (initial index)", nodeID, name, repo, len(batch))
}
initial = false
} else {
if err = conn.IndexUpdate(repo, batch); err != nil {
return false
}
if debug {
l.Debugf("sendIndexes for %s-%s/%q: %d files (batched update)", nodeID, name, repo, len(batch))
}
}
batch = make([]protocol.FileInfo, 0, indexBatchSize)
}
batch = append(batch, f)
return true
})
if initial && err == nil {
err = conn.Index(repo, batch)
if debug && err == nil {
l.Debugf("sendIndexes for %s-%s/%q: %d files (small initial index)", nodeID, name, repo, len(batch))
}
} else if len(batch) > 0 && err == nil {
err = conn.IndexUpdate(repo, batch)
if debug && err == nil {
l.Debugf("sendIndexes for %s-%s/%q: %d files (last batch)", nodeID, name, repo, len(batch))
}
}
return maxLocalVer, err
}
func (m *Model) updateLocal(repo string, f protocol.FileInfo) {
f.LocalVersion = 0
m.rmut.RLock()
+65
View File
@@ -0,0 +1,65 @@
// Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file).
// All rights reserved. Use of this source code is governed by an MIT-style
// license that can be found in the LICENSE file.
package scanner
import (
"os"
"path/filepath"
"sync"
"github.com/calmh/syncthing/protocol"
)
// The parallell hasher reads FileInfo structures from the inbox, hashes the
// file to populate the Blocks element and sends it to the outbox. A number of
// workers are used in parallel. The outbox will become closed when the inbox
// is closed and all items handled.
func newParallelHasher(dir string, blockSize, workers int, outbox, inbox chan protocol.FileInfo) {
var wg sync.WaitGroup
wg.Add(workers)
for i := 0; i < workers; i++ {
go func() {
hashFile(dir, blockSize, outbox, inbox)
wg.Done()
}()
}
go func() {
wg.Wait()
close(outbox)
}()
}
func hashFile(dir string, blockSize int, outbox, inbox chan protocol.FileInfo) {
for f := range inbox {
if protocol.IsDirectory(f.Flags) || protocol.IsDeleted(f.Flags) {
outbox <- f
continue
}
fd, err := os.Open(filepath.Join(dir, f.Name))
if err != nil {
if debug {
l.Debugln("open:", err)
}
continue
}
blocks, err := Blocks(fd, blockSize)
fd.Close()
if err != nil {
if debug {
l.Debugln("hash error:", f.Name, err)
}
continue
}
f.Blocks = blocks
outbox <- f
}
}
+10 -32
View File
@@ -13,7 +13,6 @@ import (
"path/filepath"
"runtime"
"strings"
"time"
"code.google.com/p/go.text/unicode/norm"
"github.com/calmh/syncthing/lamport"
@@ -60,18 +59,20 @@ type CurrentFiler interface {
// Walk returns the list of files found in the local repository by scanning the
// file system. Files are blockwise hashed.
func (w *Walker) Walk() (files chan protocol.FileInfo, ignore map[string][]string, err error) {
func (w *Walker) Walk() (chan protocol.FileInfo, map[string][]string, error) {
if debug {
l.Debugln("Walk", w.Dir, w.BlockSize, w.IgnoreFile)
}
err = checkDir(w.Dir)
err := checkDir(w.Dir)
if err != nil {
return
return nil, nil, err
}
ignore = make(map[string][]string)
files = make(chan protocol.FileInfo)
ignore := make(map[string][]string)
files := make(chan protocol.FileInfo)
hashedFiles := make(chan protocol.FileInfo)
newParallelHasher(w.Dir, w.BlockSize, runtime.NumCPU(), hashedFiles, files)
hashFiles := w.walkAndHashFiles(files, ignore)
go func() {
@@ -80,7 +81,7 @@ func (w *Walker) Walk() (files chan protocol.FileInfo, ignore map[string][]strin
close(files)
}()
return
return hashedFiles, ignore, nil
}
// CleanTempFiles removes all files that match the temporary filename pattern.
@@ -219,40 +220,17 @@ func (w *Walker) walkAndHashFiles(fchan chan protocol.FileInfo, ign map[string][
}
}
fd, err := os.Open(p)
if err != nil {
if debug {
l.Debugln("open:", p, err)
}
return nil
}
defer fd.Close()
t0 := time.Now()
blocks, err := Blocks(fd, w.BlockSize)
if err != nil {
if debug {
l.Debugln("hash error:", rn, err)
}
return nil
}
if debug {
t1 := time.Now()
l.Debugln("hashed:", rn, ";", len(blocks), "blocks;", info.Size(), "bytes;", int(float64(info.Size())/1024/t1.Sub(t0).Seconds()), "KB/s")
}
var flags = uint32(info.Mode() & os.ModePerm)
if w.IgnorePerms {
flags = protocol.FlagNoPermBits | 0666
}
f := protocol.FileInfo{
fchan <- protocol.FileInfo{
Name: rn,
Version: lamport.Default.Tick(0),
Flags: flags,
Modified: info.ModTime().Unix(),
Blocks: blocks,
}
fchan <- f
}
return nil
+16
View File
@@ -7,6 +7,7 @@ package scanner
import (
"fmt"
"reflect"
"sort"
"testing"
"time"
@@ -39,6 +40,7 @@ func TestWalk(t *testing.T) {
for f := range fchan {
files = append(files, f)
}
sort.Sort(fileList(files))
if err != nil {
t.Fatal(err)
@@ -133,3 +135,17 @@ func TestIgnore(t *testing.T) {
}
}
}
type fileList []protocol.FileInfo
func (f fileList) Len() int {
return len(f)
}
func (f fileList) Less(a, b int) bool {
return f[a].Name < f[b].Name
}
func (f fileList) Swap(a, b int) {
f[a], f[b] = f[b], f[a]
}