mirror of
https://github.com/syncthing/syncthing.git
synced 2026-01-03 19:39:20 -05:00
Compare commits
37 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c513171014 | ||
|
|
da5010d37a | ||
|
|
e6b78e5d56 | ||
|
|
410d700ae3 | ||
|
|
fc173bf679 | ||
|
|
72154aa668 | ||
|
|
31b5156191 | ||
|
|
ebce5d07ac | ||
|
|
915e1ac7de | ||
|
|
b78bfc0a43 | ||
|
|
30436741a7 | ||
|
|
98734375f2 | ||
|
|
37816e3818 | ||
|
|
4bc2b3f369 | ||
|
|
00be2bf18d | ||
|
|
44290a66b7 | ||
|
|
f6cc344623 | ||
|
|
a89d487510 | ||
|
|
a0ec4467fd | ||
|
|
e7280f1eb5 | ||
|
|
bf7fcc612d | ||
|
|
cff9bbc9c5 | ||
|
|
fddca3d2d6 | ||
|
|
9db49fb45e | ||
|
|
891409aedf | ||
|
|
77e47066ed | ||
|
|
852759f904 | ||
|
|
1dbc310c9b | ||
|
|
86ca58e2a9 | ||
|
|
22280db5db | ||
|
|
8e060e23e3 | ||
|
|
6e07742fe9 | ||
|
|
04d5032055 | ||
|
|
73ae87fad1 | ||
|
|
cd05282369 | ||
|
|
ee94d53bda | ||
|
|
922e1407c2 |
1
AUTHORS
1
AUTHORS
@@ -8,6 +8,7 @@ Anderson Mesquita <andersonvom@gmail.com>
|
||||
Andrew Dunham <andrew@du.nham.ca>
|
||||
Antony Male <antony.male@gmail.com>
|
||||
Arthur Axel fREW Schmidt <frew@afoolishmanifesto.com> <frioux@gmail.com>
|
||||
Alexandre Viau <alexandre@alexandreviau.net> <aviau@debian.org>
|
||||
Audrius Butkevicius <audrius.butkevicius@gmail.com>
|
||||
Bart De Vries <devriesb@gmail.com>
|
||||
Ben Curthoys <ben@bencurthoys.com>
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
Do not report security issues in this bug tracker. Instead, contact
|
||||
security@syncthing.net directly - see https://syncthing.net/security.html
|
||||
for more information.
|
||||
|
||||
If your issue is a support request ("How do I get my devices to connect?"
|
||||
or similar), please use the support forum at https://forum.syncthing.net/
|
||||
where a large number of helpful people hang out. This issue tracker is for
|
||||
reporting bugs or feature requests directly to the developers.
|
||||
reporting bugs or feature requests directly to the developers.
|
||||
|
||||
If your issue is a bug report, replace this boilerplate with a description
|
||||
of the problem, being sure to include at least:
|
||||
|
||||
1
NICKS
1
NICKS
@@ -7,6 +7,7 @@ andersonvom <andersonvom@gmail.com>
|
||||
andrew-d <andrew@du.nham.ca>
|
||||
asdil12 <dominik@heidler.eu>
|
||||
AudriusButkevicius <audrius.butkevicius@gmail.com>
|
||||
aviau <alexandre@alexandreviau.net> <aviau@debian.org>
|
||||
bencurthoys <ben@bencurthoys.com>
|
||||
bigbear2nd <bigbear2nd@gmail.com>
|
||||
brbecker <brbecker@gmail.com>
|
||||
|
||||
79
build.go
79
build.go
@@ -117,16 +117,8 @@ func main() {
|
||||
log.SetOutput(os.Stdout)
|
||||
log.SetFlags(0)
|
||||
|
||||
// If GOPATH isn't set, set it correctly with the assumption that we are
|
||||
// in $GOPATH/src/github.com/syncthing/syncthing.
|
||||
if os.Getenv("GOPATH") == "" {
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
gopath := filepath.Clean(filepath.Join(cwd, "../../../../"))
|
||||
log.Println("GOPATH is", gopath)
|
||||
os.Setenv("GOPATH", gopath)
|
||||
setGoPath()
|
||||
}
|
||||
|
||||
// We use Go 1.5+ vendoring.
|
||||
@@ -136,12 +128,7 @@ func main() {
|
||||
// might have installed during "build.go setup".
|
||||
os.Setenv("PATH", fmt.Sprintf("%s%cbin%c%s", os.Getenv("GOPATH"), os.PathSeparator, os.PathListSeparator, os.Getenv("PATH")))
|
||||
|
||||
flag.StringVar(&goarch, "goarch", runtime.GOARCH, "GOARCH")
|
||||
flag.StringVar(&goos, "goos", runtime.GOOS, "GOOS")
|
||||
flag.BoolVar(&noupgrade, "no-upgrade", noupgrade, "Disable upgrade functionality")
|
||||
flag.StringVar(&version, "version", getVersion(), "Set compiled in version string")
|
||||
flag.BoolVar(&race, "race", race, "Use race detector")
|
||||
flag.Parse()
|
||||
parseFlags()
|
||||
|
||||
switch goarch {
|
||||
case "386", "amd64", "arm", "arm64", "ppc64", "ppc64le":
|
||||
@@ -230,17 +217,46 @@ func main() {
|
||||
clean()
|
||||
|
||||
case "vet":
|
||||
vet("build.go")
|
||||
vet("cmd", "lib")
|
||||
|
||||
case "lint":
|
||||
lint(".")
|
||||
lint("./cmd/...")
|
||||
lint("./lib/...")
|
||||
if isGometalinterInstalled() {
|
||||
dirs := []string{".", "./cmd/...", "./lib/..."}
|
||||
gometalinter("deadcode", dirs, "test/util.go")
|
||||
gometalinter("structcheck", dirs)
|
||||
gometalinter("varcheck", dirs)
|
||||
}
|
||||
|
||||
default:
|
||||
log.Fatalf("Unknown command %q", cmd)
|
||||
}
|
||||
}
|
||||
|
||||
// setGoPath sets GOPATH correctly with the assumption that we are
|
||||
// in $GOPATH/src/github.com/syncthing/syncthing.
|
||||
func setGoPath() {
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
gopath := filepath.Clean(filepath.Join(cwd, "../../../../"))
|
||||
log.Println("GOPATH is", gopath)
|
||||
os.Setenv("GOPATH", gopath)
|
||||
}
|
||||
|
||||
func parseFlags() {
|
||||
flag.StringVar(&goarch, "goarch", runtime.GOARCH, "GOARCH")
|
||||
flag.StringVar(&goos, "goos", runtime.GOOS, "GOOS")
|
||||
flag.BoolVar(&noupgrade, "no-upgrade", noupgrade, "Disable upgrade functionality")
|
||||
flag.StringVar(&version, "version", getVersion(), "Set compiled in version string")
|
||||
flag.BoolVar(&race, "race", race, "Use race detector")
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
func checkRequiredGoVersion() (float64, bool) {
|
||||
re := regexp.MustCompile(`go(\d+\.\d+)`)
|
||||
ver := runtime.Version()
|
||||
@@ -271,6 +287,7 @@ func setup() {
|
||||
runPrint("go", "get", "-v", "github.com/axw/gocov/gocov")
|
||||
runPrint("go", "get", "-v", "github.com/AlekSi/gocov-xml")
|
||||
runPrint("go", "get", "-v", "bitbucket.org/tebeka/go2xunit")
|
||||
runPrint("go", "get", "-v", "github.com/alecthomas/gometalinter")
|
||||
}
|
||||
|
||||
func test(pkgs ...string) {
|
||||
@@ -852,7 +869,6 @@ func vet(dirs ...string) {
|
||||
// A genuine error exit from the vet tool.
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func lint(pkg string) {
|
||||
@@ -901,3 +917,34 @@ func exitStatus(err error) int {
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
func isGometalinterInstalled() bool {
|
||||
if _, err := runError("gometalinter", "--disable-all"); err != nil {
|
||||
log.Println("gometalinter is not installed")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func gometalinter(linter string, dirs []string, excludes ...string) {
|
||||
params := []string{"--disable-all"}
|
||||
params = append(params, fmt.Sprintf("--deadline=%ds", 60))
|
||||
params = append(params, "--enable="+linter)
|
||||
|
||||
for _, exclude := range excludes {
|
||||
params = append(params, "--exclude="+exclude)
|
||||
}
|
||||
|
||||
for _, dir := range dirs {
|
||||
params = append(params, dir)
|
||||
}
|
||||
|
||||
bs, err := runError("gometalinter", params...)
|
||||
|
||||
if len(bs) > 0 {
|
||||
log.Printf("%s", bs)
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,11 +35,11 @@ import (
|
||||
"github.com/syncthing/syncthing/lib/model"
|
||||
"github.com/syncthing/syncthing/lib/osutil"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syncthing/syncthing/lib/rand"
|
||||
"github.com/syncthing/syncthing/lib/stats"
|
||||
"github.com/syncthing/syncthing/lib/sync"
|
||||
"github.com/syncthing/syncthing/lib/tlsutil"
|
||||
"github.com/syncthing/syncthing/lib/upgrade"
|
||||
"github.com/syncthing/syncthing/lib/util"
|
||||
"github.com/vitrun/qart/qr"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
@@ -250,6 +250,7 @@ func (s *apiService) Serve() {
|
||||
getRestMux.HandleFunc("/rest/svc/deviceid", s.getDeviceID) // id
|
||||
getRestMux.HandleFunc("/rest/svc/lang", s.getLang) // -
|
||||
getRestMux.HandleFunc("/rest/svc/report", s.getReport) // -
|
||||
getRestMux.HandleFunc("/rest/svc/random/string", s.getRandomString) // [length]
|
||||
getRestMux.HandleFunc("/rest/system/browse", s.getSystemBrowse) // current
|
||||
getRestMux.HandleFunc("/rest/system/config", s.getSystemConfig) // -
|
||||
getRestMux.HandleFunc("/rest/system/config/insync", s.getSystemConfigInsync) // -
|
||||
@@ -298,13 +299,16 @@ func (s *apiService) Serve() {
|
||||
// Serve compiled in assets unless an asset directory was set (for development)
|
||||
assets := &embeddedStatic{
|
||||
theme: s.cfg.GUI().Theme,
|
||||
lastModified: time.Now(),
|
||||
lastModified: time.Now().Truncate(time.Second), // must truncate, for the wire precision is 1s
|
||||
mut: sync.NewRWMutex(),
|
||||
assetDir: s.assetDir,
|
||||
assets: auto.Assets(),
|
||||
}
|
||||
mux.Handle("/", assets)
|
||||
|
||||
// Handle the special meta.js path
|
||||
mux.HandleFunc("/meta.js", s.getJSMetadata)
|
||||
|
||||
s.cfg.Subscribe(assets)
|
||||
|
||||
guiCfg := s.cfg.GUI()
|
||||
@@ -461,10 +465,6 @@ func corsMiddleware(next http.Handler) http.Handler {
|
||||
//
|
||||
// See https://www.w3.org/TR/cors/ for details.
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Add a generous access-control-allow-origin header since we may be
|
||||
// redirecting REST requests over protocols
|
||||
w.Header().Add("Access-Control-Allow-Origin", "*")
|
||||
|
||||
// Process OPTIONS requests
|
||||
if r.Method == "OPTIONS" {
|
||||
// Only GET/POST Methods are supported
|
||||
@@ -529,6 +529,14 @@ func (s *apiService) restPing(w http.ResponseWriter, r *http.Request) {
|
||||
sendJSON(w, map[string]string{"ping": "pong"})
|
||||
}
|
||||
|
||||
func (s *apiService) getJSMetadata(w http.ResponseWriter, r *http.Request) {
|
||||
meta, _ := json.Marshal(map[string]string{
|
||||
"deviceID": s.id.String(),
|
||||
})
|
||||
w.Header().Set("Content-Type", "application/javascript")
|
||||
fmt.Fprintf(w, "var metadata = %s;\n", meta)
|
||||
}
|
||||
|
||||
func (s *apiService) getSystemVersion(w http.ResponseWriter, r *http.Request) {
|
||||
sendJSON(w, map[string]string{
|
||||
"version": Version,
|
||||
@@ -743,7 +751,7 @@ func (s *apiService) postSystemConfig(w http.ResponseWriter, r *http.Request) {
|
||||
if curAcc := s.cfg.Options().URAccepted; to.Options.URAccepted > curAcc {
|
||||
// UR was enabled
|
||||
to.Options.URAccepted = usageReportVersion
|
||||
to.Options.URUniqueID = util.RandomString(8)
|
||||
to.Options.URUniqueID = rand.String(8)
|
||||
} else if to.Options.URAccepted < curAcc {
|
||||
// UR was disabled
|
||||
to.Options.URAccepted = -1
|
||||
@@ -923,6 +931,16 @@ func (s *apiService) getReport(w http.ResponseWriter, r *http.Request) {
|
||||
sendJSON(w, reportData(s.cfg, s.model))
|
||||
}
|
||||
|
||||
func (s *apiService) getRandomString(w http.ResponseWriter, r *http.Request) {
|
||||
length := 32
|
||||
if val, _ := strconv.Atoi(r.URL.Query().Get("length")); val > 0 {
|
||||
length = val
|
||||
}
|
||||
str := rand.String(length)
|
||||
|
||||
sendJSON(w, map[string]string{"random": str})
|
||||
}
|
||||
|
||||
func (s *apiService) getDBIgnores(w http.ResponseWriter, r *http.Request) {
|
||||
qs := r.URL.Query()
|
||||
|
||||
@@ -1228,7 +1246,8 @@ func (s embeddedStatic) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
if modifiedSince, err := time.Parse(r.Header.Get("If-Modified-Since"), http.TimeFormat); err == nil && modified.Before(modifiedSince) {
|
||||
modifiedSince, err := http.ParseTime(r.Header.Get("If-Modified-Since"))
|
||||
if err == nil && !modified.After(modifiedSince) {
|
||||
w.WriteHeader(http.StatusNotModified)
|
||||
return
|
||||
}
|
||||
@@ -1247,7 +1266,7 @@ func (s embeddedStatic) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
gr.Close()
|
||||
}
|
||||
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(bs)))
|
||||
w.Header().Set("Last-Modified", modified.Format(http.TimeFormat))
|
||||
w.Header().Set("Last-Modified", modified.UTC().Format(http.TimeFormat))
|
||||
w.Header().Set("Cache-Control", "public")
|
||||
|
||||
w.Write(bs)
|
||||
|
||||
@@ -9,15 +9,14 @@ package main
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/events"
|
||||
"github.com/syncthing/syncthing/lib/rand"
|
||||
"github.com/syncthing/syncthing/lib/sync"
|
||||
"github.com/syncthing/syncthing/lib/util"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
@@ -114,7 +113,7 @@ func basicAuthAndSessionMiddleware(cookieName string, cfg config.GUIConfiguratio
|
||||
return
|
||||
|
||||
passwordOK:
|
||||
sessionid := util.RandomString(32)
|
||||
sessionid := rand.String(32)
|
||||
sessionsMut.Lock()
|
||||
sessions[sessionid] = true
|
||||
sessionsMut.Unlock()
|
||||
|
||||
@@ -15,8 +15,8 @@ import (
|
||||
|
||||
"github.com/syncthing/syncthing/lib/config"
|
||||
"github.com/syncthing/syncthing/lib/osutil"
|
||||
"github.com/syncthing/syncthing/lib/rand"
|
||||
"github.com/syncthing/syncthing/lib/sync"
|
||||
"github.com/syncthing/syncthing/lib/util"
|
||||
)
|
||||
|
||||
// csrfTokens is a list of valid tokens. It is sorted so that the most
|
||||
@@ -41,7 +41,8 @@ func csrfMiddleware(unique string, prefix string, cfg config.GUIConfiguration, n
|
||||
return
|
||||
}
|
||||
|
||||
// Allow requests for the front page, and set a CSRF cookie if there isn't already a valid one.
|
||||
// Allow requests for anything not under the protected path prefix,
|
||||
// and set a CSRF cookie if there isn't already a valid one.
|
||||
if !strings.HasPrefix(r.URL.Path, prefix) {
|
||||
cookie, err := r.Cookie("CSRF-Token-" + unique)
|
||||
if err != nil || !validCsrfToken(cookie.Value) {
|
||||
@@ -56,18 +57,6 @@ func csrfMiddleware(unique string, prefix string, cfg config.GUIConfiguration, n
|
||||
return
|
||||
}
|
||||
|
||||
if r.Method == "GET" {
|
||||
// Allow GET requests unconditionally, but if we got the CSRF
|
||||
// token cookie do the verification anyway so we keep the
|
||||
// csrfTokens list sorted by recent usage. We don't care about the
|
||||
// outcome of the validity check.
|
||||
if cookie, err := r.Cookie("CSRF-Token-" + unique); err == nil {
|
||||
validCsrfToken(cookie.Value)
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Verify the CSRF token
|
||||
token := r.Header.Get("X-CSRF-Token-" + unique)
|
||||
if !validCsrfToken(token) {
|
||||
@@ -98,7 +87,7 @@ func validCsrfToken(token string) bool {
|
||||
}
|
||||
|
||||
func newCsrfToken() string {
|
||||
token := util.RandomString(32)
|
||||
token := rand.String(32)
|
||||
|
||||
csrfMut.Lock()
|
||||
csrfTokens = append([]string{token}, csrfTokens...)
|
||||
|
||||
@@ -9,6 +9,7 @@ package main
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
@@ -189,7 +190,9 @@ type httpTestCase struct {
|
||||
}
|
||||
|
||||
func TestAPIServiceRequests(t *testing.T) {
|
||||
const testAPIKey = "foobarbaz"
|
||||
cfg := new(mockedConfig)
|
||||
cfg.gui.APIKey = testAPIKey
|
||||
baseURL, err := startHTTP(cfg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -344,13 +347,13 @@ func TestAPIServiceRequests(t *testing.T) {
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Log("Testing", tc.URL, "...")
|
||||
testHTTPRequest(t, baseURL, tc)
|
||||
testHTTPRequest(t, baseURL, tc, testAPIKey)
|
||||
}
|
||||
}
|
||||
|
||||
// testHTTPRequest tries the given test case, comparing the result code,
|
||||
// content type, and result prefix.
|
||||
func testHTTPRequest(t *testing.T, baseURL string, tc httpTestCase) {
|
||||
func testHTTPRequest(t *testing.T, baseURL string, tc httpTestCase, apikey string) {
|
||||
timeout := time.Second
|
||||
if tc.Timeout > 0 {
|
||||
timeout = tc.Timeout
|
||||
@@ -359,7 +362,14 @@ func testHTTPRequest(t *testing.T, baseURL string, tc httpTestCase) {
|
||||
Timeout: timeout,
|
||||
}
|
||||
|
||||
resp, err := cli.Get(baseURL + tc.URL)
|
||||
req, err := http.NewRequest("GET", baseURL+tc.URL, nil)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error requesting %s: %v", tc.URL, err)
|
||||
return
|
||||
}
|
||||
req.Header.Set("X-API-Key", apikey)
|
||||
|
||||
resp, err := cli.Do(req)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error requesting %s: %v", tc.URL, err)
|
||||
return
|
||||
@@ -400,7 +410,7 @@ func TestHTTPLogin(t *testing.T) {
|
||||
|
||||
// Verify rejection when not using authorization
|
||||
|
||||
req, _ := http.NewRequest("GET", baseURL+"/rest/system/status", nil)
|
||||
req, _ := http.NewRequest("GET", baseURL, nil)
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@@ -491,3 +501,122 @@ func startHTTP(cfg *mockedConfig) (string, error) {
|
||||
|
||||
return baseURL, nil
|
||||
}
|
||||
|
||||
func TestCSRFRequired(t *testing.T) {
|
||||
const testAPIKey = "foobarbaz"
|
||||
cfg := new(mockedConfig)
|
||||
cfg.gui.APIKey = testAPIKey
|
||||
baseURL, err := startHTTP(cfg)
|
||||
|
||||
cli := &http.Client{
|
||||
Timeout: time.Second,
|
||||
}
|
||||
|
||||
// Getting the base URL (i.e. "/") should succeed.
|
||||
|
||||
resp, err := cli.Get(baseURL)
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected error from getting base URL:", err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatal("Getting base URL should succeed, not", resp.Status)
|
||||
}
|
||||
|
||||
// Find the returned CSRF token for future use
|
||||
|
||||
var csrfTokenName, csrfTokenValue string
|
||||
for _, cookie := range resp.Cookies() {
|
||||
if strings.HasPrefix(cookie.Name, "CSRF-Token") {
|
||||
csrfTokenName = cookie.Name
|
||||
csrfTokenValue = cookie.Value
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Calling on /rest without a token should fail
|
||||
|
||||
resp, err = cli.Get(baseURL + "/rest/system/config")
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected error from getting /rest/system/config:", err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusForbidden {
|
||||
t.Fatal("Getting /rest/system/config without CSRF token should fail, not", resp.Status)
|
||||
}
|
||||
|
||||
// Calling on /rest with a token should succeed
|
||||
|
||||
req, _ := http.NewRequest("GET", baseURL+"/rest/system/config", nil)
|
||||
req.Header.Set("X-"+csrfTokenName, csrfTokenValue)
|
||||
resp, err = cli.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected error from getting /rest/system/config:", err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatal("Getting /rest/system/config with CSRF token should succeed, not", resp.Status)
|
||||
}
|
||||
|
||||
// Calling on /rest with the API key should succeed
|
||||
|
||||
req, _ = http.NewRequest("GET", baseURL+"/rest/system/config", nil)
|
||||
req.Header.Set("X-API-Key", testAPIKey)
|
||||
resp, err = cli.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected error from getting /rest/system/config:", err)
|
||||
}
|
||||
resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatal("Getting /rest/system/config with API key should succeed, not", resp.Status)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRandomString(t *testing.T) {
|
||||
const testAPIKey = "foobarbaz"
|
||||
cfg := new(mockedConfig)
|
||||
cfg.gui.APIKey = testAPIKey
|
||||
baseURL, err := startHTTP(cfg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
cli := &http.Client{
|
||||
Timeout: time.Second,
|
||||
}
|
||||
|
||||
// The default should be to return a 32 character random string
|
||||
|
||||
for _, url := range []string{"/rest/svc/random/string", "/rest/svc/random/string?length=-1", "/rest/svc/random/string?length=yo"} {
|
||||
req, _ := http.NewRequest("GET", baseURL+url, nil)
|
||||
req.Header.Set("X-API-Key", testAPIKey)
|
||||
resp, err := cli.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var res map[string]string
|
||||
if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(res["random"]) != 32 {
|
||||
t.Errorf("Expected 32 random characters, got %q of length %d", res["random"], len(res["random"]))
|
||||
}
|
||||
}
|
||||
|
||||
// We can ask for a different length if we like
|
||||
|
||||
req, _ := http.NewRequest("GET", baseURL+"/rest/svc/random/string?length=27", nil)
|
||||
req.Header.Set("X-API-Key", testAPIKey)
|
||||
resp, err := cli.Do(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var res map[string]string
|
||||
if err := json.NewDecoder(resp.Body).Decode(&res); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(res["random"]) != 27 {
|
||||
t.Errorf("Expected 27 random characters, got %q of length %d", res["random"], len(res["random"]))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,10 +39,10 @@ import (
|
||||
"github.com/syncthing/syncthing/lib/model"
|
||||
"github.com/syncthing/syncthing/lib/osutil"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syncthing/syncthing/lib/rand"
|
||||
"github.com/syncthing/syncthing/lib/symlinks"
|
||||
"github.com/syncthing/syncthing/lib/tlsutil"
|
||||
"github.com/syncthing/syncthing/lib/upgrade"
|
||||
"github.com/syncthing/syncthing/lib/util"
|
||||
|
||||
"github.com/thejerf/suture"
|
||||
)
|
||||
@@ -532,8 +532,9 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
|
||||
errors := logger.NewRecorder(l, logger.LevelWarn, maxSystemErrors, 0)
|
||||
systemLog := logger.NewRecorder(l, logger.LevelDebug, maxSystemLog, initialSystemLog)
|
||||
|
||||
// Event subscription for the API; must start early to catch the early events.
|
||||
apiSub := events.NewBufferedSubscription(events.Default.Subscribe(events.AllEvents), 1000)
|
||||
// Event subscription for the API; must start early to catch the early events. The LocalDiskUpdated
|
||||
// event might overwhelm the event reciever in some situations so we will not subscribe to it here.
|
||||
apiSub := events.NewBufferedSubscription(events.Default.Subscribe(events.AllEvents&^events.LocalChangeDetected), 1000)
|
||||
|
||||
if len(os.Getenv("GOMAXPROCS")) == 0 {
|
||||
runtime.GOMAXPROCS(runtime.NumCPU())
|
||||
@@ -760,7 +761,7 @@ func syncthingMain(runtimeOptions RuntimeOptions) {
|
||||
if opts.URUniqueID == "" {
|
||||
// Previously the ID was generated from the node ID. We now need
|
||||
// to generate a new one.
|
||||
opts.URUniqueID = util.RandomString(8)
|
||||
opts.URUniqueID = rand.String(8)
|
||||
cfg.SetOptions(opts)
|
||||
cfg.Save()
|
||||
}
|
||||
@@ -946,7 +947,7 @@ func defaultConfig(myName string) config.Configuration {
|
||||
|
||||
if !noDefaultFolder {
|
||||
l.Infoln("Default folder created and/or linked to new config")
|
||||
folderID := util.RandomString(5) + "-" + util.RandomString(5)
|
||||
folderID := rand.String(5) + "-" + rand.String(5)
|
||||
defaultFolder = config.NewFolderConfiguration(folderID, locations[locDefFolder])
|
||||
defaultFolder.Label = "Default Folder (" + folderID + ")"
|
||||
defaultFolder.RescanIntervalS = 60
|
||||
|
||||
@@ -59,7 +59,7 @@ func (c *folderSummaryService) Stop() {
|
||||
// listenForUpdates subscribes to the event bus and makes note of folders that
|
||||
// need their data recalculated.
|
||||
func (c *folderSummaryService) listenForUpdates() {
|
||||
sub := events.Default.Subscribe(events.LocalIndexUpdated | events.RemoteIndexUpdated | events.StateChanged)
|
||||
sub := events.Default.Subscribe(events.LocalIndexUpdated | events.RemoteIndexUpdated | events.StateChanged | events.RemoteDownloadProgress)
|
||||
defer events.Default.Unsubscribe(sub)
|
||||
|
||||
for {
|
||||
|
||||
@@ -72,15 +72,18 @@ func (s *verboseService) formatEvent(ev events.Event) string {
|
||||
|
||||
case events.Starting:
|
||||
return fmt.Sprintf("Starting up (%s)", ev.Data.(map[string]string)["home"])
|
||||
|
||||
case events.StartupComplete:
|
||||
return "Startup complete"
|
||||
|
||||
case events.DeviceDiscovered:
|
||||
data := ev.Data.(map[string]interface{})
|
||||
return fmt.Sprintf("Discovered device %v at %v", data["device"], data["addrs"])
|
||||
|
||||
case events.DeviceConnected:
|
||||
data := ev.Data.(map[string]string)
|
||||
return fmt.Sprintf("Connected to device %v at %v (type %s)", data["id"], data["addr"], data["type"])
|
||||
|
||||
case events.DeviceDisconnected:
|
||||
data := ev.Data.(map[string]string)
|
||||
return fmt.Sprintf("Disconnected from device %v", data["id"])
|
||||
@@ -89,6 +92,11 @@ func (s *verboseService) formatEvent(ev events.Event) string {
|
||||
data := ev.Data.(map[string]interface{})
|
||||
return fmt.Sprintf("Folder %q is now %v", data["folder"], data["to"])
|
||||
|
||||
case events.LocalChangeDetected:
|
||||
data := ev.Data.(map[string]string)
|
||||
// Local change detected in folder "foo": modified file /Users/jb/whatever
|
||||
return fmt.Sprintf("Local change detected in folder %q: %s %s %s", data["folder"], data["action"], data["type"], data["path"])
|
||||
|
||||
case events.RemoteIndexUpdated:
|
||||
data := ev.Data.(map[string]interface{})
|
||||
return fmt.Sprintf("Device %v sent an index update for %q with %d items", data["device"], data["folder"], data["items"])
|
||||
@@ -96,6 +104,7 @@ func (s *verboseService) formatEvent(ev events.Event) string {
|
||||
case events.DeviceRejected:
|
||||
data := ev.Data.(map[string]interface{})
|
||||
return fmt.Sprintf("Rejected connection from device %v at %v", data["device"], data["address"])
|
||||
|
||||
case events.FolderRejected:
|
||||
data := ev.Data.(map[string]string)
|
||||
return fmt.Sprintf("Rejected unshared folder %q from device %v", data["folder"], data["device"])
|
||||
@@ -103,6 +112,7 @@ func (s *verboseService) formatEvent(ev events.Event) string {
|
||||
case events.ItemStarted:
|
||||
data := ev.Data.(map[string]string)
|
||||
return fmt.Sprintf("Started syncing %q / %q (%v %v)", data["folder"], data["item"], data["action"], data["type"])
|
||||
|
||||
case events.ItemFinished:
|
||||
data := ev.Data.(map[string]interface{})
|
||||
if err, ok := data["error"].(*string); ok && err != nil {
|
||||
@@ -119,13 +129,18 @@ func (s *verboseService) formatEvent(ev events.Event) string {
|
||||
case events.FolderCompletion:
|
||||
data := ev.Data.(map[string]interface{})
|
||||
return fmt.Sprintf("Completion for folder %q on device %v is %v%%", data["folder"], data["device"], data["completion"])
|
||||
|
||||
case events.FolderSummary:
|
||||
data := ev.Data.(map[string]interface{})
|
||||
sum := data["summary"].(map[string]interface{})
|
||||
delete(sum, "invalid")
|
||||
delete(sum, "ignorePatterns")
|
||||
delete(sum, "stateChanged")
|
||||
return fmt.Sprintf("Summary for folder %q is %v", data["folder"], data["summary"])
|
||||
sum := make(map[string]interface{})
|
||||
for k, v := range data["summary"].(map[string]interface{}) {
|
||||
if k == "invalid" || k == "ignorePatterns" || k == "stateChanged" {
|
||||
continue
|
||||
}
|
||||
sum[k] = v
|
||||
}
|
||||
return fmt.Sprintf("Summary for folder %q is %v", data["folder"], sum)
|
||||
|
||||
case events.FolderScanProgress:
|
||||
data := ev.Data.(map[string]interface{})
|
||||
folder := data["folder"].(string)
|
||||
@@ -142,16 +157,19 @@ func (s *verboseService) formatEvent(ev events.Event) string {
|
||||
data := ev.Data.(map[string]string)
|
||||
device := data["device"]
|
||||
return fmt.Sprintf("Device %v was paused", device)
|
||||
|
||||
case events.DeviceResumed:
|
||||
data := ev.Data.(map[string]string)
|
||||
device := data["device"]
|
||||
return fmt.Sprintf("Device %v was resumed", device)
|
||||
|
||||
case events.ListenAddressesChanged:
|
||||
data := ev.Data.(map[string]interface{})
|
||||
address := data["address"]
|
||||
lan := data["lan"]
|
||||
wan := data["wan"]
|
||||
return fmt.Sprintf("Listen address %s resolution has changed: lan addresses: %s wan addresses: %s", address, lan, wan)
|
||||
|
||||
case events.LoginAttempt:
|
||||
data := ev.Data.(map[string]interface{})
|
||||
username := data["username"].(string)
|
||||
@@ -162,7 +180,6 @@ func (s *verboseService) formatEvent(ev events.Event) string {
|
||||
success = "failed"
|
||||
}
|
||||
return fmt.Sprintf("Login %s for username %s.", success, username)
|
||||
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s %#v", ev.Type, ev)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
[Unit]
|
||||
Description=Restart Syncthing after resume
|
||||
Documentation=man:syncthing(1)
|
||||
After=suspend.target
|
||||
|
||||
[Service]
|
||||
|
||||
@@ -38,7 +38,7 @@ ul+h5 {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
.panel-title a:hover {
|
||||
a.panel-heading:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"Comment, when used at the start of a line": "Коментар, използван в началото на реда",
|
||||
"Compression": "Компресиране",
|
||||
"Connection Error": "Грешка при свързването",
|
||||
"Connection Type": "Вид връзка",
|
||||
"Copied from elsewhere": "Копиране от някъде другаде",
|
||||
"Copied from original": "Копиран от оригинала",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Всички правата запазени © 2014-2016 Сътрудници:",
|
||||
@@ -74,6 +75,7 @@
|
||||
"Folder Label": "Етикет на папката",
|
||||
"Folder Master": "Главна папка",
|
||||
"Folder Path": "Път до папката",
|
||||
"Folder Type": "Вид папка",
|
||||
"Folders": "Папки",
|
||||
"GUI": "Потребителски интерфейс",
|
||||
"GUI Authentication Password": "Парола за потребителския интерфейс",
|
||||
@@ -98,10 +100,12 @@
|
||||
"Last File Received": "Последния получен файл",
|
||||
"Last seen": "Последно видян",
|
||||
"Later": "По-късно",
|
||||
"Listeners": "Слушащи",
|
||||
"Local Discovery": "Локално откриване",
|
||||
"Local State": "Локално състояние",
|
||||
"Local State (Total)": "Локално състояние (Общо)",
|
||||
"Major Upgrade": "Основно Обновяване",
|
||||
"Master": "Главен",
|
||||
"Maximum Age": "Максимална възраст",
|
||||
"Metadata Only": "Само мета информация",
|
||||
"Minimum Free Disk Space": "Минимално свободно дисково пространство",
|
||||
@@ -113,6 +117,7 @@
|
||||
"Newest First": "Първо най-новите",
|
||||
"No": "Не",
|
||||
"No File Versioning": "Без версии",
|
||||
"Normal": "Нормален",
|
||||
"Notice": "Известие",
|
||||
"OK": "ОК",
|
||||
"Off": "Изключено",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"Comment, when used at the start of a line": "Comentari quan és usat al principi d'una línia",
|
||||
"Compression": "Compressió",
|
||||
"Connection Error": "Error de connexió",
|
||||
"Connection Type": "Connection Type",
|
||||
"Copied from elsewhere": "Copiat d'un altre lloc",
|
||||
"Copied from original": "Copiat de l'original",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 the following Contributors:",
|
||||
@@ -74,6 +75,7 @@
|
||||
"Folder Label": "Folder Label",
|
||||
"Folder Master": "Carpeta mestra",
|
||||
"Folder Path": "Camí de carpeta",
|
||||
"Folder Type": "Folder Type",
|
||||
"Folders": "Carpetes",
|
||||
"GUI": "GUI",
|
||||
"GUI Authentication Password": "Contrasenya d'autenticació GUI",
|
||||
@@ -98,10 +100,12 @@
|
||||
"Last File Received": "Últim fitxer rebut",
|
||||
"Last seen": "Vist per última vegada",
|
||||
"Later": "Després",
|
||||
"Listeners": "Listeners",
|
||||
"Local Discovery": "Descobriment Local",
|
||||
"Local State": "Estat local",
|
||||
"Local State (Total)": "Estat local (Total)",
|
||||
"Major Upgrade": "Actualització major",
|
||||
"Master": "Master",
|
||||
"Maximum Age": "Antiguitat Màxima",
|
||||
"Metadata Only": "Només metadades",
|
||||
"Minimum Free Disk Space": "Espai de disc lliure mínim",
|
||||
@@ -113,6 +117,7 @@
|
||||
"Newest First": "Més nou primer",
|
||||
"No": "No",
|
||||
"No File Versioning": "Sense Versionat de Fitxer",
|
||||
"Normal": "Normal",
|
||||
"Notice": "Avís",
|
||||
"OK": "OK",
|
||||
"Off": "Desactivar",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"Comment, when used at the start of a line": "Comentar, quant s'utilitza al principi d'una línia",
|
||||
"Compression": "Compresió",
|
||||
"Connection Error": "Error de connexió",
|
||||
"Connection Type": "Connection Type",
|
||||
"Copied from elsewhere": "Copiat de qualsevol lloc",
|
||||
"Copied from original": "Copiat de l'original",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 els següents Col·laboradors:",
|
||||
@@ -74,6 +75,7 @@
|
||||
"Folder Label": "Etiqueta de la Carpeta",
|
||||
"Folder Master": "Carpeta principal",
|
||||
"Folder Path": "Ruta de la carpeta",
|
||||
"Folder Type": "Folder Type",
|
||||
"Folders": "Carpetes",
|
||||
"GUI": "IGU (Interfície Gràfica d'Usuari)",
|
||||
"GUI Authentication Password": "Password d'autenticació de l'Interfície Gràfica d'Usuari (GUI)",
|
||||
@@ -98,10 +100,12 @@
|
||||
"Last File Received": "Darrer fitxer rebut",
|
||||
"Last seen": "Vist per última vegada",
|
||||
"Later": "Més tard",
|
||||
"Listeners": "Listeners",
|
||||
"Local Discovery": "Descobriment local",
|
||||
"Local State": "Estat local",
|
||||
"Local State (Total)": "Estat Local (Total)",
|
||||
"Major Upgrade": "Actualització important",
|
||||
"Master": "Master",
|
||||
"Maximum Age": "Edat màxima",
|
||||
"Metadata Only": "Sols metadades",
|
||||
"Minimum Free Disk Space": "Espai minim de disc lliure",
|
||||
@@ -113,6 +117,7 @@
|
||||
"Newest First": "El més nou primer",
|
||||
"No": "No",
|
||||
"No File Versioning": "Sense versionat de fitxer",
|
||||
"Normal": "Normal",
|
||||
"Notice": "Avís",
|
||||
"OK": "OK",
|
||||
"Off": "Off",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"Comment, when used at the start of a line": "Komentář, pokud použito na začátku řádku",
|
||||
"Compression": "Komprese",
|
||||
"Connection Error": "Chyba připojení",
|
||||
"Connection Type": "Typ připojení",
|
||||
"Copied from elsewhere": "Zkopírováno odjinud",
|
||||
"Copied from original": "Zkopírováno z originálu",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 následující přispěvatelé:",
|
||||
@@ -74,6 +75,7 @@
|
||||
"Folder Label": "Jmenovka adresáře",
|
||||
"Folder Master": "Master adresář",
|
||||
"Folder Path": "Cesta k adresáři",
|
||||
"Folder Type": "Typ adresáře",
|
||||
"Folders": "Adresáře",
|
||||
"GUI": "GUI",
|
||||
"GUI Authentication Password": "Přihlašovací heslo pro GUI",
|
||||
@@ -98,10 +100,12 @@
|
||||
"Last File Received": "Poslední přijatý soubor",
|
||||
"Last seen": "Naposledy spatřen",
|
||||
"Later": "Později",
|
||||
"Listeners": "Naslouchající",
|
||||
"Local Discovery": "Místní oznamování",
|
||||
"Local State": "Místní status",
|
||||
"Local State (Total)": "Místní status (Celkem)",
|
||||
"Major Upgrade": "Důležitá aktualizace",
|
||||
"Master": "Master",
|
||||
"Maximum Age": "Maximální časový limit",
|
||||
"Metadata Only": "Pouze metadata",
|
||||
"Minimum Free Disk Space": "Minimální velikost volného místa na disku",
|
||||
@@ -113,6 +117,7 @@
|
||||
"Newest First": "Od nejnovějšího",
|
||||
"No": "Ne",
|
||||
"No File Versioning": "Bez verzování souborů",
|
||||
"Normal": "Normální",
|
||||
"Notice": "Oznámení",
|
||||
"OK": "OK",
|
||||
"Off": "Vypnuta",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"Comment, when used at the start of a line": "Kommentering som bruges i starten af en linje",
|
||||
"Compression": "Anvend komprimering",
|
||||
"Connection Error": "Tilslutnings fejl",
|
||||
"Connection Type": "Connection Type",
|
||||
"Copied from elsewhere": "Kopieret fra et andet sted",
|
||||
"Copied from original": "Kopieret fra originalen",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 the following Contributors:",
|
||||
@@ -74,6 +75,7 @@
|
||||
"Folder Label": "Folder Label",
|
||||
"Folder Master": "Mastermappe",
|
||||
"Folder Path": "Mappesti",
|
||||
"Folder Type": "Folder Type",
|
||||
"Folders": "Mapper",
|
||||
"GUI": "GUI",
|
||||
"GUI Authentication Password": "GUI-kodeord",
|
||||
@@ -98,10 +100,12 @@
|
||||
"Last File Received": "Sidste modtaget fil",
|
||||
"Last seen": "Sidst set",
|
||||
"Later": "Senere",
|
||||
"Listeners": "Listeners",
|
||||
"Local Discovery": "Lokal opslag",
|
||||
"Local State": "Lokal tilstand",
|
||||
"Local State (Total)": "Lokal tilstand (total)",
|
||||
"Major Upgrade": "Ny version",
|
||||
"Master": "Master",
|
||||
"Maximum Age": "Maks alder",
|
||||
"Metadata Only": "Kun metadata",
|
||||
"Minimum Free Disk Space": "Mindst ledig diskplads",
|
||||
@@ -113,6 +117,7 @@
|
||||
"Newest First": "Nyeste først",
|
||||
"No": "Nej",
|
||||
"No File Versioning": "Ingen filversion",
|
||||
"Normal": "Normal",
|
||||
"Notice": "OBS",
|
||||
"OK": "OK",
|
||||
"Off": "Slå fra",
|
||||
|
||||
@@ -21,17 +21,18 @@
|
||||
"An external command handles the versioning. It has to remove the file from the synced folder.": "Ein externer Programmaufruf handhabt die Versionierung. Es muss die Datei aus dem zu synchronisierendem Verzeichnis entfernen.",
|
||||
"Anonymous Usage Reporting": "Anonymer Nutzungsbericht",
|
||||
"Any devices configured on an introducer device will be added to this device as well.": "Alle Geräte, die beim Verteiler eingetragen sind, werden auch bei diesem Gerät eingetragen",
|
||||
"Automatic upgrades": "automatische Updates",
|
||||
"Automatic upgrades": "Automatische Updates aktivieren",
|
||||
"Be careful!": "Vorsicht!",
|
||||
"Bugs": "Fehler",
|
||||
"CPU Utilization": "Prozessorauslastung",
|
||||
"Changelog": "Änderungsprotokoll",
|
||||
"Clean out after": "Löschen nach",
|
||||
"Close": "Schließen",
|
||||
"Command": "Kommando",
|
||||
"Command": "Befehl",
|
||||
"Comment, when used at the start of a line": "Kommentar, wenn am Anfang der Zeile benutzt.",
|
||||
"Compression": "Komprimierung",
|
||||
"Connection Error": "Verbindungsfehler",
|
||||
"Connection Type": "Verbindungstyp",
|
||||
"Copied from elsewhere": "Von anderer Quelle kopiert",
|
||||
"Copied from original": "Vom Original kopiert",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 der folgenden Unterstützer:",
|
||||
@@ -74,6 +75,7 @@
|
||||
"Folder Label": "Verzeichnisbezeichnung",
|
||||
"Folder Master": "Master Verzeichnis - schreibgeschützt",
|
||||
"Folder Path": "Verzeichnispfad",
|
||||
"Folder Type": "Ordnertyp",
|
||||
"Folders": "Verzeichnisse",
|
||||
"GUI": "GUI",
|
||||
"GUI Authentication Password": "Passwort für Zugang zur Benutzeroberfläche",
|
||||
@@ -98,10 +100,12 @@
|
||||
"Last File Received": "Letzte Änderung",
|
||||
"Last seen": "Zuletzt online",
|
||||
"Later": "Später",
|
||||
"Listeners": "Lauscher",
|
||||
"Local Discovery": "Lokale Gerätesuche",
|
||||
"Local State": "Lokaler Status",
|
||||
"Local State (Total)": "Lokaler Status (Gesamt)",
|
||||
"Major Upgrade": "Hauptversionsupgrade",
|
||||
"Master": "Master",
|
||||
"Maximum Age": "Höchstalter",
|
||||
"Metadata Only": "Nur Metadaten",
|
||||
"Minimum Free Disk Space": "Minimal freier Festplattenspeicher",
|
||||
@@ -113,6 +117,7 @@
|
||||
"Newest First": "Neueste zuerst",
|
||||
"No": "Nein",
|
||||
"No File Versioning": "Keine Dateiversionierung",
|
||||
"Normal": "Normal",
|
||||
"Notice": "Hinweis",
|
||||
"OK": "OK",
|
||||
"Off": "Aus",
|
||||
@@ -240,5 +245,5 @@
|
||||
"items": "Objekte",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} möchte das Verzeichnis \"{{folder}}\" teilen.",
|
||||
"{%device%} wants to share folder \"{%folderLabel%}\" ({%folder%}).": "{{device}} möchte das Verzeichnis \"{{folderLabel}}\" ({{folder}}) teilen.",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} möchte das Verzeichnis \"{{folderLabel}}\" ({{folder}}) teilen."
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} möchte den Ordner \"{{folderLabel}}\" ({{folder}}) teilen."
|
||||
}
|
||||
@@ -32,6 +32,7 @@
|
||||
"Comment, when used at the start of a line": "Σχόλιο, όταν χρησιμοποιείται στην αρχή μιας γραμμής",
|
||||
"Compression": "Συμπίεση",
|
||||
"Connection Error": "Σφάλμα σύνδεσης",
|
||||
"Connection Type": "Connection Type",
|
||||
"Copied from elsewhere": "Έχει αντιγραφεί από κάπου αλλού",
|
||||
"Copied from original": "Έχει αντιγραφεί από το πρωτότυπο",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 the following Contributors:",
|
||||
@@ -74,6 +75,7 @@
|
||||
"Folder Label": "Folder Label",
|
||||
"Folder Master": "Να μην επιτρέπονται αλλαγές",
|
||||
"Folder Path": "Μονοπάτι φακέλου",
|
||||
"Folder Type": "Folder Type",
|
||||
"Folders": "Φάκελοι",
|
||||
"GUI": "Γραφικό περιβάλλον",
|
||||
"GUI Authentication Password": "Κωδικός για την πρόσβαση στη διεπαφή",
|
||||
@@ -98,10 +100,12 @@
|
||||
"Last File Received": "Πιο πρόσφατο αρχείο",
|
||||
"Last seen": "Τελευταία φορά συνδεδεμένος",
|
||||
"Later": "Αργότερα",
|
||||
"Listeners": "Listeners",
|
||||
"Local Discovery": "Τοπική ανεύρεση",
|
||||
"Local State": "Τοπική κατάσταση",
|
||||
"Local State (Total)": "Τοπική κατάσταση (συνολικά)",
|
||||
"Major Upgrade": "Σημαντική αναβάθμιση",
|
||||
"Master": "Master",
|
||||
"Maximum Age": "Μέγιστη ηλικία",
|
||||
"Metadata Only": "Μόνο μεταδεδομένα",
|
||||
"Minimum Free Disk Space": "Ελάχιστος ελεύθερος αποθηκευτικός χώρος",
|
||||
@@ -113,6 +117,7 @@
|
||||
"Newest First": "Το νεότερο πρώτα",
|
||||
"No": "Όχι",
|
||||
"No File Versioning": "Να μην τηρούνται εκδόσεις",
|
||||
"Normal": "Normal",
|
||||
"Notice": "Σημείωση",
|
||||
"OK": "OK",
|
||||
"Off": "Απενεργοποιημένο",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"Comment, when used at the start of a line": "Comment, when used at the start of a line",
|
||||
"Compression": "Compression",
|
||||
"Connection Error": "Connection Error",
|
||||
"Connection Type": "Connection Type",
|
||||
"Copied from elsewhere": "Copied from elsewhere",
|
||||
"Copied from original": "Copied from original",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 the following Contributors:",
|
||||
@@ -74,6 +75,7 @@
|
||||
"Folder Label": "Folder Label",
|
||||
"Folder Master": "Folder Master",
|
||||
"Folder Path": "Folder Path",
|
||||
"Folder Type": "Folder Type",
|
||||
"Folders": "Folders",
|
||||
"GUI": "GUI",
|
||||
"GUI Authentication Password": "GUI Authentication Password",
|
||||
@@ -98,10 +100,12 @@
|
||||
"Last File Received": "Last File Received",
|
||||
"Last seen": "Last seen",
|
||||
"Later": "Later",
|
||||
"Listeners": "Listeners",
|
||||
"Local Discovery": "Local Discovery",
|
||||
"Local State": "Local State",
|
||||
"Local State (Total)": "Local State (Total)",
|
||||
"Major Upgrade": "Major Upgrade",
|
||||
"Master": "Master",
|
||||
"Maximum Age": "Maximum Age",
|
||||
"Metadata Only": "Metadata Only",
|
||||
"Minimum Free Disk Space": "Minimum Free Disk Space",
|
||||
@@ -113,6 +117,7 @@
|
||||
"Newest First": "Newest First",
|
||||
"No": "No",
|
||||
"No File Versioning": "No File Versioning",
|
||||
"Normal": "Normal",
|
||||
"Notice": "Notice",
|
||||
"OK": "OK",
|
||||
"Off": "Off",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"Comment, when used at the start of a line": "Comment, when used at the start of a line",
|
||||
"Compression": "Compression",
|
||||
"Connection Error": "Connection Error",
|
||||
"Connection Type": "Connection Type",
|
||||
"Copied from elsewhere": "Copied from elsewhere",
|
||||
"Copied from original": "Copied from original",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 the following Contributors:",
|
||||
@@ -74,6 +75,7 @@
|
||||
"Folder Label": "Folder Label",
|
||||
"Folder Master": "Folder Master",
|
||||
"Folder Path": "Folder Path",
|
||||
"Folder Type": "Folder Type",
|
||||
"Folders": "Folders",
|
||||
"GUI": "GUI",
|
||||
"GUI Authentication Password": "GUI Authentication Password",
|
||||
@@ -98,10 +100,12 @@
|
||||
"Last File Received": "Last File Received",
|
||||
"Last seen": "Last seen",
|
||||
"Later": "Later",
|
||||
"Listeners": "Listeners",
|
||||
"Local Discovery": "Local Discovery",
|
||||
"Local State": "Local State",
|
||||
"Local State (Total)": "Local State (Total)",
|
||||
"Major Upgrade": "Major Upgrade",
|
||||
"Master": "Master",
|
||||
"Maximum Age": "Maximum Age",
|
||||
"Metadata Only": "Metadata Only",
|
||||
"Minimum Free Disk Space": "Minimum Free Disk Space",
|
||||
@@ -113,6 +117,7 @@
|
||||
"Newest First": "Newest First",
|
||||
"No": "No",
|
||||
"No File Versioning": "No File Versioning",
|
||||
"Normal": "Normal",
|
||||
"Notice": "Notice",
|
||||
"OK": "OK",
|
||||
"Off": "Off",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"Comment, when used at the start of a line": "Comentar, cuando se usa al comienzo de una línea",
|
||||
"Compression": "Compresión",
|
||||
"Connection Error": "Error de conexión",
|
||||
"Connection Type": "Connection Type",
|
||||
"Copied from elsewhere": "Copiado de otro sitio",
|
||||
"Copied from original": "Copiado del original",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 los siguientes Colaboradores:",
|
||||
@@ -74,6 +75,7 @@
|
||||
"Folder Label": "Etiqueta de la Carpeta",
|
||||
"Folder Master": "Carpeta principal",
|
||||
"Folder Path": "Ruta de la carpeta",
|
||||
"Folder Type": "Folder Type",
|
||||
"Folders": "Carpetas",
|
||||
"GUI": "GUI",
|
||||
"GUI Authentication Password": "Password de la Interfaz Gráfica de Usuario (GUI)",
|
||||
@@ -98,10 +100,12 @@
|
||||
"Last File Received": "Último fichero recibido",
|
||||
"Last seen": "Visto por última vez",
|
||||
"Later": "Más tarde",
|
||||
"Listeners": "Listeners",
|
||||
"Local Discovery": "Descubrimiento local",
|
||||
"Local State": "Estado local",
|
||||
"Local State (Total)": "Estado Local (Total)",
|
||||
"Major Upgrade": "Actualización importante",
|
||||
"Master": "Master",
|
||||
"Maximum Age": "Edad máxima",
|
||||
"Metadata Only": "Sólo metadatos",
|
||||
"Minimum Free Disk Space": "Espacio mínimo libre en disco",
|
||||
@@ -113,6 +117,7 @@
|
||||
"Newest First": "El más nuevo primero",
|
||||
"No": "No",
|
||||
"No File Versioning": "Sin versionado de fichero",
|
||||
"Normal": "Normal",
|
||||
"Notice": "Aviso",
|
||||
"OK": "OK",
|
||||
"Off": "Desconectar",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"Comment, when used at the start of a line": "Comentario, cuando es utilizado al inicio de una línea.",
|
||||
"Compression": "Compresión",
|
||||
"Connection Error": "Error de conexión",
|
||||
"Connection Type": "Connection Type",
|
||||
"Copied from elsewhere": "Copiado desde otra parte.",
|
||||
"Copied from original": "Copiado del original",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 los siguientes contribuidores:",
|
||||
@@ -74,6 +75,7 @@
|
||||
"Folder Label": "Folder Label",
|
||||
"Folder Master": "Repositorio maestro",
|
||||
"Folder Path": "Ruta del repositorio",
|
||||
"Folder Type": "Folder Type",
|
||||
"Folders": "Repositorios",
|
||||
"GUI": "GUI",
|
||||
"GUI Authentication Password": "Contraseña de autenticación de la GUI",
|
||||
@@ -98,10 +100,12 @@
|
||||
"Last File Received": "Último archivo recibido",
|
||||
"Last seen": "Visto por ultima vez",
|
||||
"Later": "Más tarde",
|
||||
"Listeners": "Listeners",
|
||||
"Local Discovery": "Búsqueda en red local",
|
||||
"Local State": "Estado local",
|
||||
"Local State (Total)": "Estado local (total)",
|
||||
"Major Upgrade": "Actualización mayor",
|
||||
"Master": "Master",
|
||||
"Maximum Age": "Edad máxima",
|
||||
"Metadata Only": "Sólo metadatos",
|
||||
"Minimum Free Disk Space": "Espacio mínimo libre en disco",
|
||||
@@ -113,6 +117,7 @@
|
||||
"Newest First": "Nuevo primero",
|
||||
"No": "No",
|
||||
"No File Versioning": "Sin control de versiones de archivos",
|
||||
"Normal": "Normal",
|
||||
"Notice": "Aviso",
|
||||
"OK": "OK",
|
||||
"Off": "Apagado",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"Comment, when used at the start of a line": "Kommentti, käytettäessä rivin alussa",
|
||||
"Compression": "Pakkaus",
|
||||
"Connection Error": "Yhteysvirhe",
|
||||
"Connection Type": "Connection Type",
|
||||
"Copied from elsewhere": "Kopioitu muualta",
|
||||
"Copied from original": "Kopioitu alkuperäisestä lähteestä",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 the following Contributors:",
|
||||
@@ -74,6 +75,7 @@
|
||||
"Folder Label": "Folder Label",
|
||||
"Folder Master": "Hallitsijakansio",
|
||||
"Folder Path": "Kansion polku",
|
||||
"Folder Type": "Folder Type",
|
||||
"Folders": "Kansiot",
|
||||
"GUI": "GUI",
|
||||
"GUI Authentication Password": "GUI:n salasana",
|
||||
@@ -98,10 +100,12 @@
|
||||
"Last File Received": "Viimeksi vastaanotettu tiedosto",
|
||||
"Last seen": "Nähty viimeksi",
|
||||
"Later": "Myöhemmin",
|
||||
"Listeners": "Listeners",
|
||||
"Local Discovery": "Paikallinen etsintä",
|
||||
"Local State": "Paikallinen tila",
|
||||
"Local State (Total)": "Paikallinen tila (Yhteensä)",
|
||||
"Major Upgrade": "Pääversion päivitys.",
|
||||
"Master": "Master",
|
||||
"Maximum Age": "Maksimi-ikä",
|
||||
"Metadata Only": "Vain metadata",
|
||||
"Minimum Free Disk Space": "Vapaan levytilan vähimmäismäärä",
|
||||
@@ -113,6 +117,7 @@
|
||||
"Newest First": "Uusin ensin",
|
||||
"No": "Ei",
|
||||
"No File Versioning": "Ei tiedostoversiointia",
|
||||
"Normal": "Normal",
|
||||
"Notice": "Huomautus",
|
||||
"OK": "OK",
|
||||
"Off": "Pois",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"Comment, when used at the start of a line": "Commentaire lorsque utilisé en début de ligne",
|
||||
"Compression": "Compression",
|
||||
"Connection Error": "Erreur de connexion",
|
||||
"Connection Type": "Connection Type",
|
||||
"Copied from elsewhere": "Copié d'ailleurs",
|
||||
"Copied from original": "Copié depuis l'original",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 the following Contributors:",
|
||||
@@ -74,6 +75,7 @@
|
||||
"Folder Label": "Folder Label",
|
||||
"Folder Master": "Répertoire maître",
|
||||
"Folder Path": "Chemin du répertoire",
|
||||
"Folder Type": "Folder Type",
|
||||
"Folders": "Dossiers",
|
||||
"GUI": "GUI",
|
||||
"GUI Authentication Password": "Mot de passe d'authentification GUI",
|
||||
@@ -98,10 +100,12 @@
|
||||
"Last File Received": "Dernier fichier reçu",
|
||||
"Last seen": "Dernière apparition",
|
||||
"Later": "Plus tard",
|
||||
"Listeners": "Listeners",
|
||||
"Local Discovery": "Recherche locale",
|
||||
"Local State": "État local",
|
||||
"Local State (Total)": "État local (Total)",
|
||||
"Major Upgrade": "Mise à jour majeure",
|
||||
"Master": "Master",
|
||||
"Maximum Age": "Ancienneté maximum",
|
||||
"Metadata Only": "Métadonnées uniquement",
|
||||
"Minimum Free Disk Space": "Espace disque libre minimum",
|
||||
@@ -113,6 +117,7 @@
|
||||
"Newest First": "Les plus récents en premier",
|
||||
"No": "Non",
|
||||
"No File Versioning": "Pas de version de fichier",
|
||||
"Normal": "Normal",
|
||||
"Notice": "Notification",
|
||||
"OK": "OK",
|
||||
"Off": "Éteint",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"Comment, when used at the start of a line": "Commentaire lorsque utilisé en début de ligne",
|
||||
"Compression": "Compression",
|
||||
"Connection Error": "Erreur de connexion",
|
||||
"Connection Type": "Type de connexion",
|
||||
"Copied from elsewhere": "Copié d'ailleurs",
|
||||
"Copied from original": "Copié depuis l'original",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016, les contributeurs suivants:",
|
||||
@@ -74,6 +75,7 @@
|
||||
"Folder Label": "Étiquette du dossier",
|
||||
"Folder Master": "Dossier maître",
|
||||
"Folder Path": "Chemin du dossier",
|
||||
"Folder Type": "Type de répertoire",
|
||||
"Folders": "Dossiers",
|
||||
"GUI": "GUI",
|
||||
"GUI Authentication Password": "Mot de passe d'authentification GUI",
|
||||
@@ -98,10 +100,12 @@
|
||||
"Last File Received": "Dernier fichier reçu",
|
||||
"Last seen": "Dernière apparition",
|
||||
"Later": "Plus tard",
|
||||
"Listeners": "Systèmes en écoute",
|
||||
"Local Discovery": "Recherche locale",
|
||||
"Local State": "État local",
|
||||
"Local State (Total)": "État local (Total)",
|
||||
"Major Upgrade": "Mise à jour majeure",
|
||||
"Master": "Maitre",
|
||||
"Maximum Age": "Ancienneté maximum",
|
||||
"Metadata Only": "Métadonnées uniquement",
|
||||
"Minimum Free Disk Space": "Espace disque libre minimum",
|
||||
@@ -113,6 +117,7 @@
|
||||
"Newest First": "Les plus récents en premier",
|
||||
"No": "Non",
|
||||
"No File Versioning": "Pas de version de fichier",
|
||||
"Normal": "Normal",
|
||||
"Notice": "Notification",
|
||||
"OK": "OK",
|
||||
"Off": "Éteint",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"Comment, when used at the start of a line": "Kommentaar, wannear as brûkt by it begjin fan in rige",
|
||||
"Compression": "Kompresje",
|
||||
"Connection Error": "Ferbiningsflater",
|
||||
"Connection Type": "Ferbiningstype",
|
||||
"Copied from elsewhere": "Oernommen fan earne oars",
|
||||
"Copied from original": "Oernommen fan orizjineel",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 de folgende bydragers:",
|
||||
@@ -74,6 +75,7 @@
|
||||
"Folder Label": "Map-opskrift",
|
||||
"Folder Master": "Map-master",
|
||||
"Folder Path": "Map-paad",
|
||||
"Folder Type": "Maptype",
|
||||
"Folders": "Mappen",
|
||||
"GUI": "GUI",
|
||||
"GUI Authentication Password": "Wachtwurd foar ferifikaasje yn GUI",
|
||||
@@ -98,10 +100,12 @@
|
||||
"Last File Received": "Leste triem ûntfongen",
|
||||
"Last seen": "Lêst sjoen",
|
||||
"Later": "Letter",
|
||||
"Listeners": "Harkers",
|
||||
"Local Discovery": "Lokale ûntdekking",
|
||||
"Local State": "Lokale tastân",
|
||||
"Local State (Total)": "Lokale tastân (Folledich)",
|
||||
"Major Upgrade": "Wichtige fernijing",
|
||||
"Master": "Master",
|
||||
"Maximum Age": "Maksimale âldens",
|
||||
"Metadata Only": "Allinnich metadata",
|
||||
"Minimum Free Disk Space": "Minimale frije skiifromte",
|
||||
@@ -113,6 +117,7 @@
|
||||
"Newest First": "Nijste earst",
|
||||
"No": "Nee",
|
||||
"No File Versioning": "Gjin triemferzjebehear",
|
||||
"Normal": "Normaal",
|
||||
"Notice": "Notysje",
|
||||
"OK": "Okee",
|
||||
"Off": "Ut",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"Comment, when used at the start of a line": "Megjegyzés, a sor elején használva",
|
||||
"Compression": "Tömörítés",
|
||||
"Connection Error": "Kapcsolódási hiba",
|
||||
"Connection Type": "Kapcsolat típus",
|
||||
"Copied from elsewhere": "Másolva máshonnan",
|
||||
"Copied from original": "Másolva az eredetiről",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Szerzői jog © 2014-2016 az alábbi közreműködők:",
|
||||
@@ -74,6 +75,7 @@
|
||||
"Folder Label": "Mappa címke",
|
||||
"Folder Master": "Központi mappa",
|
||||
"Folder Path": "Mappa elérési útja",
|
||||
"Folder Type": "Mappa típus",
|
||||
"Folders": "Mappák",
|
||||
"GUI": "Grafikus felület",
|
||||
"GUI Authentication Password": "Grafikus felület jelszava",
|
||||
@@ -98,10 +100,12 @@
|
||||
"Last File Received": "Utolsó beérkezett fájl",
|
||||
"Last seen": "Utoljára látva",
|
||||
"Later": "Később",
|
||||
"Listeners": "Kapcsolatok",
|
||||
"Local Discovery": "Helyi felfedezés",
|
||||
"Local State": "Helyi állapot",
|
||||
"Local State (Total)": "Helyi állapot (Teljes)",
|
||||
"Major Upgrade": "Főverzió frissítés",
|
||||
"Master": "Központi",
|
||||
"Maximum Age": "Maximális kor",
|
||||
"Metadata Only": "Csak metaadatok",
|
||||
"Minimum Free Disk Space": "Minimális szabad lemezterület",
|
||||
@@ -113,6 +117,7 @@
|
||||
"Newest First": "Újabb először",
|
||||
"No": "Nem",
|
||||
"No File Versioning": "Nincs fájl verziókövetés",
|
||||
"Normal": "Normál",
|
||||
"Notice": "Megjegyzés",
|
||||
"OK": "Rendben",
|
||||
"Off": "Kikapcsolva",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"Comment, when used at the start of a line": "Komentar, digunakan saat awal baris",
|
||||
"Compression": "Kompresi",
|
||||
"Connection Error": "Koneksi Galat",
|
||||
"Connection Type": "Connection Type",
|
||||
"Copied from elsewhere": "Tersalin dari tempat lain",
|
||||
"Copied from original": "Tersalin dari asal",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 the following Contributors:",
|
||||
@@ -74,6 +75,7 @@
|
||||
"Folder Label": "Folder Label",
|
||||
"Folder Master": "Master Folder",
|
||||
"Folder Path": "Path Folder",
|
||||
"Folder Type": "Folder Type",
|
||||
"Folders": "Folder",
|
||||
"GUI": "GUI",
|
||||
"GUI Authentication Password": "Sandi Otentikasi GUI",
|
||||
@@ -98,10 +100,12 @@
|
||||
"Last File Received": "Last File Received",
|
||||
"Last seen": "Last seen",
|
||||
"Later": "Later",
|
||||
"Listeners": "Listeners",
|
||||
"Local Discovery": "Local Discovery",
|
||||
"Local State": "Local State",
|
||||
"Local State (Total)": "Local State (Total)",
|
||||
"Major Upgrade": "Major Upgrade",
|
||||
"Master": "Master",
|
||||
"Maximum Age": "Maximum Age",
|
||||
"Metadata Only": "Metadata Only",
|
||||
"Minimum Free Disk Space": "Minimum Free Disk Space",
|
||||
@@ -113,6 +117,7 @@
|
||||
"Newest First": "Newest First",
|
||||
"No": "No",
|
||||
"No File Versioning": "No File Versioning",
|
||||
"Normal": "Normal",
|
||||
"Notice": "Notice",
|
||||
"OK": "OK",
|
||||
"Off": "Off",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"Comment, when used at the start of a line": "Per commentare, va inserito all'inizio di una riga",
|
||||
"Compression": "Compressione",
|
||||
"Connection Error": "Errore di Connessione",
|
||||
"Connection Type": "Tipo di Connessione",
|
||||
"Copied from elsewhere": "Copiato da qualche altra parte",
|
||||
"Copied from original": "Copiato dall'originale",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 i seguenti Collaboratori:",
|
||||
@@ -74,6 +75,7 @@
|
||||
"Folder Label": "Etichetta per la cartella",
|
||||
"Folder Master": "Cartella Principale",
|
||||
"Folder Path": "Percorso Cartella",
|
||||
"Folder Type": "Tipo di Cartella",
|
||||
"Folders": "Cartelle",
|
||||
"GUI": "Interfaccia grafica utente",
|
||||
"GUI Authentication Password": "Password di Autenticazione dell'Utente",
|
||||
@@ -98,10 +100,12 @@
|
||||
"Last File Received": "Ultimo File Ricevuto",
|
||||
"Last seen": "Ultima connessione",
|
||||
"Later": "Più Tardi",
|
||||
"Listeners": "In ascolto",
|
||||
"Local Discovery": "Individuazione Locale",
|
||||
"Local State": "Stato Locale",
|
||||
"Local State (Total)": "Stato Locale (Totale)",
|
||||
"Major Upgrade": "Aggiornamento principale",
|
||||
"Master": "Principale",
|
||||
"Maximum Age": "Durata Massima",
|
||||
"Metadata Only": "Solo i Metadati",
|
||||
"Minimum Free Disk Space": "Minimo spazio libero su disco",
|
||||
@@ -113,6 +117,7 @@
|
||||
"Newest First": "Prima il più recente",
|
||||
"No": "No",
|
||||
"No File Versioning": "Nessun Controllo Versione",
|
||||
"Normal": "Normale",
|
||||
"Notice": "Avviso",
|
||||
"OK": "OK",
|
||||
"Off": "Disattiva",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"Comment, when used at the start of a line": "行頭で使用された場合、コメント行",
|
||||
"Compression": "圧縮",
|
||||
"Connection Error": "接続エラー",
|
||||
"Connection Type": "接続種別",
|
||||
"Copied from elsewhere": "別ファイルからコピー済",
|
||||
"Copied from original": "元ファイルからコピー済",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 the following Contributors:",
|
||||
@@ -74,6 +75,7 @@
|
||||
"Folder Label": "フォルダー名",
|
||||
"Folder Master": "フォルダーのマスター",
|
||||
"Folder Path": "フォルダーパス",
|
||||
"Folder Type": "フォルダーの種類",
|
||||
"Folders": "フォルダー",
|
||||
"GUI": "GUI",
|
||||
"GUI Authentication Password": "GUI認証パスワード",
|
||||
@@ -98,10 +100,12 @@
|
||||
"Last File Received": "最後に受信したファイル",
|
||||
"Last seen": "最終接続日時",
|
||||
"Later": "後で設定",
|
||||
"Listeners": "待ち受けポート",
|
||||
"Local Discovery": "LAN内で探索",
|
||||
"Local State": "ローカル状態",
|
||||
"Local State (Total)": "ローカル状態 (合計)",
|
||||
"Major Upgrade": "メジャーアップグレード",
|
||||
"Master": "マスター",
|
||||
"Maximum Age": "最大寿命",
|
||||
"Metadata Only": "メタデータのみ",
|
||||
"Minimum Free Disk Space": "同期を停止する最小空きディスク容量",
|
||||
@@ -113,6 +117,7 @@
|
||||
"Newest First": "新しい順",
|
||||
"No": "いいえ",
|
||||
"No File Versioning": "バージョン管理をしない",
|
||||
"Normal": "通常",
|
||||
"Notice": "通知",
|
||||
"OK": "OK",
|
||||
"Off": "オフ",
|
||||
@@ -192,7 +197,7 @@
|
||||
"The device ID cannot be blank.": "デバイスIDは空欄にできません。",
|
||||
"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).": "ここに入力するデバイスIDは、接続したい相手側デバイスの [メニュー]→[IDを表示] で確認することができます。スペースとハイフンは入力しなくてもかまいません。",
|
||||
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "ここに入力するデバイスIDは、接続したい相手側デバイスの [メニュー]→[IDを表示] で確認することができます。スペースとハイフンは入力しなくてもかまいません。",
|
||||
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "利用状況レポートは暗号化されて毎日送信されます。この情報はプラットフォーム、フォルダの大きさ、アプリのバージョンを調査するために使われます。レポートのデータが変更された場合、このダイアログがまた表示されます。",
|
||||
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "利用状況レポートは暗号化されて毎日送信されます。この情報はプラットフォーム、フォルダーの大きさ、アプリのバージョンを調査するために使われます。送信するデータセットが変更された場合、このダイアログで再度確認が求められます。",
|
||||
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "入力されたデバイスIDが正しくありません。デバイスIDは、52文字または56文字のアルファベットと数字からなる文字列です。スペースとハイフンは入力してもしなくてもかまいません。",
|
||||
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "第1コマンドライン引数はフォルダーのパス、第2引数はフォルダー内の相対パスです。",
|
||||
"The folder ID cannot be blank.": "フォルダーIDは空欄にできません。",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"A device with that ID is already added.": "A device with that ID is already added.",
|
||||
"A negative number of days doesn't make sense.": "A negative number of days doesn't make sense.",
|
||||
"A device with that ID is already added.": "이 기기 ID는 이미 추가되었습니다.",
|
||||
"A negative number of days doesn't make sense.": "음수로는 지정할 수 없습니다.",
|
||||
"A new major version may not be compatible with previous versions.": "새로운 메이저 버전은 이전 버전과 호환되지 않을 수 있습니다.",
|
||||
"API Key": "API 키",
|
||||
"About": " 정보",
|
||||
@@ -8,13 +8,13 @@
|
||||
"Add": "추가",
|
||||
"Add Device": "기기 추가",
|
||||
"Add Folder": "폴더 추가",
|
||||
"Add Remote Device": "Add Remote Device",
|
||||
"Add Remote Device": "다른 기기 추가",
|
||||
"Add new folder?": "새로운 폴더를 추가하시겠습니까?",
|
||||
"Address": "주소",
|
||||
"Addresses": "주소",
|
||||
"Advanced": "Advanced",
|
||||
"Advanced Configuration": "Advanced Configuration",
|
||||
"Advanced settings": "Advanced settings",
|
||||
"Advanced": "고급",
|
||||
"Advanced Configuration": "고급 설정",
|
||||
"Advanced settings": "고급 설정",
|
||||
"All Data": "전체 데이터",
|
||||
"Allow Anonymous Usage Reporting?": "익명 사용 보고서를 보내시겠습니까?",
|
||||
"Alphabetic": "알파벳순",
|
||||
@@ -22,31 +22,32 @@
|
||||
"Anonymous Usage Reporting": "익명 사용 보고서",
|
||||
"Any devices configured on an introducer device will be added to this device as well.": "유도 장치에 추가된 기기들은 이 기기에도 동시에 추가됩니다.",
|
||||
"Automatic upgrades": "자동 업데이트",
|
||||
"Be careful!": "Be careful!",
|
||||
"Be careful!": "주의!",
|
||||
"Bugs": "버그",
|
||||
"CPU Utilization": "CPU 사용률",
|
||||
"Changelog": "바뀐 점",
|
||||
"Clean out after": "Clean out after",
|
||||
"Clean out after": "삭제 후",
|
||||
"Close": "닫기",
|
||||
"Command": "커맨드",
|
||||
"Comment, when used at the start of a line": "명령행에서 시작을 할수 있어요.",
|
||||
"Compression": "압축",
|
||||
"Connection Error": "연결 에러",
|
||||
"Connection Type": "연결 종류",
|
||||
"Copied from elsewhere": "다른 곳에서 복사됨",
|
||||
"Copied from original": "원본에서 복사됨",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 the following Contributors:",
|
||||
"Copyright © 2015 the following Contributors:": "Copyright © 2015 the following Contributors:",
|
||||
"Danger!": "Danger!",
|
||||
"Danger!": "경고!",
|
||||
"Delete": "삭제",
|
||||
"Deleted": "Deleted",
|
||||
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Device \"{{name}}\" ({{device}} at {{address}}) wants to connect. Add new device?",
|
||||
"Deleted": "삭제됨",
|
||||
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "다른 기기 {{device}} ({{address}}) 에서 접속을 요청했습니다. 새 장치를 추가하시겠습니까?",
|
||||
"Device ID": "기기 ID",
|
||||
"Device Identification": "기기 식별자",
|
||||
"Device Name": "기기 이름",
|
||||
"Device {%device%} ({%address%}) wants to connect. Add new device?": "다른 기기 {{device}} ({{address}}) 에서 접속을 요청했습니다. 새 장치를 추가하시겠습니까?",
|
||||
"Devices": "기기",
|
||||
"Disconnected": "연결 끊김",
|
||||
"Discovery": "Discovery",
|
||||
"Discovery": "탐색",
|
||||
"Documentation": "문서",
|
||||
"Download Rate": "다운로드 속도",
|
||||
"Downloaded": "다운로드됨",
|
||||
@@ -55,25 +56,26 @@
|
||||
"Edit Device": "기기 편집",
|
||||
"Edit Folder": "폴더 편집",
|
||||
"Editing": "편집",
|
||||
"Enable NAT traversal": "Enable NAT traversal",
|
||||
"Enable Relaying": "Enable Relaying",
|
||||
"Enable NAT traversal": "NAT traversal 활성화",
|
||||
"Enable Relaying": "Relaying 활성화",
|
||||
"Enable UPnP": "UPnP 활성화",
|
||||
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.",
|
||||
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "주소 자동 검색을 하기 위해서는 \"ip:port\" 형식의 주소들을 쉼표로 구분해서 입력하거나 \"dynamic\"을 입력하세요.",
|
||||
"Enter ignore patterns, one per line.": "무시할 패턴을 한 줄에 하나씩 입력하세요.",
|
||||
"Error": "오류",
|
||||
"External File Versioning": "외부 파일 버전 관리",
|
||||
"Failed Items": "Failed Items",
|
||||
"Failed Items": "실패한 항목",
|
||||
"File Pull Order": "파일 동기화 순서",
|
||||
"File Versioning": "파일 버전 관리",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT file systems.": "파일을 동기화할 때 파일 권한이 무시됩니다. FAT 파일 시스템에서 사용하세요.",
|
||||
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "Files are moved to .stversions folder when replaced or deleted by Syncthing.",
|
||||
"Files are moved to .stversions folder when replaced or deleted by Syncthing.": "파일이 Syncthing에 의해서 교체되거나 삭제되면 .stversions 폴더로 이동됩니다.",
|
||||
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by Syncthing.": "파일이 Syncthing에 의해서 교체되거나 삭제되면 .stversions 폴더에 있는 날짜가 바뀐 버전으로 이동됩니다.",
|
||||
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "다른 장치가 파일을 편집할 수 없으며 반드시 이 장치의 내용을 기준으로 동기화합니다.",
|
||||
"Folder": "Folder",
|
||||
"Folder": "폴더",
|
||||
"Folder ID": "폴더 ID",
|
||||
"Folder Label": "Folder Label",
|
||||
"Folder Label": "폴더 라벨",
|
||||
"Folder Master": "폴더 소유자",
|
||||
"Folder Path": "폴더 경로",
|
||||
"Folder Type": "폴더 유형",
|
||||
"Folders": "폴더",
|
||||
"GUI": "GUI",
|
||||
"GUI Authentication Password": "GUI 인증 비밀번호",
|
||||
@@ -82,15 +84,15 @@
|
||||
"Generate": "생성",
|
||||
"Global Discovery": "글로벌 탐색",
|
||||
"Global Discovery Server": "글로벌 탐색 서버",
|
||||
"Global Discovery Servers": "Global Discovery Servers",
|
||||
"Global Discovery Servers": "글로벌 탐색 서버",
|
||||
"Global State": "글로벌 서버 상태",
|
||||
"Help": "도움말",
|
||||
"Home page": "Home page",
|
||||
"Home page": "홈페이지",
|
||||
"Ignore": "무시",
|
||||
"Ignore Patterns": "패턴 무시",
|
||||
"Ignore Permissions": "권한 무시",
|
||||
"Incoming Rate Limit (KiB/s)": "다운로드 속도 제한 (KiB/S)",
|
||||
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Incorrect configuration may damage your folder contents and render Syncthing inoperable.",
|
||||
"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": "버전 보관",
|
||||
@@ -98,13 +100,15 @@
|
||||
"Last File Received": "마지막으로 받은 파일",
|
||||
"Last seen": "마지막 접속",
|
||||
"Later": "나중에",
|
||||
"Listeners": "수신자",
|
||||
"Local Discovery": "로컬 노드 검색",
|
||||
"Local State": "로컬 상태",
|
||||
"Local State (Total)": "Local State (Total)",
|
||||
"Local State (Total)": "로컬 상태 (합계)",
|
||||
"Major Upgrade": "메이저 업데이트",
|
||||
"Master": "마스터",
|
||||
"Maximum Age": "최대 보존 기간",
|
||||
"Metadata Only": "메타데이터만",
|
||||
"Minimum Free Disk Space": "Minimum Free Disk Space",
|
||||
"Minimum Free Disk Space": "최소 여유 디스크 용량",
|
||||
"Move to top of queue": "대기열 상단으로 이동",
|
||||
"Multi level wildcard (matches multiple directory levels)": "다중 레벨 와일드 카드 (여러 단계의 디렉토리와 일치하는 경우)",
|
||||
"Never": "사용 안 함",
|
||||
@@ -113,45 +117,46 @@
|
||||
"Newest First": "새로운 파일순",
|
||||
"No": "아니오",
|
||||
"No File Versioning": "파일 버전 관리 안 함",
|
||||
"Normal": "일반",
|
||||
"Notice": "공지",
|
||||
"OK": "확인",
|
||||
"Off": "꺼짐",
|
||||
"Oldest First": "오래된 파일순",
|
||||
"Optional descriptive label for the folder. Can be different on each device.": "Optional descriptive label for the folder. Can be different on each device.",
|
||||
"Options": "Options",
|
||||
"Out of Sync": "Out of Sync",
|
||||
"Optional descriptive label for the folder. Can be different on each device.": "폴더 라벨은 편의를 위한 것입니다. 기기마다 다르게 설정할 수 있습니다.",
|
||||
"Options": "옵션",
|
||||
"Out of Sync": "동기화 오류",
|
||||
"Out of Sync Items": "동기화되지 않은 항목",
|
||||
"Outgoing Rate Limit (KiB/s)": "업로드 속도 제한 (KiB/s)",
|
||||
"Override Changes": "덮어쓰기",
|
||||
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "로컬 컴퓨터에 있는 폴더의 경로를 지정합니다. 존재하지 않는 폴더일 경우 자동으로 생성됩니다. 물결 기호 (~)는 아래와 같은 폴더를 나타냅니다.",
|
||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "버전을 보관할 경로 (비워둘 시 기본값 .stversions 폴더로 지정됨)",
|
||||
"Pause": "Pause",
|
||||
"Paused": "Paused",
|
||||
"Pause": "일시 중지",
|
||||
"Paused": "일시 중지됨",
|
||||
"Please consult the release notes before performing a major upgrade.": "메이저 업데이트를 하기 전에 먼저 릴리즈 노트를 살펴보세요.",
|
||||
"Please set a GUI Authentication User and Password in the Settings dialog.": "Please set a GUI Authentication User and Password in the Settings dialog.",
|
||||
"Please set a GUI Authentication User and Password in the Settings dialog.": "설정에서 GUI 인증용 User와 암호를 입력해주세요.",
|
||||
"Please wait": "기다려 주십시오",
|
||||
"Preview": "미리보기",
|
||||
"Preview Usage Report": "사용 보고서 미리보기",
|
||||
"Quick guide to supported patterns": "지원하는 패턴에 대한 빠른 도움말",
|
||||
"RAM Utilization": "RAM 사용량",
|
||||
"Random": "무작위",
|
||||
"Relay Servers": "Relay Servers",
|
||||
"Relayed via": "Relayed via",
|
||||
"Relays": "Relays",
|
||||
"Relay Servers": "중계 서버",
|
||||
"Relayed via": "중계 중인 서버 주소",
|
||||
"Relays": "중계",
|
||||
"Release Notes": "릴리즈 노트",
|
||||
"Remote Devices": "Remote Devices",
|
||||
"Remove": "Remove",
|
||||
"Required identifier for the folder. Must be the same on all cluster devices.": "Required identifier for the folder. Must be the same on all cluster devices.",
|
||||
"Remote Devices": "원격 기기",
|
||||
"Remove": "삭제",
|
||||
"Required identifier for the folder. Must be the same on all cluster devices.": "폴더 식별자가 필요합니다. 모든 장치에서 동일해야 합니다.",
|
||||
"Rescan": "재탐색",
|
||||
"Rescan All": "전체 재탐색",
|
||||
"Rescan Interval": "재탐색 간격",
|
||||
"Restart": "재시작",
|
||||
"Restart Needed": "재시작 필요함",
|
||||
"Restarting": "재시작 중",
|
||||
"Resume": "Resume",
|
||||
"Resume": "재개",
|
||||
"Reused": "재개",
|
||||
"Save": "저장",
|
||||
"Scan Time Remaining": "Scan Time Remaining",
|
||||
"Scan Time Remaining": "탐색 남은 시간",
|
||||
"Scanning": "탐색중",
|
||||
"Select the devices to share this folder with.": "이 폴더를 공유할 장치를 선택합니다.",
|
||||
"Select the folders to share with this device.": "이 장치와 공유할 폴더를 선택합니다.",
|
||||
@@ -164,7 +169,7 @@
|
||||
"Shared With": "~와 공유",
|
||||
"Short identifier for the folder. Must be the same on all cluster devices.": "간단한 폴더 식별자입니다. 모든 장치에서 동일해야 합니다.",
|
||||
"Show ID": "내 기기 ID",
|
||||
"Show QR": "Show QR",
|
||||
"Show QR": "QR 코드 보기",
|
||||
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "장치에 대한 아이디로 표시됩니다. 옵션에 얻은 기본이름으로 다른장치에 통보합니다.",
|
||||
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "아이디가 비어있는 경우 기본 값으로 다른 장치에 업데이트 됩니다.",
|
||||
"Shutdown": "종료",
|
||||
@@ -175,7 +180,7 @@
|
||||
"Source Code": "소스 코드",
|
||||
"Staggered File Versioning": "타임스탬프 기준 파일 버전 관리",
|
||||
"Start Browser": "브라우저 열기",
|
||||
"Statistics": "Statistics",
|
||||
"Statistics": "통계",
|
||||
"Stopped": "중지됨",
|
||||
"Support": "지원",
|
||||
"Sync Protocol Listen Addresses": "동기화 프로토콜 수신 주소",
|
||||
@@ -186,11 +191,11 @@
|
||||
"Syncthing is upgrading.": "Syncthing이 업데이트 중입니다.",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing이 중지되었거나 인터넷 연결에 문제가 있는 것 같습니다. 재시도 중입니다...",
|
||||
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing에서 요청을 처리하는 중에 문제가 발생했습니다. 계속 문제가 발생하면 페이지를 다시 불러오거나 Syncthing을 재시작해 보세요.",
|
||||
"The Syncthing admin interface is configured to allow remote access without a password.": "The Syncthing admin interface is configured to allow remote access without a password.",
|
||||
"The Syncthing admin interface is configured to allow remote access without a password.": "Syncthing 관리자 인터페이스가 암호 없이 원격 접속이 허가되도록 설정되었습니다.",
|
||||
"The aggregated statistics are publicly available at {%url%}.": "수집된 통계는 {{URL}} 에서 공개적으로 볼 수 있습니다.",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "설정이 저장되었지만 활성화되지 않았습니다. 설정을 활성화 하려면 Syncthing을 다시 시작하세요.",
|
||||
"The device ID cannot be blank.": "기기 ID는 비워 둘 수 없습니다.",
|
||||
"The device ID to enter here can be found in the \"Actions > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "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).",
|
||||
"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).": "여기에 입력한 기기 ID가 다른 장치의 \"동작 - ID 보기\"에 표시됩니다. 공백과 하이픈은 세지 않습니다. 즉 무시됩니다.",
|
||||
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "여기에 입력한 기기 ID가 다른 장치의 \"편집 - ID 보기\"에 표시됩니다. 공백과 하이픈은 세지 않습니다. 즉 무시됩니다.",
|
||||
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "암호화된 사용 보고서는 매일 전송됩니다 사용 중인 플랫폼과 폴더 크기, 앱 버전이 포함되어 있습니다. 전송되는 데이터가 변경되면 다시 이 대화 상자가 나타납니다.",
|
||||
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "입력한 기기 ID가 올바르지 않습니다. 52/56자의 알파벳과 숫자로 구성되어 있으며, 공백과 하이픈은 포함되지 않습니다.",
|
||||
@@ -200,27 +205,27 @@
|
||||
"The folder ID must be unique.": "폴더 ID는 중복될 수 없습니다.",
|
||||
"The folder path cannot be blank.": "폴더 경로는 비워 둘 수 없습니다.",
|
||||
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "다음과 같은 간격이 사용됩니다: 첫 한 시간 동안은 버전이 매 30초마다 유지되며, 첫 하루 동안은 매 시간, 첫 한 달 동안은 매 일마다 유지됩니다. 그리고 최대 날짜까지는 버전이 매 주마다 유지됩니다.",
|
||||
"The following items could not be synchronized.": "The following items could not be synchronized.",
|
||||
"The following items could not be synchronized.": "이 항목들은 동기화 할 수 없습니다.",
|
||||
"The maximum age must be a number and cannot be blank.": "최대 보존 기간은 숫자여야 하며 비워 둘 수 없습니다.",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "버전을 유지할 최대 시간을 지정합니다. 일단위이며 버전을 계속 유지하려면 0을 입력하세요,",
|
||||
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).",
|
||||
"The number of days must be a number and cannot be blank.": "The number of days must be a number and cannot be blank.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "The number of days to keep files in the trash can. Zero means forever.",
|
||||
"The minimum free disk space percentage must be a non-negative number between 0 and 100 (inclusive).": "최소 여유 디스크 용량의 퍼센티지 설정은 0부터 100 까지 가능합니다.",
|
||||
"The number of days must be a number and cannot be blank.": "날짜는 숫자여야 하며 비워 둘 수 없습니다.",
|
||||
"The number of days to keep files in the trash can. Zero means forever.": "설정된 날짜 동안 파일이 휴지통에 보관됩니다. 0은 무제한입니다.",
|
||||
"The number of old versions to keep, per file.": "각 파일별로 유지할 이전 버전의 개수를 지정합니다.",
|
||||
"The number of versions must be a number and cannot be blank.": "버전 개수는 숫자여야 하며 비워 둘 수 없습니다.",
|
||||
"The path cannot be blank.": "경로는 비워 둘 수 없습니다.",
|
||||
"The rate limit must be a non-negative number (0: no limit)": "The rate limit must be a non-negative number (0: no limit)",
|
||||
"The rate limit must be a non-negative number (0: no limit)": "대역폭 제한 설정은 반드시 양수로 입력해야 합니다 (0: 무제한)",
|
||||
"The rescan interval must be a non-negative number of seconds.": "재검색 간격은 초단위이며 양수로 입력해야 합니다.",
|
||||
"They are retried automatically and will be synced when the error is resolved.": "They are retried automatically and will be synced when the error is resolved.",
|
||||
"This Device": "This Device",
|
||||
"This can easily give hackers access to read and change any files on your computer.": "This can easily give hackers access to read and change any files on your computer.",
|
||||
"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": "Trash Can File Versioning",
|
||||
"Trash Can File Versioning": "휴지통을 통한 파일 버전 관리",
|
||||
"Unknown": "알 수 없음",
|
||||
"Unshared": "공유되지 않음",
|
||||
"Unused": "사용되지 않음",
|
||||
"Up to Date": "최신 데이터",
|
||||
"Updated": "Updated",
|
||||
"Updated": "업데이트 완료",
|
||||
"Upgrade": "업데이트",
|
||||
"Upgrade To {%version%}": "{{version}} 으로 업데이트",
|
||||
"Upgrading": "업데이트 중",
|
||||
@@ -230,15 +235,15 @@
|
||||
"Version": "버전",
|
||||
"Versions Path": "버전 저장 경로",
|
||||
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "최대 보존 기간보다 오래되었거나 지정한 개수를 넘긴 버전은 자동으로 삭제됩니다.",
|
||||
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Warning, this path is a subdirectory of an existing folder \"{{otherFolder}}\".",
|
||||
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "경고, 이 경로는 현재 존재하는 폴더 \"{{otherFolder}}\" 의 하위 폴더 입니다.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "새 장치를 추가할 시 추가한 기기 쪽에서도 이 장치를 추가해야 합니다.",
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "새 폴더를 추가할 시 폴더 ID는 장치간에 폴더를 묶을 때 사용됩니다. 대소문자를 구분하며 모든 장치에서 같은 ID를 사용해야 합니다.",
|
||||
"Yes": "예",
|
||||
"You must keep at least one version.": "최소 한 개의 버전은 유지해야 합니다.",
|
||||
"days": "days",
|
||||
"days": "일",
|
||||
"full documentation": "전체 문서",
|
||||
"items": "항목",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} 에서 폴더 \\\"{{folder}}\\\" 를 공유하길 원합니다.",
|
||||
"{%device%} wants to share folder \"{%folderLabel%}\" ({%folder%}).": "{{device}} wants to share folder \"{{folderLabel}}\" ({{folder}}).",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} wants to share folder \"{{folderlabel}}\" ({{folder}})."
|
||||
"{%device%} wants to share folder \"{%folderLabel%}\" ({%folder%}).": "{{device}} 에서 폴더 \"{{folderLabel}}\" ({{folder}})를 공유하길 원합니다.",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} 에서 폴더 \"{{folderLabel}}\" ({{folder}})를 공유하길 원합니다."
|
||||
}
|
||||
@@ -32,6 +32,7 @@
|
||||
"Comment, when used at the start of a line": "Komentaras naudojamas naujoje eilutėje",
|
||||
"Compression": "Kompresija",
|
||||
"Connection Error": "Susijungimo klaida",
|
||||
"Connection Type": "Ryšio tipas",
|
||||
"Copied from elsewhere": "Nukopijuota iš kitur",
|
||||
"Copied from original": "Nukopijuota iš originalo",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Autorių teisės © 2014-2016 šių bendraautorių:",
|
||||
@@ -74,6 +75,7 @@
|
||||
"Folder Label": "Aplanko etiketė",
|
||||
"Folder Master": "Aplanko vadovas",
|
||||
"Folder Path": "Kelias iki aplanko",
|
||||
"Folder Type": "Aplanko tipas",
|
||||
"Folders": "Aplankai",
|
||||
"GUI": "Valdymo skydelis",
|
||||
"GUI Authentication Password": "Valdymo skydelio slaptažodis",
|
||||
@@ -98,10 +100,12 @@
|
||||
"Last File Received": "Paskutinis priimtas failas",
|
||||
"Last seen": "Paskutinį kartą matytas",
|
||||
"Later": "Vėliau",
|
||||
"Listeners": "Listeners",
|
||||
"Local Discovery": "Vietinis matomumas",
|
||||
"Local State": "Vietinė būsena",
|
||||
"Local State (Total)": "Vietinė būsena (Bendrai)",
|
||||
"Major Upgrade": "Stambus atnaujinimas",
|
||||
"Master": "Master",
|
||||
"Maximum Age": "Maksimalus amžius",
|
||||
"Metadata Only": "Metaduomenims",
|
||||
"Minimum Free Disk Space": "Minimum laisvos vietos diske",
|
||||
@@ -113,6 +117,7 @@
|
||||
"Newest First": "Naujausi pirmiau",
|
||||
"No": "Ne",
|
||||
"No File Versioning": "Nėra versijų valdymo",
|
||||
"Normal": "Normal",
|
||||
"Notice": "Įspėjimas",
|
||||
"OK": "Gerai",
|
||||
"Off": "Netaikoma",
|
||||
|
||||
@@ -8,13 +8,13 @@
|
||||
"Add": "Legg til",
|
||||
"Add Device": "Legg til Enhet",
|
||||
"Add Folder": "Legg til Mappe",
|
||||
"Add Remote Device": "Add Remote Device",
|
||||
"Add Remote Device": "Legg til ekstern enhet",
|
||||
"Add new folder?": "Legg til ny mappe?",
|
||||
"Address": "Adresse",
|
||||
"Addresses": "Adresser",
|
||||
"Advanced": "Avansert",
|
||||
"Advanced Configuration": "Avanserte Innstillinger",
|
||||
"Advanced settings": "Advanced settings",
|
||||
"Advanced settings": "Avanserte innstillinger ",
|
||||
"All Data": "Alle data",
|
||||
"Allow Anonymous Usage Reporting?": "Tillat Anonym Innsamling Av Brukerdata?",
|
||||
"Alphabetic": "Alfabetisk",
|
||||
@@ -32,14 +32,15 @@
|
||||
"Comment, when used at the start of a line": "Kommentar, når det blir brukt i starten av en linje.",
|
||||
"Compression": "Komprimering",
|
||||
"Connection Error": "Tilkoblingsfeil",
|
||||
"Connection Type": "Tilkoblingstype",
|
||||
"Copied from elsewhere": "Kopiert fra et annet sted",
|
||||
"Copied from original": "Kopiert fra original",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 the following Contributors:",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Opphavsrett © 2014-2016 for følgende bidragsytere:",
|
||||
"Copyright © 2015 the following Contributors:": "Opphavsrett © 2015 de følgende bidragsytere:",
|
||||
"Danger!": "Fare!",
|
||||
"Delete": "Slett",
|
||||
"Deleted": "Slettet",
|
||||
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Device \"{{name}}\" ({{device}} at {{address}}) wants to connect. Add new device?",
|
||||
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Enhet \"{{name}}\" ({{device}} på {{address}}) ønsker å koble til. Legge til ny enhet?",
|
||||
"Device ID": "Enhets ID",
|
||||
"Device Identification": "Enhetskjennemerke",
|
||||
"Device Name": "Navn på Enhet",
|
||||
@@ -55,7 +56,7 @@
|
||||
"Edit Device": "Rediger Enhet",
|
||||
"Edit Folder": "Rediger Mappe",
|
||||
"Editing": "Redigerer",
|
||||
"Enable NAT traversal": "Enable NAT traversal",
|
||||
"Enable NAT traversal": "Slå på NAT traversering",
|
||||
"Enable Relaying": "Aktiver relésending",
|
||||
"Enable UPnP": "Aktiver UPnP",
|
||||
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Skriv inn kommaseparerte (\"tcp://ip:port\", \"tcp://host:port\") adresser, eller ordet \"dynamic\" for å gjøre automatisk oppslag for adressen.",
|
||||
@@ -71,9 +72,10 @@
|
||||
"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 er beskyttet mot endringer som er gjort på andre enheter, men endringer som er gjort på denne enheten blir sendt til resten av gruppen.",
|
||||
"Folder": "Katalog",
|
||||
"Folder ID": "Mappe ID",
|
||||
"Folder Label": "Folder Label",
|
||||
"Folder Label": "Merkelapp for katalog",
|
||||
"Folder Master": "Styrende Mappe",
|
||||
"Folder Path": "Mappeplassering",
|
||||
"Folder Type": "Katalogtype",
|
||||
"Folders": "Mapper",
|
||||
"GUI": "grafisk brukergrensesnitt",
|
||||
"GUI Authentication Password": "Passord for GUI-autenisering",
|
||||
@@ -98,10 +100,12 @@
|
||||
"Last File Received": "Sist Mottatte Fil",
|
||||
"Last seen": "Sist sett",
|
||||
"Later": "Senere",
|
||||
"Listeners": "Lyttere",
|
||||
"Local Discovery": "Lokalt oppslag",
|
||||
"Local State": "Lokal Tilstand",
|
||||
"Local State (Total)": "Lokal Tilstand (Total)",
|
||||
"Major Upgrade": "Hovedoppgradering",
|
||||
"Master": "Hoved",
|
||||
"Maximum Age": "Maksimal Levetid",
|
||||
"Metadata Only": "Kun metadata",
|
||||
"Minimum Free Disk Space": "Nødvendig ledig diskplass",
|
||||
@@ -113,11 +117,12 @@
|
||||
"Newest First": "Den nyeste først",
|
||||
"No": "Nei",
|
||||
"No File Versioning": "Ingen Versjonskontroll",
|
||||
"Normal": "Normal",
|
||||
"Notice": "Merknader",
|
||||
"OK": "OK",
|
||||
"Off": "Av",
|
||||
"Oldest First": "Den eldste først",
|
||||
"Optional descriptive label for the folder. Can be different on each device.": "Optional descriptive label for the folder. Can be different on each device.",
|
||||
"Optional descriptive label for the folder. Can be different on each device.": "Valgfri merkelapp på katalogen. Denne kan være ulik på forskjellige enheter",
|
||||
"Options": "Valg",
|
||||
"Out of Sync": "Ikke synkronisert",
|
||||
"Out of Sync Items": "Ikke Synkroniserte Element",
|
||||
@@ -139,9 +144,9 @@
|
||||
"Relayed via": "Relé via",
|
||||
"Relays": "Reléer",
|
||||
"Release Notes": "Utgivelsesnotat",
|
||||
"Remote Devices": "Remote Devices",
|
||||
"Remote Devices": "Andre enheter",
|
||||
"Remove": "Fjern",
|
||||
"Required identifier for the folder. Must be the same on all cluster devices.": "Required identifier for the folder. Must be the same on all cluster devices.",
|
||||
"Required identifier for the folder. Must be the same on all cluster devices.": "Påkrevd identifikator for katalogen. Denne må være lik på alle enheter i samme klynge.",
|
||||
"Rescan": "Gjennomsøk på nytt",
|
||||
"Rescan All": "Gjennomsøk alt på nytt",
|
||||
"Rescan Interval": "Intervall for gjennomsøking",
|
||||
@@ -212,7 +217,7 @@
|
||||
"The rate limit must be a non-negative number (0: no limit)": "Hastighetsbegrensningen kan ikke være et negativt tall (0: ingen begrensing)",
|
||||
"The rescan interval must be a non-negative number of seconds.": "Antall sekund for intervallet kan ikke være negativt.",
|
||||
"They are retried automatically and will be synced when the error is resolved.": "Disse hentes automatisk og vil synkroniseres når feilen er blitt utbedret.",
|
||||
"This Device": "This Device",
|
||||
"This Device": "Denne enheten",
|
||||
"This can easily give hackers access to read and change any files on your computer.": "Dette kan lett gi hackere tilgang til å lese og endre alle filer på datamaskinen din.",
|
||||
"This is a major version upgrade.": "Dette er en hovedoppgradering",
|
||||
"Trash Can File Versioning": "Papirkurv Versjonskontroll",
|
||||
@@ -230,7 +235,7 @@
|
||||
"Version": "Versjon",
|
||||
"Versions Path": "Plassering Av Versjoner",
|
||||
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Versjoner blir automatisk slettet når maksimal levetid er nådd eller når antall filer er oversteget.",
|
||||
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Warning, this path is a subdirectory of an existing folder \"{{otherFolder}}\".",
|
||||
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Advarsel, denne stien er en underkatalog i en eksisterende katalog \"{{otherFolder}}\".",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Merk at når en ny enhet blir lagt til må denne også legges til på andre siden.",
|
||||
"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 en ny mappe blir lagt til, husk at Mappe-ID blir brukt til å binde sammen mapper mellom enheter. Det er forskjell på store og små bokstaver, så IDene må være identiske på alle enhetene.",
|
||||
"Yes": "Ja",
|
||||
@@ -239,6 +244,6 @@
|
||||
"full documentation": "all dokumentasjon",
|
||||
"items": "elementer",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} ønsker å dele mappen \"{{folder}}\".",
|
||||
"{%device%} wants to share folder \"{%folderLabel%}\" ({%folder%}).": "{{device}} wants to share folder \"{{folderLabel}}\" ({{folder}}).",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} wants to share folder \"{{folderlabel}}\" ({{folder}})."
|
||||
"{%device%} wants to share folder \"{%folderLabel%}\" ({%folder%}).": "{{device}} ønsker å dele katalogen \"{{folderLabel}}\" ({{folder}}).",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} ønsker å dele katalogen \"{{folderlabel}}\" ({{folder}})."
|
||||
}
|
||||
@@ -32,6 +32,7 @@
|
||||
"Comment, when used at the start of a line": "Reageer indien gebruikt aan het begin van een lijn.",
|
||||
"Compression": "Compressie",
|
||||
"Connection Error": "Verbindingsfout",
|
||||
"Connection Type": "Soort verbinding",
|
||||
"Copied from elsewhere": "Gekopieerd vanaf elders",
|
||||
"Copied from original": "Gekopieerd van het origineel",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 voor de volgende contributanten:",
|
||||
@@ -74,6 +75,7 @@
|
||||
"Folder Label": "Map label",
|
||||
"Folder Master": "Hoofdmap",
|
||||
"Folder Path": "Maplocatie",
|
||||
"Folder Type": "Soort map",
|
||||
"Folders": "Mappen",
|
||||
"GUI": "GUI",
|
||||
"GUI Authentication Password": "GUI-wachtwoord",
|
||||
@@ -98,10 +100,12 @@
|
||||
"Last File Received": "Laatst ontvangen bestand",
|
||||
"Last seen": "Laatst gezien op",
|
||||
"Later": "Later",
|
||||
"Listeners": "Luisteraars",
|
||||
"Local Discovery": "Lokaal zoeken",
|
||||
"Local State": "Lokale status",
|
||||
"Local State (Total)": "Lokale status (totaal)",
|
||||
"Major Upgrade": "Grote update",
|
||||
"Master": "Master",
|
||||
"Maximum Age": "Maximum leeftijd",
|
||||
"Metadata Only": "Alleen metadata",
|
||||
"Minimum Free Disk Space": "Minimale vrije schijfruimte",
|
||||
@@ -113,6 +117,7 @@
|
||||
"Newest First": "Nieuwste eerst",
|
||||
"No": "Nee",
|
||||
"No File Versioning": "Geen versiebeheer",
|
||||
"Normal": "Normaal",
|
||||
"Notice": "Mededeling",
|
||||
"OK": "OK",
|
||||
"Off": "Uit",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"Comment, when used at the start of a line": "Kommentar, når brukt i starten av linja",
|
||||
"Compression": "Komprimering",
|
||||
"Connection Error": "Tilkoplingsfeil",
|
||||
"Connection Type": "Connection Type",
|
||||
"Copied from elsewhere": "Kopiert frå ein annan stad",
|
||||
"Copied from original": "Kopiert frå originalen",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 the following Contributors:",
|
||||
@@ -74,6 +75,7 @@
|
||||
"Folder Label": "Folder Label",
|
||||
"Folder Master": "Styrande Mappe",
|
||||
"Folder Path": "Mappeplassering",
|
||||
"Folder Type": "Folder Type",
|
||||
"Folders": "Mapper",
|
||||
"GUI": "grafisk brukargrensesnitt",
|
||||
"GUI Authentication Password": "GUI Passord",
|
||||
@@ -98,10 +100,12 @@
|
||||
"Last File Received": "Siste mottatte fila",
|
||||
"Last seen": "Sist sett",
|
||||
"Later": "Seinare",
|
||||
"Listeners": "Listeners",
|
||||
"Local Discovery": "Lokal oppdaging",
|
||||
"Local State": "Lokal Tilstand",
|
||||
"Local State (Total)": "Lokal tilstand (total)",
|
||||
"Major Upgrade": "Hovudoppgradering",
|
||||
"Master": "Master",
|
||||
"Maximum Age": "Maksimal Levetid",
|
||||
"Metadata Only": "Berre metadata",
|
||||
"Minimum Free Disk Space": "Naudsynt ledig diskplass",
|
||||
@@ -113,6 +117,7 @@
|
||||
"Newest First": "Nyaste fyrst",
|
||||
"No": "Nei",
|
||||
"No File Versioning": "Ingen filutgåvehandtering",
|
||||
"Normal": "Normal",
|
||||
"Notice": "Merknad",
|
||||
"OK": "OK",
|
||||
"Off": "Av",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"Comment, when used at the start of a line": "Komentarz, jeżeli użyty na początku linii",
|
||||
"Compression": "Kompresja",
|
||||
"Connection Error": "Błąd połączenia",
|
||||
"Connection Type": "Rodzaj połączenia",
|
||||
"Copied from elsewhere": "Skopiowane z innego miejsca ",
|
||||
"Copied from original": "Skopiowane z oryginału",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016: ",
|
||||
@@ -74,6 +75,7 @@
|
||||
"Folder Label": "Etykieta folderu",
|
||||
"Folder Master": "Główny folder",
|
||||
"Folder Path": "Ścieżka folderu",
|
||||
"Folder Type": "Rodzaj folderu",
|
||||
"Folders": "Foldery",
|
||||
"GUI": "GUI",
|
||||
"GUI Authentication Password": "Hasło",
|
||||
@@ -98,10 +100,12 @@
|
||||
"Last File Received": "Ostatni otrzymany plik",
|
||||
"Last seen": "Ostatnio widziany",
|
||||
"Later": "Później",
|
||||
"Listeners": "Nasłuchujący",
|
||||
"Local Discovery": "Lokalne odnajdywanie",
|
||||
"Local State": "Status lokalny",
|
||||
"Local State (Total)": "Status lokalny (suma)",
|
||||
"Major Upgrade": "Ważna aktualizacja",
|
||||
"Master": "Mistrz",
|
||||
"Maximum Age": "Maksymalny wiek",
|
||||
"Metadata Only": "Tylko metadane",
|
||||
"Minimum Free Disk Space": "Minimum wolnego miejsca na dysku",
|
||||
@@ -113,6 +117,7 @@
|
||||
"Newest First": "Najnowsze na początku",
|
||||
"No": "Nie",
|
||||
"No File Versioning": "Bez wersjonowania pliku",
|
||||
"Normal": "Zwykły",
|
||||
"Notice": "Wskazówka",
|
||||
"OK": "OK",
|
||||
"Off": "Wyłącz",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"Comment, when used at the start of a line": "Comentário, se usado no início de uma linha",
|
||||
"Compression": "Compressão",
|
||||
"Connection Error": "Erro de conexão",
|
||||
"Connection Type": "Tipo da conexão",
|
||||
"Copied from elsewhere": "Copiado de outro lugar",
|
||||
"Copied from original": "Copiado do original",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Direitos reservados © 2014-2016 aos seguintes colaboradores:",
|
||||
@@ -74,6 +75,7 @@
|
||||
"Folder Label": "Rótulo da pasta",
|
||||
"Folder Master": "Pasta mestre",
|
||||
"Folder Path": "Caminho da pasta",
|
||||
"Folder Type": "Tipo da pasta",
|
||||
"Folders": "Pastas",
|
||||
"GUI": "Interface gráfica",
|
||||
"GUI Authentication Password": "Senha para acesso à interface",
|
||||
@@ -98,10 +100,12 @@
|
||||
"Last File Received": "Último arquivo recebido",
|
||||
"Last seen": "Visto por último em",
|
||||
"Later": "Depois",
|
||||
"Listeners": "Escutadores",
|
||||
"Local Discovery": "Descoberta local",
|
||||
"Local State": "Estado local",
|
||||
"Local State (Total)": "Estado local (total)",
|
||||
"Major Upgrade": "Atualização \"major\"",
|
||||
"Master": "Mestre",
|
||||
"Maximum Age": "Idade máxima",
|
||||
"Metadata Only": "Somente metadados",
|
||||
"Minimum Free Disk Space": "Espaço livre mínimo no disco",
|
||||
@@ -113,6 +117,7 @@
|
||||
"Newest First": "Mais novo primeiro",
|
||||
"No": "Não",
|
||||
"No File Versioning": "Sem versionamento de arquivos",
|
||||
"Normal": "Normal",
|
||||
"Notice": "Aviso",
|
||||
"OK": "OK",
|
||||
"Off": "Desligada",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"Comment, when used at the start of a line": "Comentário, quando usado no início de uma linha",
|
||||
"Compression": "Compressão",
|
||||
"Connection Error": "Erro de ligação",
|
||||
"Connection Type": "Tipo de ligação",
|
||||
"Copied from elsewhere": "Copiado doutro sítio",
|
||||
"Copied from original": "Copiado do original",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 os seguintes contribuidores:",
|
||||
@@ -46,7 +47,7 @@
|
||||
"Device {%device%} ({%address%}) wants to connect. Add new device?": "O dispositivo {{device}} ({{address}}) quer conectar-se. Adiciono este novo dispositivo?",
|
||||
"Devices": "Dispositivos",
|
||||
"Disconnected": "Desconectado",
|
||||
"Discovery": "Detecção",
|
||||
"Discovery": "Pesquisa",
|
||||
"Documentation": "Documentação",
|
||||
"Download Rate": "Velocidade de recepção",
|
||||
"Downloaded": "Recebido",
|
||||
@@ -74,15 +75,16 @@
|
||||
"Folder Label": "Etiqueta da pasta",
|
||||
"Folder Master": "Pasta mestre",
|
||||
"Folder Path": "Caminho da pasta",
|
||||
"Folder Type": "Tipo de pasta",
|
||||
"Folders": "Pastas",
|
||||
"GUI": "GUI",
|
||||
"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",
|
||||
"Generate": "Gerar",
|
||||
"Global Discovery": "Detecção global",
|
||||
"Global Discovery Server": "Servidor de detecção global",
|
||||
"Global Discovery Servers": "Servidores de detecção global",
|
||||
"Global Discovery": "Pesquisa global",
|
||||
"Global Discovery Server": "Servidor de pesquisa global",
|
||||
"Global Discovery Servers": "Servidores de pesquisa global",
|
||||
"Global State": "Estado global",
|
||||
"Help": "Ajuda",
|
||||
"Home page": "Página do projecto",
|
||||
@@ -98,10 +100,12 @@
|
||||
"Last File Received": "Último ficheiro recebido",
|
||||
"Last seen": "Última vez que foi verificado",
|
||||
"Later": "Mais tarde",
|
||||
"Local Discovery": "Detecção local",
|
||||
"Listeners": "Auscultadores",
|
||||
"Local Discovery": "Pesquisa local",
|
||||
"Local State": "Estado local",
|
||||
"Local State (Total)": "Estado local (total)",
|
||||
"Major Upgrade": "Actualização importante",
|
||||
"Master": "Mestre",
|
||||
"Maximum Age": "Idade máxima",
|
||||
"Metadata Only": "Metadados apenas",
|
||||
"Minimum Free Disk Space": "Espaço livre mínimo no disco",
|
||||
@@ -113,6 +117,7 @@
|
||||
"Newest First": "Primeiro os mais recentes",
|
||||
"No": "Não",
|
||||
"No File Versioning": "Nenhuma",
|
||||
"Normal": "Normal",
|
||||
"Notice": "Avisos",
|
||||
"OK": "OK",
|
||||
"Off": "Desligada",
|
||||
|
||||
@@ -16,15 +16,15 @@
|
||||
"Advanced Configuration": "Дополнительные настройки",
|
||||
"Advanced settings": "Дополнительные настройки",
|
||||
"All Data": "Все данные",
|
||||
"Allow Anonymous Usage Reporting?": "Разрешить сбор анонимной статистики использования?",
|
||||
"Allow Anonymous Usage Reporting?": "Разрешить анонимный отчет об использовании?",
|
||||
"Alphabetic": "По алфавиту",
|
||||
"An external command handles the versioning. It has to remove the file from the synced folder.": "Внешний процесс управляет версиями файлов. Процесс удалит файл из синхронизируемой папки.",
|
||||
"Anonymous Usage Reporting": "Анонимная статистика использования",
|
||||
"Anonymous Usage Reporting": "Анонимный отчет об использовании",
|
||||
"Any devices configured on an introducer device will be added to this device as well.": "Все устройства, подключённые к устройству-рекомендателю, будут добавлены к текущему устройству.",
|
||||
"Automatic upgrades": "Автообновление",
|
||||
"Be careful!": "Будьте осторожны!",
|
||||
"Bugs": "Ошибки",
|
||||
"CPU Utilization": "Загрузка ЦПУ",
|
||||
"CPU Utilization": "Загрузка ЦП",
|
||||
"Changelog": "Журнал изменений",
|
||||
"Clean out after": "Очистить после",
|
||||
"Close": "Закрыть",
|
||||
@@ -32,6 +32,7 @@
|
||||
"Comment, when used at the start of a line": "Комментарий, если используется в начале строки",
|
||||
"Compression": "Сжатие",
|
||||
"Connection Error": "Ошибка подключения",
|
||||
"Connection Type": "Тип соединения",
|
||||
"Copied from elsewhere": "Скопировано из другого места",
|
||||
"Copied from original": "Скопировано с оригинала",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Авторские права © 2014–2016 принадлежат:",
|
||||
@@ -51,14 +52,14 @@
|
||||
"Download Rate": "Скорость загрузки",
|
||||
"Downloaded": "Загружено",
|
||||
"Downloading": "Загрузка",
|
||||
"Edit": "Изменить",
|
||||
"Edit Device": "Изменить устройство",
|
||||
"Edit Folder": "Изменить папку",
|
||||
"Edit": "Редактировать",
|
||||
"Edit Device": "Редактирование устройства",
|
||||
"Edit Folder": "Редактирование папки",
|
||||
"Editing": "Редактирование",
|
||||
"Enable NAT traversal": "Включить NAT traversal",
|
||||
"Enable Relaying": "Включить релеи",
|
||||
"Enable UPnP": "Включить UPnP",
|
||||
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Введите адреса через запятую (\"tcp://ip:port\", \"tcp://host:port\") или \"dynamic\" для автоматического поиска адресов.",
|
||||
"Enter comma separated (\"tcp://ip:port\", \"tcp://host:port\") addresses or \"dynamic\" to perform automatic discovery of the address.": "Введите через запятую («tcp://ip:port», «tcp://host:port») адреса, либо «dynamic», чтобы выполнить автоматическое обнаружение адреса.",
|
||||
"Enter ignore patterns, one per line.": "Введите шаблоны игнорирования, по одному на строку.",
|
||||
"Error": "Ошибка",
|
||||
"External File Versioning": "Внешний контроль версий файлов",
|
||||
@@ -74,6 +75,7 @@
|
||||
"Folder Label": "Ярлык папки",
|
||||
"Folder Master": "Папка-оригинал",
|
||||
"Folder Path": "Путь к папке",
|
||||
"Folder Type": "Тип папки",
|
||||
"Folders": "Папки",
|
||||
"GUI": "Интерфейс",
|
||||
"GUI Authentication Password": "Пароль для доступа к панели управления",
|
||||
@@ -89,19 +91,21 @@
|
||||
"Ignore": "Игнорировать",
|
||||
"Ignore Patterns": "Шаблоны игнорирования",
|
||||
"Ignore Permissions": "Игнорировать файловые права доступа",
|
||||
"Incoming Rate Limit (KiB/s)": "Ограничение входящего потока (Кбит/сек)",
|
||||
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Неправильные настройки могут повредить содержимое папок и сделать Syncthing нерабочим",
|
||||
"Incoming Rate Limit (KiB/s)": "Ограничение входящей скорости (КиБ/с)",
|
||||
"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": "Количество хранимых версий",
|
||||
"Largest First": "Сначала большие",
|
||||
"Last File Received": "Последний полученный файл",
|
||||
"Last seen": "Был доступен",
|
||||
"Later": "Потом",
|
||||
"Later": "Позже",
|
||||
"Listeners": "Listeners",
|
||||
"Local Discovery": "Локальное обнаружение",
|
||||
"Local State": "Локальное состояние",
|
||||
"Local State (Total)": "Локально (всего)",
|
||||
"Local State (Total)": "Локальное состояние (всего)",
|
||||
"Major Upgrade": "Обновление основной версии",
|
||||
"Master": "Master",
|
||||
"Maximum Age": "Максимальный срок",
|
||||
"Metadata Only": "Только метаданные",
|
||||
"Minimum Free Disk Space": "Минимальное свободное место на диске",
|
||||
@@ -113,6 +117,7 @@
|
||||
"Newest First": "Сначала новые",
|
||||
"No": "Нет",
|
||||
"No File Versioning": "Без управления версиями файлов",
|
||||
"Normal": "Normal",
|
||||
"Notice": "Внимание",
|
||||
"OK": "ОК",
|
||||
"Off": "Отключить",
|
||||
@@ -120,32 +125,32 @@
|
||||
"Optional descriptive label for the folder. Can be different on each device.": "Необязательное описательное название папки. Может различаться на разных устройствах.",
|
||||
"Options": "Настройки",
|
||||
"Out of Sync": "Нет синхронизации",
|
||||
"Out of Sync Items": "Не синхронизированные пункты",
|
||||
"Outgoing Rate Limit (KiB/s)": "Предел скорости отдачи (KiB/s)",
|
||||
"Out of Sync Items": "Несинхронизированные элементы",
|
||||
"Outgoing Rate Limit (KiB/s)": "Ограничение исходящей скорости (КиБ/с)",
|
||||
"Override Changes": "Перезаписать изменения",
|
||||
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Путь к папке на локальном компьютере. Если её не существует, то она будет создана. Тильда (~) может использоваться как сокращение для",
|
||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Путь, где должны храниться версии (оставьте пустым, чтобы использовать папку по умолчанию .stversions внутри папки).",
|
||||
"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.": "Установите имя пользователя и пароль для интерфейса в настройках",
|
||||
"Please wait": "Пожалуйста, подождите",
|
||||
"Preview": "Предварительный просмотр",
|
||||
"Preview Usage Report": "Посмотреть отчёт об использовании",
|
||||
"Quick guide to supported patterns": "Краткое руководство по поддерживаемым шаблонам",
|
||||
"RAM Utilization": "Использование ОЗУ",
|
||||
"RAM Utilization": "Использование памяти",
|
||||
"Random": "Случайно",
|
||||
"Relay Servers": "Релеи",
|
||||
"Relayed via": "Релей через",
|
||||
"Relays": "Релеи",
|
||||
"Release Notes": "Замечания к версии",
|
||||
"Release Notes": "Примечания к выпуску",
|
||||
"Remote Devices": "Удалённые устройства",
|
||||
"Remove": "Удалить",
|
||||
"Required identifier for the folder. Must be the same on all cluster devices.": "Обязательный идентификатор папки. Должен быть одним и тем же на всех устройствах кластера.",
|
||||
"Rescan": "Пересканирование",
|
||||
"Rescan": "Пересканировать",
|
||||
"Rescan All": "Пересканировать все",
|
||||
"Rescan Interval": "Интервал пересканирования",
|
||||
"Restart": "Перезапуск",
|
||||
"Restart": "Перезапустить",
|
||||
"Restart Needed": "Требуется перезапуск",
|
||||
"Restarting": "Перезапуск",
|
||||
"Resume": "Возобновить",
|
||||
@@ -154,7 +159,7 @@
|
||||
"Scan Time Remaining": "Оставшееся время сканирования",
|
||||
"Scanning": "Сканирование",
|
||||
"Select the devices to share this folder with.": "Выберите устройства, для которых будет доступна эта папка.",
|
||||
"Select the folders to share with this device.": "Выберите папку для предоставления доступа данному устройству",
|
||||
"Select the folders to share with this device.": "Выберите папки, которые будут доступны этому устройству.",
|
||||
"Settings": "Настройки",
|
||||
"Share": "Предоставить доступ",
|
||||
"Share Folder": "Предоставить доступ к папке",
|
||||
@@ -168,32 +173,32 @@
|
||||
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Отображается вместо ID устройства в статусе группы. Будет разослан другим устройствам в качестве имени по умолчанию.",
|
||||
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Отображается вместо ID устройства в статусе группы. Если поле не заполнено, то будет установлено имя, передаваемое этим устройством.",
|
||||
"Shutdown": "Выключить",
|
||||
"Shutdown Complete": "Выключено",
|
||||
"Shutdown Complete": "Выключение",
|
||||
"Simple File Versioning": "Простое управление версиями файлов",
|
||||
"Single level wildcard (matches within a directory only)": "Одноуровневая маска (поиск совпадений только внутри папки)",
|
||||
"Smallest First": "Сначала маленькие",
|
||||
"Source Code": "Исходный код",
|
||||
"Staggered File Versioning": "Ступенчатое управление версиями файлов",
|
||||
"Start Browser": "Открыть браузер",
|
||||
"Start Browser": "Запускать браузер",
|
||||
"Statistics": "Статистика",
|
||||
"Stopped": "Остановлено",
|
||||
"Support": "Поддержка",
|
||||
"Sync Protocol Listen Addresses": "Адрес протокола синхронизации",
|
||||
"Syncing": "Синхронизация",
|
||||
"Syncthing has been shut down.": "Syncthing выключен.",
|
||||
"Syncthing has been shut down.": "Syncthing был выключен.",
|
||||
"Syncthing includes the following software or portions thereof:": "Syncthing включает в себя следующее ПО или его части:",
|
||||
"Syncthing is restarting.": "Перезапуск Syncthing",
|
||||
"Syncthing is upgrading.": "Обновление Syncthing ",
|
||||
"Syncthing is restarting.": "Перезапуск Syncthing.",
|
||||
"Syncthing is upgrading.": "Обновление Syncthing.",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Кажется, Syncthing не запущен или есть проблемы с подключением к Интернету. Переподключаюсь...",
|
||||
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing столкнулся с проблемой при обработке Вашего запроса. Пожалуйста, обновите страницу или перезапустите Syncthing если проблема повторится.",
|
||||
"The Syncthing admin interface is configured to allow remote access without a password.": "Административный интерфейс 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 configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Конфигурация была сохранена, но не активирована. Syncthing должен быть перезапущен для применения новой конфигурации.",
|
||||
"The device ID cannot be blank.": "ID устройства не может быть пустым.",
|
||||
"The device ID to enter here can be found in the \"Actions > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "Идентификатор устройства можно найти в диалоге «Действия → Показать ID» на другом устройстве. Пробелы и дефисы вводить не обязательно (они игнорируются).",
|
||||
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "Идентификатор устройства, который следует тут ввести, может быть найден в диалоге \"Редактирование > Показать ID\" на другом устройстве. Пробелы и тире не обязательны (игнорируются).",
|
||||
"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).": "Идентификатор устройства можно найти в диалоге «Действия → Показать ID» на другом устройстве. Пробелы и дефисы вводить не обязательно (игнорируются).",
|
||||
"The device ID to enter here can be found in the \"Edit > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "Идентификатор устройства можно найти в диалоге «Редактирование → Показать ID» на другом устройстве. Пробелы и дефисы вводить не обязательно (игнорируются).",
|
||||
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Зашифрованный отчет об использовании отправляется ежедневно. Это используется для отслеживания общих платформ, размеров папок и версий приложения. Если отчетные данные изменятся, вам будет снова показано это диалоговое окно.",
|
||||
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "Введён недопустимый ID устройства. Он должен состоять из букв и цифр, может включать пробелы и дефисы, длина должна быть от 52 до 56 символов, ",
|
||||
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "Введён недопустимый ID устройства. Он должен состоять из букв и цифр, может включать пробелы и дефисы, длина должна быть 52 или 56 символов.",
|
||||
"The first command line parameter is the folder path and the second parameter is the relative path in the folder.": "Первый параметр командной строки - путь к папке, второй параметр - относительный путь в папке.",
|
||||
"The folder ID cannot be blank.": "ID папки не может быть пустым.",
|
||||
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "ID папки должен быть коротким (не более 64 символов), должен состоять только из букв, цифр, точек (.), дефисов (-) или подчёркиваний (_).",
|
||||
@@ -219,13 +224,13 @@
|
||||
"Unknown": "Неизвестно",
|
||||
"Unshared": "Необщедоступно",
|
||||
"Unused": "Не используется",
|
||||
"Up to Date": "Обновлено",
|
||||
"Up to Date": "В актуальном состоянии",
|
||||
"Updated": "Обновлено",
|
||||
"Upgrade": "Обновить",
|
||||
"Upgrade To {%version%}": "Обновить до {{version}}",
|
||||
"Upgrading": "Обновление",
|
||||
"Upload Rate": "Скорость отдачи",
|
||||
"Uptime": "Аптайм",
|
||||
"Uptime": "Время работы",
|
||||
"Use HTTPS for GUI": "Использовать HTTPS для панели управления",
|
||||
"Version": "Версия",
|
||||
"Versions Path": "Путь к версиям",
|
||||
@@ -235,10 +240,10 @@
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Когда добавляете новую папку, помните, что ID папок используются для того, чтобы связывать папки между всеми устройствами. Они чувствительны к регистру и должны совпадать на всех используемых устройствах.",
|
||||
"Yes": "Да",
|
||||
"You must keep at least one version.": "Вы должны хранить как минимум одну версию.",
|
||||
"days": "Дней",
|
||||
"days": "дней",
|
||||
"full documentation": "полная документация",
|
||||
"items": "элементы",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} хочет поделиться папкой \"{{folder}}\".",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} хочет поделиться папкой «{{folder}}».",
|
||||
"{%device%} wants to share folder \"{%folderLabel%}\" ({%folder%}).": "{{device}} хочет поделиться папкой «{{folderLabel}}» ({{folder}}).",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} хочет поделиться папкой «{{folderlabel}}» ({{folder}})."
|
||||
}
|
||||
@@ -32,6 +32,7 @@
|
||||
"Comment, when used at the start of a line": "Kommentar, vid början av en rad.",
|
||||
"Compression": "Komprimering",
|
||||
"Connection Error": "Anslutningsproblem",
|
||||
"Connection Type": "Anslutningstyp",
|
||||
"Copied from elsewhere": "Kopierat utifrån",
|
||||
"Copied from original": "Oförändrat",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 följande bidragande:",
|
||||
@@ -74,6 +75,7 @@
|
||||
"Folder Label": "Katalog etikett",
|
||||
"Folder Master": "Huvudlagring",
|
||||
"Folder Path": "Sökväg",
|
||||
"Folder Type": "Katalogtyp",
|
||||
"Folders": "Kataloger",
|
||||
"GUI": "GUI",
|
||||
"GUI Authentication Password": "GUI-lösenord",
|
||||
@@ -98,10 +100,12 @@
|
||||
"Last File Received": "Senast Mottagna Fil",
|
||||
"Last seen": "Senast online",
|
||||
"Later": "Senare",
|
||||
"Listeners": "Lyssnare",
|
||||
"Local Discovery": "Lokal uppslagning",
|
||||
"Local State": "Lokal status",
|
||||
"Local State (Total)": "Lokal status (Total)",
|
||||
"Major Upgrade": "Stor uppgradering",
|
||||
"Master": "Huvud",
|
||||
"Maximum Age": "Högsta åldersgräns",
|
||||
"Metadata Only": "Endast metadata",
|
||||
"Minimum Free Disk Space": "Minimum ledigt diskutrymme",
|
||||
@@ -113,6 +117,7 @@
|
||||
"Newest First": "Nyast först",
|
||||
"No": "Nej",
|
||||
"No File Versioning": "Ingen versionshantering",
|
||||
"Normal": "Normal",
|
||||
"Notice": "Observera",
|
||||
"OK": "OK",
|
||||
"Off": "Av",
|
||||
@@ -141,7 +146,7 @@
|
||||
"Release Notes": "versionsnyheter",
|
||||
"Remote Devices": "Fjärrenheter",
|
||||
"Remove": "Ta bort",
|
||||
"Required identifier for the folder. Must be the same on all cluster devices.": "Krävs identifierare för mappen. Måste vara densamma på alla kluster enheter.",
|
||||
"Required identifier for the folder. Must be the same on all cluster devices.": "Krävs identifierare för katalogen. Måste vara densamma på alla kluster enheter.",
|
||||
"Rescan": "Uppdatera",
|
||||
"Rescan All": "Uppdatera alla",
|
||||
"Rescan Interval": "Uppdateringsintervall",
|
||||
@@ -151,7 +156,7 @@
|
||||
"Resume": "Återuppta",
|
||||
"Reused": "Återanvänt",
|
||||
"Save": "Spara",
|
||||
"Scan Time Remaining": "Skanna Återstående Tid",
|
||||
"Scan Time Remaining": "Granska återstående tid",
|
||||
"Scanning": "Uppdaterar",
|
||||
"Select the devices to share this folder with.": "Ange enheterna att dela den här katalogen med.",
|
||||
"Select the folders to share with this device.": "Välj kataloger att dela med den här enheten.",
|
||||
@@ -239,6 +244,6 @@
|
||||
"full documentation": "fullständig dokumentation",
|
||||
"items": "poster",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} vill dela katalogen \"{{folder}}\".",
|
||||
"{%device%} wants to share folder \"{%folderLabel%}\" ({%folder%}).": "{{device}} vill dela mappen \"{{folderLabel}}\" ({{folder}}).",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} vill dela mappen \"{{folderlabel}}\" ({{folder}})."
|
||||
"{%device%} wants to share folder \"{%folderLabel%}\" ({%folder%}).": "{{device}} vill dela katalogen \"{{folderLabel}}\" ({{folder}}).",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} vill dela katalogen \"{{folderlabel}}\" ({{folder}})."
|
||||
}
|
||||
@@ -32,6 +32,7 @@
|
||||
"Comment, when used at the start of a line": "Satır başında kullanıldığında açıklama özelliği taşır",
|
||||
"Compression": "Sıkıştırma",
|
||||
"Connection Error": "Bağlantı hatası",
|
||||
"Connection Type": "Connection Type",
|
||||
"Copied from elsewhere": "Başka bir yerden kopyalanmış",
|
||||
"Copied from original": "Aslından kopyalanmış",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 the following Contributors:",
|
||||
@@ -74,6 +75,7 @@
|
||||
"Folder Label": "Folder Label",
|
||||
"Folder Master": "Ana Klasör",
|
||||
"Folder Path": "Klasör Yolu",
|
||||
"Folder Type": "Folder Type",
|
||||
"Folders": "Klasörler",
|
||||
"GUI": "GUI / Kullanıcı Grafik Arayüzü",
|
||||
"GUI Authentication Password": "GUI Kimlik Doğrulaması için Kullanıcı Parolası",
|
||||
@@ -98,10 +100,12 @@
|
||||
"Last File Received": "Alınan Son Dosya",
|
||||
"Last seen": "Son Görülen",
|
||||
"Later": "Sonra",
|
||||
"Listeners": "Listeners",
|
||||
"Local Discovery": "Yerel Discovery",
|
||||
"Local State": "Yerel Durum",
|
||||
"Local State (Total)": "Yerel Durum (Toplamı)",
|
||||
"Major Upgrade": "Birincil Yükseltme",
|
||||
"Master": "Master",
|
||||
"Maximum Age": "Azami Süre",
|
||||
"Metadata Only": "Sadece Üstveri",
|
||||
"Minimum Free Disk Space": "En Az Boş Disk Alanı",
|
||||
@@ -113,6 +117,7 @@
|
||||
"Newest First": "En yeni olan önce",
|
||||
"No": "Hayır",
|
||||
"No File Versioning": "Dosya Sürümlendirmesi Yok",
|
||||
"Normal": "Normal",
|
||||
"Notice": "Uyarı",
|
||||
"OK": "Tamam",
|
||||
"Off": "Kapalı",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"Comment, when used at the start of a line": "Коментар, якщо використовується на початку рядка",
|
||||
"Compression": "Стиснення",
|
||||
"Connection Error": "Помилка з’єднання",
|
||||
"Connection Type": "Тип з*єднання",
|
||||
"Copied from elsewhere": "Скопійовано з іншого місця",
|
||||
"Copied from original": "Скопійовано з оригіналу",
|
||||
"Copyright © 2014-2016 the following Contributors:": "© 2014-2016 Всі права застережено, вклад внесли:",
|
||||
@@ -46,7 +47,7 @@
|
||||
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Пристрій {{device}} ({{address}}) намагається під’єднатися. Додати новий пристрій?",
|
||||
"Devices": "Пристрої",
|
||||
"Disconnected": "З’єднання відсутнє",
|
||||
"Discovery": "Сервери обходу NAT",
|
||||
"Discovery": "Сервери координації NAT",
|
||||
"Documentation": "Документація",
|
||||
"Download Rate": "Швидкість завантаження",
|
||||
"Downloaded": "Завантажено",
|
||||
@@ -74,6 +75,7 @@
|
||||
"Folder Label": "Мітка директорії",
|
||||
"Folder Master": "Вважати за оригінал",
|
||||
"Folder Path": "Шлях до директорії",
|
||||
"Folder Type": "Тип директорії",
|
||||
"Folders": "Директорії",
|
||||
"GUI": "Графічний інтерфейс",
|
||||
"GUI Authentication Password": "Пароль для доступу до панелі управління",
|
||||
@@ -82,7 +84,7 @@
|
||||
"Generate": "Згенерувати",
|
||||
"Global Discovery": "Глобальне виявлення (internet)",
|
||||
"Global Discovery Server": "Сервер для глобального виявлення",
|
||||
"Global Discovery Servers": "Сервери глобального виявлення \n(координації обходу NAT)",
|
||||
"Global Discovery Servers": "Сервери глобального виявлення \n(координації NAT)",
|
||||
"Global State": "Глобальний статус",
|
||||
"Help": "Допомога",
|
||||
"Home page": "Домашня сторінка",
|
||||
@@ -98,10 +100,12 @@
|
||||
"Last File Received": "Останній завантажений файл",
|
||||
"Last seen": "З’являвся останній раз",
|
||||
"Later": "Пізніше",
|
||||
"Listeners": "Приймачі (TCP & Relay)",
|
||||
"Local Discovery": "Локальне виявлення (LAN)",
|
||||
"Local State": "Локальний статус",
|
||||
"Local State (Total)": "Локальний статус (загалом)",
|
||||
"Major Upgrade": "Мажорне оновлення",
|
||||
"Master": "Головний",
|
||||
"Maximum Age": "Максимальний вік",
|
||||
"Metadata Only": "Тільки метадані",
|
||||
"Minimum Free Disk Space": "Мінімальний вільний простір на диску",
|
||||
@@ -113,6 +117,7 @@
|
||||
"Newest First": "Спершу новіші",
|
||||
"No": "Ні",
|
||||
"No File Versioning": "Версіонування вимкнено",
|
||||
"Normal": "Нормальний",
|
||||
"Notice": "Повідомлення",
|
||||
"OK": "Гаразд",
|
||||
"Off": "Вимкнути",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"Comment, when used at the start of a line": "Bình luận, khi dùng trước đầu dòng",
|
||||
"Compression": "Nén",
|
||||
"Connection Error": "Lỗi kết nối",
|
||||
"Connection Type": "Connection Type",
|
||||
"Copied from elsewhere": "Đã sao chép từ nơi khác",
|
||||
"Copied from original": "Đã sao chép từ nguồn",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Bản quyền © 2014-2016 thuộc về các nhà cộng tác sau:",
|
||||
@@ -74,6 +75,7 @@
|
||||
"Folder Label": "Nhãn thư mục",
|
||||
"Folder Master": "Thư mục Chủ",
|
||||
"Folder Path": "Đ.dẫn đến th.mục",
|
||||
"Folder Type": "Folder Type",
|
||||
"Folders": "Các th.mục",
|
||||
"GUI": "GUI",
|
||||
"GUI Authentication Password": "Mật khẩu xác minh GUI",
|
||||
@@ -98,10 +100,12 @@
|
||||
"Last File Received": "T.tin nhận được gần đây",
|
||||
"Last seen": "Thấy lần cuối",
|
||||
"Later": "Để sau",
|
||||
"Listeners": "Listeners",
|
||||
"Local Discovery": "Dò tìm cục bộ",
|
||||
"Local State": "Tr.thái cục bộ",
|
||||
"Local State (Total)": "Tr.thái cục bộ (Tổng)",
|
||||
"Major Upgrade": "Bản n.cấp q.trọng",
|
||||
"Master": "Master",
|
||||
"Maximum Age": "Thời hạn tối đa",
|
||||
"Metadata Only": "Chỉ siêu dữ liệu",
|
||||
"Minimum Free Disk Space": "Dung lượng đĩa trống tối thiểu",
|
||||
@@ -113,6 +117,7 @@
|
||||
"Newest First": "Mới nhất đầu tiên",
|
||||
"No": "Không",
|
||||
"No File Versioning": "Không dùng",
|
||||
"Normal": "Normal",
|
||||
"Notice": "Chú ý",
|
||||
"OK": "OK",
|
||||
"Off": "Tắt",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"Comment, when used at the start of a line": "注释,在行首使用",
|
||||
"Compression": "压缩",
|
||||
"Connection Error": "连接出错",
|
||||
"Connection Type": "连接类型",
|
||||
"Copied from elsewhere": "从其他设备复制",
|
||||
"Copied from original": "从源复制",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 以下贡献者:",
|
||||
@@ -74,6 +75,7 @@
|
||||
"Folder Label": "文件夹标签",
|
||||
"Folder Master": "主文件夹",
|
||||
"Folder Path": "文件夹路径",
|
||||
"Folder Type": "文件夹类型",
|
||||
"Folders": "文件夹",
|
||||
"GUI": "图形用户界面",
|
||||
"GUI Authentication Password": "图形管理界面密码",
|
||||
@@ -98,10 +100,12 @@
|
||||
"Last File Received": "最后接收的文件",
|
||||
"Last seen": "最后可见",
|
||||
"Later": "稍后",
|
||||
"Listeners": "侦听程序",
|
||||
"Local Discovery": "在局域网上寻找设备",
|
||||
"Local State": "本地状态",
|
||||
"Local State (Total)": "本地状态汇总",
|
||||
"Major Upgrade": "重大更新",
|
||||
"Master": "主要",
|
||||
"Maximum Age": "历史版本最长保留时间",
|
||||
"Metadata Only": "仅元数据",
|
||||
"Minimum Free Disk Space": "最低可用磁盘空间",
|
||||
@@ -113,6 +117,7 @@
|
||||
"Newest First": "新文件优先",
|
||||
"No": "否",
|
||||
"No File Versioning": "不启用版本控制",
|
||||
"Normal": "正常",
|
||||
"Notice": "提示",
|
||||
"OK": "确定",
|
||||
"Off": "关闭",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"Comment, when used at the start of a line": "註解,當輸入在一行的開頭時",
|
||||
"Compression": "壓縮",
|
||||
"Connection Error": "連線錯誤",
|
||||
"Connection Type": "Connection Type",
|
||||
"Copied from elsewhere": "從別處複製",
|
||||
"Copied from original": "從原處複製",
|
||||
"Copyright © 2014-2016 the following Contributors:": "Copyright © 2014-2016 下列貢獻者:",
|
||||
@@ -74,6 +75,7 @@
|
||||
"Folder Label": "資料夾標籤",
|
||||
"Folder Master": "主資料夾",
|
||||
"Folder Path": "資料夾路徑",
|
||||
"Folder Type": "Folder Type",
|
||||
"Folders": "資料夾",
|
||||
"GUI": "GUI",
|
||||
"GUI Authentication Password": "GUI 認證密碼",
|
||||
@@ -98,10 +100,12 @@
|
||||
"Last File Received": "最後接收的檔案",
|
||||
"Last seen": "最後發現時間",
|
||||
"Later": "稍後",
|
||||
"Listeners": "Listeners",
|
||||
"Local Discovery": "本機探索",
|
||||
"Local State": "本機狀態",
|
||||
"Local State (Total)": "本機狀態 (總結)",
|
||||
"Major Upgrade": "重大更新",
|
||||
"Master": "Master",
|
||||
"Maximum Age": "最長保留時間",
|
||||
"Metadata Only": "僅中繼資料",
|
||||
"Minimum Free Disk Space": "最少閒置磁碟空間",
|
||||
@@ -113,6 +117,7 @@
|
||||
"Newest First": "最新的優先",
|
||||
"No": "否",
|
||||
"No File Versioning": "無檔案版本控制",
|
||||
"Normal": "Normal",
|
||||
"Notice": "注意",
|
||||
"OK": "確定",
|
||||
"Off": "關閉",
|
||||
|
||||
23
gui/default/index.html
Executable file → Normal file
23
gui/default/index.html
Executable file → Normal file
@@ -50,7 +50,7 @@
|
||||
</li>
|
||||
<li class="dropdown" language-select></li>
|
||||
<li>
|
||||
<a href="https://docs.syncthing.net/intro/gui.html" target="_blank">
|
||||
<a class="navbar-link" href="https://docs.syncthing.net/intro/gui.html" target="_blank">
|
||||
<span class="fa fa-question-circle"></span> <span class="hidden-xs" translate>Help</span>
|
||||
</a>
|
||||
</li>
|
||||
@@ -227,15 +227,13 @@
|
||||
<h3 translate>Folders</h3>
|
||||
<div class="panel-group" id="folders">
|
||||
<div class="panel panel-default" ng-repeat="folder in folderList()">
|
||||
<div class="panel-heading" data-toggle="collapse" data-parent="#folders" href="#folder-{{$index}}" style="cursor: pointer">
|
||||
<a class="panel-heading" data-toggle="collapse" data-parent="#folders" href="#folder-{{$index}}" style="cursor: pointer; display: block;">
|
||||
<div class="panel-progress" ng-show="folderStatus(folder) == 'syncing'" ng-attr-style="width: {{syncPercentage(folder.id)}}%"></div>
|
||||
<div class="panel-progress" ng-show="folderStatus(folder) == 'scanning' && scanProgress[folder.id] != undefined" ng-attr-style="width: {{scanPercentage(folder.id)}}%"></div>
|
||||
<h4 class="panel-title">
|
||||
<span class="fa hidden-xs fa-fw" ng-class="[folder.type == 'readonly' ? 'fa-lock' : 'fa-folder']"></span>
|
||||
<a href="#folder-{{$index}}">
|
||||
<span ng-show="folder.label.length == 0">{{folder.id}}</span>
|
||||
<span tooltip data-original-title="{{folder.id}}" ng-show="folder.label.length != 0">{{folder.label}}</span>
|
||||
</a>
|
||||
<span ng-show="folder.label.length == 0">{{folder.id}}</span>
|
||||
<span tooltip data-original-title="{{folder.id}}" ng-show="folder.label.length != 0">{{folder.label}}</span>
|
||||
<span class="pull-right text-{{folderClass(folder)}}" ng-switch="folderStatus(folder)">
|
||||
<span ng-switch-when="unknown"><span class="hidden-xs" translate>Unknown</span><span class="visible-xs">◼</span></span>
|
||||
<span ng-switch-when="unshared"><span class="hidden-xs" translate>Unshared</span><span class="visible-xs">◼</span></span>
|
||||
@@ -255,7 +253,7 @@
|
||||
<span ng-switch-when="outofsync"><span class="hidden-xs" translate>Out of Sync</span><span class="visible-xs">◼</span></span>
|
||||
</span>
|
||||
</h4>
|
||||
</div>
|
||||
</a>
|
||||
<div id="folder-{{$index}}" class="panel-collapse collapse">
|
||||
<div class="panel-body">
|
||||
<table class="table table-condensed table-striped">
|
||||
@@ -401,11 +399,11 @@
|
||||
<div class="col-md-6">
|
||||
<h3 translate>This Device</h3>
|
||||
<div class="panel panel-default" ng-repeat="deviceCfg in [thisDevice()]">
|
||||
<div class="panel-heading" data-toggle="collapse" href="#device-this" style="cursor: pointer">
|
||||
<a class="panel-heading" data-toggle="collapse" href="#device-this" style="cursor: pointer; display: block;">
|
||||
<h4 class="panel-title">
|
||||
<identicon data-value="deviceCfg.deviceID"></identicon> {{deviceName(deviceCfg)}}
|
||||
</h4>
|
||||
</div>
|
||||
</a>
|
||||
<div id="device-this" class="panel-collapse collapse in">
|
||||
<div class="panel-body">
|
||||
<table class="table table-condensed table-striped">
|
||||
@@ -476,10 +474,10 @@
|
||||
<h3 translate>Remote Devices</h3>
|
||||
<div class="panel-group" id="devices">
|
||||
<div class="panel panel-default" ng-repeat="deviceCfg in otherDevices()">
|
||||
<div class="panel-heading" data-toggle="collapse" data-parent="#devices" href="#device-{{$index}}" style="cursor: pointer">
|
||||
<a class="panel-heading" data-toggle="collapse" data-parent="#devices" href="#device-{{$index}}" style="cursor: pointer; display: block;">
|
||||
<div class="panel-progress" ng-show="deviceStatus(deviceCfg) == 'syncing'" ng-attr-style="width: {{completion[deviceCfg.deviceID]._total | number:0}}%"></div>
|
||||
<h4 class="panel-title">
|
||||
<identicon data-value="deviceCfg.deviceID"></identicon> <a href="#device-{{$index}}">{{deviceName(deviceCfg)}}</a>
|
||||
<identicon data-value="deviceCfg.deviceID"></identicon> {{deviceName(deviceCfg)}}
|
||||
<span ng-switch="deviceStatus(deviceCfg)" class="pull-right text-{{deviceClass(deviceCfg)}}">
|
||||
<span ng-switch-when="insync"><span class="hidden-xs" translate>Up to Date</span><span class="visible-xs">◼</span></span>
|
||||
<span ng-switch-when="syncing">
|
||||
@@ -490,7 +488,7 @@
|
||||
<span ng-switch-when="unused"><span class="hidden-xs" translate>Unused</span><span class="visible-xs">◼</span></span>
|
||||
</span>
|
||||
</h4>
|
||||
</div>
|
||||
</a>
|
||||
<div id="device-{{$index}}" class="panel-collapse collapse">
|
||||
<div class="panel-body">
|
||||
<table class="table table-condensed table-striped">
|
||||
@@ -664,6 +662,7 @@
|
||||
|
||||
<script src="assets/lang/valid-langs.js"></script>
|
||||
<script src="assets/lang/prettyprint.js"></script>
|
||||
<script src="meta.js"></script>
|
||||
<script src="syncthing/app.js"></script>
|
||||
<!-- / gui application code -->
|
||||
|
||||
|
||||
@@ -23,31 +23,9 @@ var syncthing = angular.module('syncthing', [
|
||||
var urlbase = 'rest';
|
||||
|
||||
syncthing.config(function ($httpProvider, $translateProvider, LocaleServiceProvider) {
|
||||
$httpProvider.interceptors.push(function xHeadersResponseInterceptor() {
|
||||
var deviceId = null;
|
||||
|
||||
return {
|
||||
response: function onResponse(response) {
|
||||
var headers = response.headers();
|
||||
|
||||
// angular template cache sends no headers
|
||||
if(Object.keys(headers).length === 0) {
|
||||
return response;
|
||||
}
|
||||
|
||||
if (!deviceId) {
|
||||
deviceId = headers['x-syncthing-id'];
|
||||
if (deviceId) {
|
||||
var deviceIdShort = deviceId.substring(0, 5);
|
||||
$httpProvider.defaults.xsrfHeaderName = 'X-CSRF-Token-' + deviceIdShort;
|
||||
$httpProvider.defaults.xsrfCookieName = 'CSRF-Token-' + deviceIdShort;
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
};
|
||||
});
|
||||
var deviceIDShort = metadata.deviceID.substr(0, 5);
|
||||
$httpProvider.defaults.xsrfHeaderName = 'X-CSRF-Token-' + deviceIDShort;
|
||||
$httpProvider.defaults.xsrfCookieName = 'CSRF-Token-' + deviceIDShort;
|
||||
|
||||
// language and localisation
|
||||
|
||||
@@ -59,7 +37,6 @@ syncthing.config(function ($httpProvider, $translateProvider, LocaleServiceProvi
|
||||
|
||||
LocaleServiceProvider.setAvailableLocales(validLangs);
|
||||
LocaleServiceProvider.setDefaultLocale('en');
|
||||
|
||||
});
|
||||
|
||||
// @TODO: extract global level functions into separate service(s)
|
||||
@@ -112,19 +89,6 @@ function decimals(val, num) {
|
||||
return decs;
|
||||
}
|
||||
|
||||
function randomString(len) {
|
||||
var chars = '01234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-';
|
||||
return randomStringFromCharset(len, chars);
|
||||
}
|
||||
|
||||
function randomStringFromCharset(len, charset) {
|
||||
var result = '';
|
||||
for (var i = 0; i < len; i++) {
|
||||
result += charset[Math.round(Math.random() * (charset.length - 1))];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function isEmptyObject(obj) {
|
||||
var name;
|
||||
for (name in obj) {
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<p translate>Copyright © 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, Ben Schulz, Caleb Callaway, Lars K.W. Gohlke, Lode Hoste, Michael Ploujnikov, Philippe Schommers, Ryan Sullivan, Sergey Mishin, Stefan Tatschner, Aaron Bieber, Adam Piggott, Alessandro G., Andrew Dunham, Antony Male, Arthur Axel fREW Schmidt, Bart De Vries, Ben Curthoys, Ben Sidhom, Benny Ng, Brandon Philips, Brendan Long, Brian R. Becker, Carsten Hagemann, Cathryne Linenweaver, Chris Howie, Chris Joel, Colin Kennedy, Daniel Bergmann, Daniel Harte, 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, 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, Tobias Nygren, Tomas Cerveny, Tully Robinson, Tyler Brazier, Veeti Paananen, Victor Buinsky, Vil Brekin, William A. Kennington III, Wulf Weich, Yannic A.
|
||||
Jakob Borg, Audrius Butkevicius, Alexander Graf, Anderson Mesquita, Ben Schulz, Caleb Callaway, 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, Antony Male, Arthur Axel fREW Schmidt, Bart De Vries, Ben Curthoys, Ben Sidhom, Benny Ng, Brandon Philips, Brendan Long, Brian R. Becker, Carsten Hagemann, Cathryne Linenweaver, Chris Howie, Chris Joel, Colin Kennedy, Daniel Bergmann, Daniel Harte, 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, 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, Tobias Nygren, Tomas Cerveny, Tully Robinson, Tyler Brazier, Veeti Paananen, Victor Buinsky, Vil Brekin, William A. Kennington III, Wulf Weich, Yannic A.
|
||||
</div>
|
||||
</div>
|
||||
<hr/>
|
||||
|
||||
@@ -1196,7 +1196,6 @@ angular.module('syncthing.core')
|
||||
$scope.addFolder = function () {
|
||||
$scope.currentFolder = {
|
||||
selectedDevices: {},
|
||||
id: $scope.createRandomFolderId(),
|
||||
type: "readwrite",
|
||||
rescanIntervalS: 60,
|
||||
minDiskFreePct: 1,
|
||||
@@ -1213,7 +1212,10 @@ angular.module('syncthing.core')
|
||||
};
|
||||
$scope.editingExisting = false;
|
||||
$scope.folderEditor.$setPristine();
|
||||
$('#editFolder').modal();
|
||||
$http.get(urlbase + '/svc/random/string?length=10').success(function (data) {
|
||||
$scope.currentFolder.id = data.random.substr(0, 5) + '-' + data.random.substr(5, 5);
|
||||
$('#editFolder').modal();
|
||||
});
|
||||
};
|
||||
|
||||
$scope.addFolderAndShare = function (folder, folderLabel, device) {
|
||||
@@ -1406,7 +1408,9 @@ angular.module('syncthing.core')
|
||||
};
|
||||
|
||||
$scope.setAPIKey = function (cfg) {
|
||||
cfg.apiKey = randomString(32);
|
||||
$http.get(urlbase + '/svc/random/string?length=32').success(function (data) {
|
||||
cfg.apiKey = data.random;
|
||||
});
|
||||
};
|
||||
|
||||
$scope.showURPreview = function () {
|
||||
@@ -1544,11 +1548,6 @@ angular.module('syncthing.core')
|
||||
return 'text';
|
||||
};
|
||||
|
||||
$scope.createRandomFolderId = function(){
|
||||
var charset = '2345679abcdefghijkmnopqrstuvwxyzACDEFGHJKLMNPQRSTUVWXYZ';
|
||||
return randomStringFromCharset(5, charset) + "-" + randomStringFromCharset(5, charset);
|
||||
};
|
||||
|
||||
$scope.themeName = function (theme) {
|
||||
return theme.replace('-', ' ').replace(/(?:^|\s)\S/g, function (a) {
|
||||
return a.toUpperCase();
|
||||
|
||||
@@ -12,6 +12,25 @@
|
||||
* https://github.com/angular-ui/bootstrap/blob/master/src/pagination/pagination.js
|
||||
*
|
||||
* Copyright 2014 Michael Bromley <michael@michaelbromley.co.uk>
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
(function() {
|
||||
|
||||
@@ -2,6 +2,25 @@
|
||||
* angular-translate - v2.11.0 - 2016-03-20
|
||||
*
|
||||
* Copyright (c) 2016 The angular-translate team, Pascal Precht; Licensed MIT
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
(function (root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
|
||||
19
gui/default/vendor/angular/angular-translate.js
vendored
19
gui/default/vendor/angular/angular-translate.js
vendored
@@ -2,6 +2,25 @@
|
||||
* angular-translate - v2.9.0 - 2016-01-24
|
||||
*
|
||||
* Copyright (c) 2016 The angular-translate team, Pascal Precht; Licensed MIT
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
(function (root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
|
||||
115
gui/default/vendor/angular/angular.js
vendored
115
gui/default/vendor/angular/angular.js
vendored
@@ -2,6 +2,25 @@
|
||||
* @license AngularJS v1.2.9
|
||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
||||
* License: MIT
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
(function(window, document, undefined) {'use strict';
|
||||
|
||||
@@ -1747,10 +1766,10 @@ function setupModuleLoader(window) {
|
||||
/* global
|
||||
angularModule: true,
|
||||
version: true,
|
||||
|
||||
|
||||
$LocaleProvider,
|
||||
$CompileProvider,
|
||||
|
||||
|
||||
htmlAnchorDirective,
|
||||
inputDirective,
|
||||
inputDirective,
|
||||
@@ -3414,11 +3433,11 @@ function annotate(fn) {
|
||||
* var Ping = function() {
|
||||
* this.$http = $http;
|
||||
* };
|
||||
*
|
||||
*
|
||||
* Ping.prototype.send = function() {
|
||||
* return this.$http.get('/ping');
|
||||
* };
|
||||
*
|
||||
* };
|
||||
*
|
||||
* return Ping;
|
||||
* }]);
|
||||
* </pre>
|
||||
@@ -3749,7 +3768,7 @@ function createInjector(modulesToLoad) {
|
||||
*
|
||||
* It also watches the `$location.hash()` and scrolls whenever it changes to match any anchor.
|
||||
* This can be disabled by calling `$anchorScrollProvider.disableAutoScrolling()`.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
<example>
|
||||
<file name="index.html">
|
||||
@@ -3764,7 +3783,7 @@ function createInjector(modulesToLoad) {
|
||||
// set the location.hash to the id of
|
||||
// the element you wish to scroll to.
|
||||
$location.hash('bottom');
|
||||
|
||||
|
||||
// call $anchorScroll()
|
||||
$anchorScroll();
|
||||
}
|
||||
@@ -3852,7 +3871,7 @@ var $animateMinErr = minErr('$animate');
|
||||
*/
|
||||
var $AnimateProvider = ['$provide', function($provide) {
|
||||
|
||||
|
||||
|
||||
this.$$selectors = {};
|
||||
|
||||
|
||||
@@ -3993,7 +4012,7 @@ var $AnimateProvider = ['$provide', function($provide) {
|
||||
* @description Moves the position of the provided element within the DOM to be placed
|
||||
* either after the `after` element or inside of the `parent` element. Once complete, the
|
||||
* done() callback will be fired (if provided).
|
||||
*
|
||||
*
|
||||
* @param {jQuery/jqLite element} element the element which will be moved around within the
|
||||
* DOM
|
||||
* @param {jQuery/jqLite element} parent the parent element where the element will be
|
||||
@@ -4458,9 +4477,9 @@ function $BrowserProvider(){
|
||||
*
|
||||
* @description
|
||||
* Factory that constructs cache objects and gives access to them.
|
||||
*
|
||||
*
|
||||
* <pre>
|
||||
*
|
||||
*
|
||||
* var cache = $cacheFactory('cacheId');
|
||||
* expect($cacheFactory.get('cacheId')).toBe(cache);
|
||||
* expect($cacheFactory.get('noSuchCacheId')).not.toBeDefined();
|
||||
@@ -4469,8 +4488,8 @@ function $BrowserProvider(){
|
||||
* cache.put("another key", "another value");
|
||||
*
|
||||
* // We've specified no options on creation
|
||||
* expect(cache.info()).toEqual({id: 'cacheId', size: 2});
|
||||
*
|
||||
* expect(cache.info()).toEqual({id: 'cacheId', size: 2});
|
||||
*
|
||||
* </pre>
|
||||
*
|
||||
*
|
||||
@@ -4653,7 +4672,7 @@ function $CacheFactoryProvider() {
|
||||
* The first time a template is used, it is loaded in the template cache for quick retrieval. You
|
||||
* can load templates directly into the cache in a `script` tag, or by consuming the
|
||||
* `$templateCache` service directly.
|
||||
*
|
||||
*
|
||||
* Adding via the `script` tag:
|
||||
* <pre>
|
||||
* <html ng-app>
|
||||
@@ -4665,29 +4684,29 @@ function $CacheFactoryProvider() {
|
||||
* ...
|
||||
* </html>
|
||||
* </pre>
|
||||
*
|
||||
*
|
||||
* **Note:** the `script` tag containing the template does not need to be included in the `head` of
|
||||
* the document, but it must be below the `ng-app` definition.
|
||||
*
|
||||
*
|
||||
* Adding via the $templateCache service:
|
||||
*
|
||||
*
|
||||
* <pre>
|
||||
* var myApp = angular.module('myApp', []);
|
||||
* myApp.run(function($templateCache) {
|
||||
* $templateCache.put('templateId.html', 'This is the content of the template');
|
||||
* });
|
||||
* </pre>
|
||||
*
|
||||
*
|
||||
* To retrieve the template later, simply use it in your HTML:
|
||||
* <pre>
|
||||
* <div ng-include=" 'templateId.html' "></div>
|
||||
* </pre>
|
||||
*
|
||||
*
|
||||
* or get it via Javascript:
|
||||
* <pre>
|
||||
* $templateCache.get('templateId.html')
|
||||
* </pre>
|
||||
*
|
||||
*
|
||||
* See {@link ng.$cacheFactory $cacheFactory}.
|
||||
*
|
||||
*/
|
||||
@@ -6809,12 +6828,12 @@ function $DocumentProvider(){
|
||||
* Any uncaught exception in angular expressions is delegated to this service.
|
||||
* The default implementation simply delegates to `$log.error` which logs it into
|
||||
* the browser console.
|
||||
*
|
||||
*
|
||||
* In unit tests, if `angular-mocks.js` is loaded, this service is overridden by
|
||||
* {@link ngMock.$exceptionHandler mock $exceptionHandler} which aids in testing.
|
||||
*
|
||||
* ## Example:
|
||||
*
|
||||
*
|
||||
* <pre>
|
||||
* angular.module('exceptionOverride', []).factory('$exceptionHandler', function () {
|
||||
* return function (exception, cause) {
|
||||
@@ -6823,7 +6842,7 @@ function $DocumentProvider(){
|
||||
* };
|
||||
* });
|
||||
* </pre>
|
||||
*
|
||||
*
|
||||
* This example will override the normal action of `$exceptionHandler`, to make angular
|
||||
* exceptions fail hard when they happen, instead of just logging to the console.
|
||||
*
|
||||
@@ -8322,7 +8341,7 @@ function $IntervalProvider() {
|
||||
* In tests you can use {@link ngMock.$interval#methods_flush `$interval.flush(millis)`} to
|
||||
* move forward by `millis` milliseconds and trigger any functions scheduled to run in that
|
||||
* time.
|
||||
*
|
||||
*
|
||||
* <div class="alert alert-warning">
|
||||
* **Note**: Intervals created by this service must be explicitly destroyed when you are finished
|
||||
* with them. In particular they are not automatically destroyed when a controller's scope or a
|
||||
@@ -8435,7 +8454,7 @@ function $IntervalProvider() {
|
||||
promise = deferred.promise,
|
||||
iteration = 0,
|
||||
skipApply = (isDefined(invokeApply) && !invokeApply);
|
||||
|
||||
|
||||
count = isDefined(count) ? count : 0,
|
||||
|
||||
promise.then(null, null, fn);
|
||||
@@ -9270,7 +9289,7 @@ function $LocationProvider(){
|
||||
* @description
|
||||
* Simple service for logging. Default implementation safely writes the message
|
||||
* into the browser's console (if present).
|
||||
*
|
||||
*
|
||||
* The main purpose of this service is to simplify debugging and troubleshooting.
|
||||
*
|
||||
* The default is to log `debug` messages. You can use
|
||||
@@ -9307,7 +9326,7 @@ function $LocationProvider(){
|
||||
function $LogProvider(){
|
||||
var debug = true,
|
||||
self = this;
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc property
|
||||
* @name ng.$logProvider#debugEnabled
|
||||
@@ -9324,7 +9343,7 @@ function $LogProvider(){
|
||||
return debug;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
this.$get = ['$window', function($window){
|
||||
return {
|
||||
/**
|
||||
@@ -9366,12 +9385,12 @@ function $LogProvider(){
|
||||
* Write an error message
|
||||
*/
|
||||
error: consoleLog('error'),
|
||||
|
||||
|
||||
/**
|
||||
* @ngdoc method
|
||||
* @name ng.$log#debug
|
||||
* @methodOf ng.$log
|
||||
*
|
||||
*
|
||||
* @description
|
||||
* Write a debug message
|
||||
*/
|
||||
@@ -12823,7 +12842,7 @@ function $SceDelegateProvider() {
|
||||
* allowing only the files in a specific directory to do this. Ensuring that the internal API
|
||||
* exposed by that code doesn't markup arbitrary values as safe then becomes a more manageable task.
|
||||
*
|
||||
* In the case of AngularJS' SCE service, one uses {@link ng.$sce#methods_trustAs $sce.trustAs}
|
||||
* In the case of AngularJS' SCE service, one uses {@link ng.$sce#methods_trustAs $sce.trustAs}
|
||||
* (and shorthand methods such as {@link ng.$sce#methods_trustAsHtml $sce.trustAsHtml}, etc.) to
|
||||
* obtain values that will be accepted by SCE / privileged contexts.
|
||||
*
|
||||
@@ -13572,7 +13591,7 @@ function $TimeoutProvider() {
|
||||
* will invoke `fn` within the {@link ng.$rootScope.Scope#methods_$apply $apply} block.
|
||||
* @returns {Promise} Promise that will be resolved when the timeout is reached. The value this
|
||||
* promise will be resolved with is the return value of the `fn` function.
|
||||
*
|
||||
*
|
||||
*/
|
||||
function timeout(fn, delay, invokeApply) {
|
||||
var deferred = $q.defer(),
|
||||
@@ -13806,7 +13825,7 @@ function $WindowProvider(){
|
||||
*
|
||||
* The filter function is registered with the `$injector` under the filter name suffix with
|
||||
* `Filter`.
|
||||
*
|
||||
*
|
||||
* <pre>
|
||||
* it('should be the same instance', inject(
|
||||
* function($filterProvider) {
|
||||
@@ -13882,7 +13901,7 @@ function $FilterProvider($provide) {
|
||||
}];
|
||||
|
||||
////////////////////////////////////////
|
||||
|
||||
|
||||
/* global
|
||||
currencyFilter: false,
|
||||
dateFilter: false,
|
||||
@@ -14590,9 +14609,9 @@ var uppercaseFilter = valueFn(uppercase);
|
||||
* the value and sign (positive or negative) of `limit`.
|
||||
*
|
||||
* @param {Array|string} input Source array or string to be limited.
|
||||
* @param {string|number} limit The length of the returned array or string. If the `limit` number
|
||||
* @param {string|number} limit The length of the returned array or string. If the `limit` number
|
||||
* is positive, `limit` number of items from the beginning of the source array/string are copied.
|
||||
* If the number is negative, `limit` number of items from the end of the source array/string
|
||||
* If the number is negative, `limit` number of items from the end of the source array/string
|
||||
* are copied. The `limit` will be trimmed if it exceeds `array.length`
|
||||
* @returns {Array|string} A new sub-array or substring of length `limit` or less if input array
|
||||
* had less than `limit` elements.
|
||||
@@ -14642,7 +14661,7 @@ var uppercaseFilter = valueFn(uppercase);
|
||||
function limitToFilter(){
|
||||
return function(input, limit) {
|
||||
if (!isArray(input) && !isString(input)) return input;
|
||||
|
||||
|
||||
limit = int(limit);
|
||||
|
||||
if (isString(input)) {
|
||||
@@ -15044,7 +15063,7 @@ var htmlAnchorDirective = valueFn({
|
||||
</doc:example>
|
||||
*
|
||||
* @element INPUT
|
||||
* @param {expression} ngDisabled If the {@link guide/expression expression} is truthy,
|
||||
* @param {expression} ngDisabled If the {@link guide/expression expression} is truthy,
|
||||
* then special attribute "disabled" will be set on the element
|
||||
*/
|
||||
|
||||
@@ -15079,7 +15098,7 @@ var htmlAnchorDirective = valueFn({
|
||||
</doc:example>
|
||||
*
|
||||
* @element INPUT
|
||||
* @param {expression} ngChecked If the {@link guide/expression expression} is truthy,
|
||||
* @param {expression} ngChecked If the {@link guide/expression expression} is truthy,
|
||||
* then special attribute "checked" will be set on the element
|
||||
*/
|
||||
|
||||
@@ -15114,7 +15133,7 @@ var htmlAnchorDirective = valueFn({
|
||||
</doc:example>
|
||||
*
|
||||
* @element INPUT
|
||||
* @param {expression} ngReadonly If the {@link guide/expression expression} is truthy,
|
||||
* @param {expression} ngReadonly If the {@link guide/expression expression} is truthy,
|
||||
* then special attribute "readonly" will be set on the element
|
||||
*/
|
||||
|
||||
@@ -15133,7 +15152,7 @@ var htmlAnchorDirective = valueFn({
|
||||
* The `ngSelected` directive solves this problem for the `selected` atttribute.
|
||||
* This complementary directive is not removed by the browser and so provides
|
||||
* a permanent reliable place to store the binding information.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
<doc:example>
|
||||
<doc:source>
|
||||
@@ -15153,7 +15172,7 @@ var htmlAnchorDirective = valueFn({
|
||||
</doc:example>
|
||||
*
|
||||
* @element OPTION
|
||||
* @param {expression} ngSelected If the {@link guide/expression expression} is truthy,
|
||||
* @param {expression} ngSelected If the {@link guide/expression expression} is truthy,
|
||||
* then special attribute "selected" will be set on the element
|
||||
*/
|
||||
|
||||
@@ -15189,7 +15208,7 @@ var htmlAnchorDirective = valueFn({
|
||||
</doc:example>
|
||||
*
|
||||
* @element DETAILS
|
||||
* @param {expression} ngOpen If the {@link guide/expression expression} is truthy,
|
||||
* @param {expression} ngOpen If the {@link guide/expression expression} is truthy,
|
||||
* then special attribute "open" will be set on the element
|
||||
*/
|
||||
|
||||
@@ -15275,7 +15294,7 @@ var nullFormCtrl = {
|
||||
* - `pattern`
|
||||
* - `required`
|
||||
* - `url`
|
||||
*
|
||||
*
|
||||
* @description
|
||||
* `FormController` keeps track of all its controls and nested forms as well as state of them,
|
||||
* such as being valid/invalid or dirty/pristine.
|
||||
@@ -17184,14 +17203,14 @@ var ngBindTemplateDirective = ['$interpolate', function($interpolate) {
|
||||
*
|
||||
* @example
|
||||
Try it here: enter text in text box and watch the greeting change.
|
||||
|
||||
|
||||
<example module="ngBindHtmlExample" deps="angular-sanitize.js">
|
||||
<file name="index.html">
|
||||
<div ng-controller="ngBindHtmlCtrl">
|
||||
<p ng-bind-html="myHTML"></p>
|
||||
</div>
|
||||
</file>
|
||||
|
||||
|
||||
<file name="script.js">
|
||||
angular.module('ngBindHtmlExample', ['ngSanitize'])
|
||||
|
||||
@@ -20349,7 +20368,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
|
||||
|
||||
// We now build up the list of options we need (we merge later)
|
||||
for (index = 0; length = keys.length, index < length; index++) {
|
||||
|
||||
|
||||
key = index;
|
||||
if (keyName) {
|
||||
key = keys[index];
|
||||
@@ -20557,4 +20576,4 @@ var styleDirective = valueFn({
|
||||
|
||||
})(window, document);
|
||||
|
||||
!angular.$$csp() && angular.element(document).find('head').prepend('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide{display:none !important;}ng\\:form{display:block;}</style>');
|
||||
!angular.$$csp() && angular.element(document).find('head').prepend('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide{display:none !important;}ng\\:form{display:block;}</style>');
|
||||
|
||||
18
gui/default/vendor/jquery/jquery-2.2.2.js
vendored
18
gui/default/vendor/jquery/jquery-2.2.2.js
vendored
@@ -9,6 +9,24 @@
|
||||
* Released under the MIT license
|
||||
* http://jquery.org/license
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
* Date: 2016-03-17T17:51Z
|
||||
*/
|
||||
|
||||
|
||||
@@ -14,16 +14,18 @@ import (
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syncthing/syncthing/lib/rand"
|
||||
"github.com/syncthing/syncthing/lib/util"
|
||||
)
|
||||
|
||||
const (
|
||||
OldestHandledVersion = 10
|
||||
CurrentVersion = 14
|
||||
CurrentVersion = 15
|
||||
MaxRescanIntervalS = 365 * 24 * 60 * 60
|
||||
)
|
||||
|
||||
@@ -201,6 +203,9 @@ func (cfg *Configuration) prepare(myID protocol.DeviceID) {
|
||||
if cfg.Version == 13 {
|
||||
convertV13V14(cfg)
|
||||
}
|
||||
if cfg.Version == 14 {
|
||||
convertV14V15(cfg)
|
||||
}
|
||||
|
||||
// Build a list of available devices
|
||||
existingDevices := make(map[protocol.DeviceID]bool)
|
||||
@@ -250,10 +255,25 @@ func (cfg *Configuration) prepare(myID protocol.DeviceID) {
|
||||
}
|
||||
|
||||
if cfg.GUI.APIKey == "" {
|
||||
cfg.GUI.APIKey = util.RandomString(32)
|
||||
cfg.GUI.APIKey = rand.String(32)
|
||||
}
|
||||
}
|
||||
|
||||
func convertV14V15(cfg *Configuration) {
|
||||
// Undo v0.13.0 broken migration
|
||||
|
||||
for i, addr := range cfg.Options.GlobalAnnServers {
|
||||
switch addr {
|
||||
case "default-v4v2/":
|
||||
cfg.Options.GlobalAnnServers[i] = "default-v4"
|
||||
case "default-v6v2/":
|
||||
cfg.Options.GlobalAnnServers[i] = "default-v6"
|
||||
}
|
||||
}
|
||||
|
||||
cfg.Version = 15
|
||||
}
|
||||
|
||||
func convertV13V14(cfg *Configuration) {
|
||||
// Not using the ignore cache is the new default. Disable it on existing
|
||||
// configurations.
|
||||
@@ -307,12 +327,13 @@ func convertV13V14(cfg *Configuration) {
|
||||
|
||||
var newAddrs []string
|
||||
for _, addr := range cfg.Options.GlobalAnnServers {
|
||||
if addr != "default" {
|
||||
uri, err := url.Parse(addr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
uri.Path += "v2/"
|
||||
uri, err := url.Parse(addr)
|
||||
if err != nil {
|
||||
// That's odd. Skip the broken address.
|
||||
continue
|
||||
}
|
||||
if uri.Scheme == "https" {
|
||||
uri.Path = path.Join(uri.Path, "v2") + "/"
|
||||
addr = uri.String()
|
||||
}
|
||||
|
||||
|
||||
@@ -365,12 +365,12 @@ func TestIssue1750(t *testing.T) {
|
||||
t.Errorf("%q != %q", cfg.Options().ListenAddresses[1], "tcp://:23001")
|
||||
}
|
||||
|
||||
if cfg.Options().GlobalAnnServers[0] != "udp4://syncthing.nym.se:22026/v2/" {
|
||||
t.Errorf("%q != %q", cfg.Options().GlobalAnnServers[0], "udp4://syncthing.nym.se:22026/v2/")
|
||||
if cfg.Options().GlobalAnnServers[0] != "udp4://syncthing.nym.se:22026" {
|
||||
t.Errorf("%q != %q", cfg.Options().GlobalAnnServers[0], "udp4://syncthing.nym.se:22026")
|
||||
}
|
||||
|
||||
if cfg.Options().GlobalAnnServers[1] != "udp4://syncthing.nym.se:22027/v2/" {
|
||||
t.Errorf("%q != %q", cfg.Options().GlobalAnnServers[1], "udp4://syncthing.nym.se:22027/v2/")
|
||||
if cfg.Options().GlobalAnnServers[1] != "udp4://syncthing.nym.se:22027" {
|
||||
t.Errorf("%q != %q", cfg.Options().GlobalAnnServers[1], "udp4://syncthing.nym.se:22027")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
0
lib/config/testdata/deviceaddressesdynamic.xml
vendored
Executable file → Normal file
0
lib/config/testdata/deviceaddressesdynamic.xml
vendored
Executable file → Normal file
0
lib/config/testdata/deviceaddressesstatic.xml
vendored
Executable file → Normal file
0
lib/config/testdata/deviceaddressesstatic.xml
vendored
Executable file → Normal file
0
lib/config/testdata/nolistenaddress.xml
vendored
Executable file → Normal file
0
lib/config/testdata/nolistenaddress.xml
vendored
Executable file → Normal file
0
lib/config/testdata/overridenvalues.xml
vendored
Executable file → Normal file
0
lib/config/testdata/overridenvalues.xml
vendored
Executable file → Normal file
14
lib/config/testdata/v15.xml
vendored
Normal file
14
lib/config/testdata/v15.xml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
<configuration version="15">
|
||||
<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>
|
||||
</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>
|
||||
0
lib/config/testdata/v5.xml
vendored
Executable file → Normal file
0
lib/config/testdata/v5.xml
vendored
Executable file → Normal file
0
lib/config/testdata/versioningconfig.xml
vendored
Executable file → Normal file
0
lib/config/testdata/versioningconfig.xml
vendored
Executable file → Normal file
@@ -90,11 +90,13 @@ func NewService(cfg *config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *
|
||||
|
||||
// The rate variables are in KiB/s in the UI (despite the camel casing
|
||||
// of the name). We multiply by 1024 here to get B/s.
|
||||
if service.cfg.Options().MaxSendKbps > 0 {
|
||||
service.writeRateLimit = ratelimit.NewBucketWithRate(float64(1024*service.cfg.Options().MaxSendKbps), int64(5*1024*service.cfg.Options().MaxSendKbps))
|
||||
options := service.cfg.Options()
|
||||
if options.MaxSendKbps > 0 {
|
||||
service.writeRateLimit = ratelimit.NewBucketWithRate(float64(1024*options.MaxSendKbps), int64(5*1024*options.MaxSendKbps))
|
||||
}
|
||||
if service.cfg.Options().MaxRecvKbps > 0 {
|
||||
service.readRateLimit = ratelimit.NewBucketWithRate(float64(1024*service.cfg.Options().MaxRecvKbps), int64(5*1024*service.cfg.Options().MaxRecvKbps))
|
||||
|
||||
if options.MaxRecvKbps > 0 {
|
||||
service.readRateLimit = ratelimit.NewBucketWithRate(float64(1024*options.MaxRecvKbps), int64(5*1024*options.MaxRecvKbps))
|
||||
}
|
||||
|
||||
// There are several moving parts here; one routine per listening address
|
||||
|
||||
@@ -8,7 +8,6 @@ package connections
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
@@ -34,13 +33,7 @@ type tcpDialer struct {
|
||||
func (d *tcpDialer) Dial(id protocol.DeviceID, uri *url.URL) (IntermediateConnection, error) {
|
||||
uri = fixupPort(uri)
|
||||
|
||||
raddr, err := net.ResolveTCPAddr(uri.Scheme, uri.Host)
|
||||
if err != nil {
|
||||
l.Debugln(err)
|
||||
return IntermediateConnection{}, err
|
||||
}
|
||||
|
||||
conn, err := dialer.DialTimeout(raddr.Network(), raddr.String(), 10*time.Second)
|
||||
conn, err := dialer.DialTimeout(uri.Scheme, uri.Host, 10*time.Second)
|
||||
if err != nil {
|
||||
l.Debugln(err)
|
||||
return IntermediateConnection{}, err
|
||||
|
||||
@@ -179,47 +179,13 @@ func (tcpListenerFactory) Enabled(cfg config.Configuration) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func isPublicIPv4(ip net.IP) bool {
|
||||
ip = ip.To4()
|
||||
if ip == nil {
|
||||
// Not an IPv4 address (IPv6)
|
||||
return false
|
||||
}
|
||||
|
||||
// IsGlobalUnicast below only checks that it's not link local or
|
||||
// multicast, and we want to exclude private (NAT:ed) addresses as well.
|
||||
rfc1918 := []net.IPNet{
|
||||
{IP: net.IP{10, 0, 0, 0}, Mask: net.IPMask{255, 0, 0, 0}},
|
||||
{IP: net.IP{172, 16, 0, 0}, Mask: net.IPMask{255, 240, 0, 0}},
|
||||
{IP: net.IP{192, 168, 0, 0}, Mask: net.IPMask{255, 255, 0, 0}},
|
||||
}
|
||||
for _, n := range rfc1918 {
|
||||
if n.Contains(ip) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return ip.IsGlobalUnicast()
|
||||
}
|
||||
|
||||
func isPublicIPv6(ip net.IP) bool {
|
||||
if ip.To4() != nil {
|
||||
// Not an IPv6 address (IPv4)
|
||||
// (To16() returns a v6 mapped v4 address so can't be used to check
|
||||
// that it's an actual v6 address)
|
||||
return false
|
||||
}
|
||||
|
||||
return ip.IsGlobalUnicast()
|
||||
}
|
||||
|
||||
func fixupPort(uri *url.URL) *url.URL {
|
||||
copyURI := *uri
|
||||
|
||||
host, port, err := net.SplitHostPort(uri.Host)
|
||||
if err != nil && strings.HasPrefix(err.Error(), "missing port") {
|
||||
// addr is on the form "1.2.3.4"
|
||||
copyURI.Host = net.JoinHostPort(host, "22000")
|
||||
copyURI.Host = net.JoinHostPort(uri.Host, "22000")
|
||||
} else if err == nil && port == "" {
|
||||
// addr is on the form "1.2.3.4:"
|
||||
copyURI.Host = net.JoinHostPort(host, "22000")
|
||||
|
||||
@@ -27,6 +27,7 @@ const (
|
||||
DeviceRejected
|
||||
DevicePaused
|
||||
DeviceResumed
|
||||
LocalChangeDetected
|
||||
LocalIndexUpdated
|
||||
RemoteIndexUpdated
|
||||
ItemStarted
|
||||
@@ -35,6 +36,7 @@ const (
|
||||
FolderRejected
|
||||
ConfigSaved
|
||||
DownloadProgress
|
||||
RemoteDownloadProgress
|
||||
FolderSummary
|
||||
FolderCompletion
|
||||
FolderErrors
|
||||
@@ -61,6 +63,8 @@ func (t EventType) String() string {
|
||||
return "DeviceDisconnected"
|
||||
case DeviceRejected:
|
||||
return "DeviceRejected"
|
||||
case LocalChangeDetected:
|
||||
return "LocalChangeDetected"
|
||||
case LocalIndexUpdated:
|
||||
return "LocalIndexUpdated"
|
||||
case RemoteIndexUpdated:
|
||||
@@ -77,6 +81,8 @@ func (t EventType) String() string {
|
||||
return "ConfigSaved"
|
||||
case DownloadProgress:
|
||||
return "DownloadProgress"
|
||||
case RemoteDownloadProgress:
|
||||
return "RemoteDownloadProgress"
|
||||
case FolderSummary:
|
||||
return "FolderSummary"
|
||||
case FolderCompletion:
|
||||
|
||||
@@ -246,8 +246,7 @@ func parseIgnoreFile(fd io.Reader, currentFile string, seen map[string]bool) ([]
|
||||
|
||||
addPattern := func(line string) error {
|
||||
pattern := Pattern{
|
||||
pattern: line,
|
||||
result: defaultResult,
|
||||
result: defaultResult,
|
||||
}
|
||||
|
||||
// Allow prefixes to be specified in any order, but only once.
|
||||
@@ -275,6 +274,8 @@ func parseIgnoreFile(fd io.Reader, currentFile string, seen map[string]bool) ([]
|
||||
line = strings.ToLower(line)
|
||||
}
|
||||
|
||||
pattern.pattern = line
|
||||
|
||||
var err error
|
||||
if strings.HasPrefix(line, "/") {
|
||||
// Pattern is rooted in the current dir only
|
||||
|
||||
@@ -665,3 +665,41 @@ func TestCommas(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssue3164(t *testing.T) {
|
||||
stignore := `
|
||||
(?d)(?i)*.part
|
||||
(?d)(?i)/foo
|
||||
(?d)(?i)**/bar
|
||||
`
|
||||
pats := New(true)
|
||||
err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
expanded := pats.Patterns()
|
||||
t.Log(expanded)
|
||||
expected := []string{
|
||||
"(?d)(?i)*.part",
|
||||
"(?d)(?i)**/*.part",
|
||||
"(?d)(?i)*.part/**",
|
||||
"(?d)(?i)**/*.part/**",
|
||||
"(?d)(?i)/foo",
|
||||
"(?d)(?i)/foo/**",
|
||||
"(?d)(?i)**/bar",
|
||||
"(?d)(?i)bar",
|
||||
"(?d)(?i)**/bar/**",
|
||||
"(?d)(?i)bar/**",
|
||||
}
|
||||
|
||||
if len(expanded) != len(expected) {
|
||||
t.Errorf("Unmatched count: %d != %d", len(expanded), len(expected))
|
||||
}
|
||||
|
||||
for i := range expanded {
|
||||
if expanded[i] != expected[i] {
|
||||
t.Errorf("Pattern %d does not match: %s != %s", i, expanded[i], expected[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,9 +23,8 @@ type deviceFolderFileDownloadState struct {
|
||||
// deviceFolderDownloadState holds current download state of all files that
|
||||
// a remote device is currently downloading in a specific folder.
|
||||
type deviceFolderDownloadState struct {
|
||||
mut sync.RWMutex
|
||||
files map[string]deviceFolderFileDownloadState
|
||||
numberOfBlocksInProgress int
|
||||
mut sync.RWMutex
|
||||
files map[string]deviceFolderFileDownloadState
|
||||
}
|
||||
|
||||
// Has returns whether a block at that specific index, and that specific version of the file
|
||||
@@ -57,7 +56,6 @@ func (p *deviceFolderDownloadState) Update(updates []protocol.FileDownloadProgre
|
||||
for _, update := range updates {
|
||||
local, ok := p.files[update.Name]
|
||||
if update.UpdateType == protocol.UpdateTypeForget && ok && local.version.Equal(update.Version) {
|
||||
p.numberOfBlocksInProgress -= len(local.blockIndexes)
|
||||
delete(p.files, update.Name)
|
||||
} else if update.UpdateType == protocol.UpdateTypeAppend {
|
||||
if !ok {
|
||||
@@ -66,25 +64,25 @@ func (p *deviceFolderDownloadState) Update(updates []protocol.FileDownloadProgre
|
||||
version: update.Version,
|
||||
}
|
||||
} else if !local.version.Equal(update.Version) {
|
||||
p.numberOfBlocksInProgress -= len(local.blockIndexes)
|
||||
local.blockIndexes = append(local.blockIndexes[:0], update.BlockIndexes...)
|
||||
local.version = update.Version
|
||||
} else {
|
||||
local.blockIndexes = append(local.blockIndexes, update.BlockIndexes...)
|
||||
}
|
||||
p.files[update.Name] = local
|
||||
p.numberOfBlocksInProgress += len(update.BlockIndexes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NumberOfBlocksInProgress returns the number of blocks the device has downloaded
|
||||
// for a specific folder.
|
||||
func (p *deviceFolderDownloadState) NumberOfBlocksInProgress() int {
|
||||
// GetBlockCounts returns a map filename -> number of blocks downloaded.
|
||||
func (p *deviceFolderDownloadState) GetBlockCounts() map[string]int {
|
||||
p.mut.RLock()
|
||||
n := p.numberOfBlocksInProgress
|
||||
res := make(map[string]int, len(p.files))
|
||||
for name, state := range p.files {
|
||||
res[name] = len(state.blockIndexes)
|
||||
}
|
||||
p.mut.RUnlock()
|
||||
return n
|
||||
return res
|
||||
}
|
||||
|
||||
// deviceDownloadState represents the state of all in progress downloads
|
||||
@@ -134,20 +132,21 @@ func (t *deviceDownloadState) Has(folder, file string, version protocol.Vector,
|
||||
return f.Has(file, version, index)
|
||||
}
|
||||
|
||||
// NumberOfBlocksInProgress returns the number of blocks the device has downloaded
|
||||
// for all folders.
|
||||
func (t *deviceDownloadState) NumberOfBlocksInProgress() int {
|
||||
// GetBlockCounts returns a map filename -> number of blocks downloaded for the
|
||||
// given folder.
|
||||
func (t *deviceDownloadState) GetBlockCounts(folder string) map[string]int {
|
||||
if t == nil {
|
||||
return 0
|
||||
return nil
|
||||
}
|
||||
|
||||
n := 0
|
||||
t.mut.RLock()
|
||||
for _, folder := range t.folders {
|
||||
n += folder.NumberOfBlocksInProgress()
|
||||
for name, state := range t.folders {
|
||||
if name == folder {
|
||||
return state.GetBlockCounts()
|
||||
}
|
||||
}
|
||||
t.mut.RUnlock()
|
||||
return n
|
||||
return nil
|
||||
}
|
||||
|
||||
func newDeviceDownloadState() *deviceDownloadState {
|
||||
|
||||
@@ -374,17 +374,26 @@ func (m *Model) Completion(device protocol.DeviceID, folder string) float64 {
|
||||
return 100 // Folder is empty, so we have all of it
|
||||
}
|
||||
|
||||
var need int64
|
||||
m.pmut.RLock()
|
||||
counts := m.deviceDownloads[device].GetBlockCounts(folder)
|
||||
m.pmut.RUnlock()
|
||||
|
||||
var need, fileNeed, downloaded int64
|
||||
rf.WithNeedTruncated(device, func(f db.FileIntf) bool {
|
||||
need += f.Size()
|
||||
ft := f.(db.FileInfoTruncated)
|
||||
|
||||
// This might might be more than it really is, because some blocks can be of a smaller size.
|
||||
downloaded = int64(counts[ft.Name] * protocol.BlockSize)
|
||||
|
||||
fileNeed = ft.Size() - downloaded
|
||||
if fileNeed < 0 {
|
||||
fileNeed = 0
|
||||
}
|
||||
|
||||
need += fileNeed
|
||||
return true
|
||||
})
|
||||
|
||||
// This might might be more than it really is, because some blocks can be of a smaller size.
|
||||
m.pmut.RLock()
|
||||
need -= int64(m.deviceDownloads[device].NumberOfBlocksInProgress() * protocol.BlockSize)
|
||||
m.pmut.RUnlock()
|
||||
|
||||
needRatio := float64(need) / float64(tot)
|
||||
completionPct := 100 * (1 - needRatio)
|
||||
l.Debugf("%v Completion(%s, %q): %f (%d / %d = %f)", m, device, folder, completionPct, need, tot, needRatio)
|
||||
@@ -1083,7 +1092,14 @@ func (m *Model) DownloadProgress(device protocol.DeviceID, folder string, update
|
||||
|
||||
m.pmut.RLock()
|
||||
m.deviceDownloads[device].Update(folder, updates)
|
||||
state := m.deviceDownloads[device].GetBlockCounts(folder)
|
||||
m.pmut.RUnlock()
|
||||
|
||||
events.Default.Log(events.RemoteDownloadProgress, map[string]interface{}{
|
||||
"device": device.String(),
|
||||
"folder": folder,
|
||||
"state": state,
|
||||
})
|
||||
}
|
||||
|
||||
func (m *Model) ResumeDevice(device protocol.DeviceID) {
|
||||
@@ -1234,6 +1250,21 @@ func sendIndexTo(initial bool, minLocalVer int64, conn protocol.Connection, fold
|
||||
return maxLocalVer, err
|
||||
}
|
||||
|
||||
func (m *Model) updateLocalsFromScanning(folder string, fs []protocol.FileInfo) {
|
||||
m.updateLocals(folder, fs)
|
||||
|
||||
// Fire the LocalChangeDetected event to notify listeners about local
|
||||
// updates.
|
||||
m.fmut.RLock()
|
||||
path := m.folderCfgs[folder].Path()
|
||||
m.fmut.RUnlock()
|
||||
m.localChangeDetected(folder, path, fs)
|
||||
}
|
||||
|
||||
func (m *Model) updateLocalsFromPulling(folder string, fs []protocol.FileInfo) {
|
||||
m.updateLocals(folder, fs)
|
||||
}
|
||||
|
||||
func (m *Model) updateLocals(folder string, fs []protocol.FileInfo) {
|
||||
m.fmut.RLock()
|
||||
files := m.folderFiles[folder]
|
||||
@@ -1257,6 +1288,44 @@ func (m *Model) updateLocals(folder string, fs []protocol.FileInfo) {
|
||||
})
|
||||
}
|
||||
|
||||
func (m *Model) localChangeDetected(folder, path string, files []protocol.FileInfo) {
|
||||
// For windows paths, strip unwanted chars from the front
|
||||
path = strings.Replace(path, `\\?\`, "", 1)
|
||||
|
||||
for _, file := range files {
|
||||
objType := "file"
|
||||
action := "modified"
|
||||
|
||||
// If our local vector is verison 1 AND it is the only version vector so far seen for this file then
|
||||
// it is a new file. Else if it is > 1 it's not new, and if it is 1 but another shortId version vector
|
||||
// exists then it is new for us but created elsewhere so the file is still not new but modified by us.
|
||||
// Only if it is truly new do we change this to 'added', else we leave it as 'modified'.
|
||||
if len(file.Version) == 1 && file.Version[0].Value == 1 {
|
||||
action = "added"
|
||||
}
|
||||
|
||||
if file.IsDirectory() {
|
||||
objType = "dir"
|
||||
}
|
||||
if file.IsDeleted() {
|
||||
action = "deleted"
|
||||
}
|
||||
|
||||
// If the file is a level or more deep then the forward slash seperator is embedded
|
||||
// in the filename and makes the path look wierd on windows, so lets fix it
|
||||
filename := filepath.FromSlash(file.Name)
|
||||
// And append it to the filepath
|
||||
path := filepath.Join(path, filename)
|
||||
|
||||
events.Default.Log(events.LocalChangeDetected, map[string]string{
|
||||
"folder": folder,
|
||||
"action": action,
|
||||
"type": objType,
|
||||
"path": path,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Model) requestGlobal(deviceID protocol.DeviceID, folder, name string, offset int64, size int, hash []byte, fromTemporary bool) ([]byte, error) {
|
||||
m.pmut.RLock()
|
||||
nc, ok := m.conn[deviceID]
|
||||
@@ -1444,7 +1513,7 @@ func (m *Model) internalScanFolderSubdirs(folder string, subs []string) error {
|
||||
l.Infof("Stopping folder %s mid-scan due to folder error: %s", folder, err)
|
||||
return err
|
||||
}
|
||||
m.updateLocals(folder, batch)
|
||||
m.updateLocalsFromScanning(folder, batch)
|
||||
batch = batch[:0]
|
||||
blocksHandled = 0
|
||||
}
|
||||
@@ -1456,7 +1525,7 @@ func (m *Model) internalScanFolderSubdirs(folder string, subs []string) error {
|
||||
l.Infof("Stopping folder %s mid-scan due to folder error: %s", folder, err)
|
||||
return err
|
||||
} else if len(batch) > 0 {
|
||||
m.updateLocals(folder, batch)
|
||||
m.updateLocalsFromScanning(folder, batch)
|
||||
}
|
||||
|
||||
if len(subs) == 0 {
|
||||
@@ -1478,7 +1547,7 @@ func (m *Model) internalScanFolderSubdirs(folder string, subs []string) error {
|
||||
iterError = err
|
||||
return false
|
||||
}
|
||||
m.updateLocals(folder, batch)
|
||||
m.updateLocalsFromScanning(folder, batch)
|
||||
batch = batch[:0]
|
||||
}
|
||||
|
||||
@@ -1530,7 +1599,7 @@ func (m *Model) internalScanFolderSubdirs(folder string, subs []string) error {
|
||||
l.Infof("Stopping folder %s mid-scan due to folder error: %s", folder, err)
|
||||
return err
|
||||
} else if len(batch) > 0 {
|
||||
m.updateLocals(folder, batch)
|
||||
m.updateLocalsFromScanning(folder, batch)
|
||||
}
|
||||
|
||||
runner.setState(FolderIdle)
|
||||
@@ -1658,7 +1727,7 @@ func (m *Model) Override(folder string) {
|
||||
fs.WithNeed(protocol.LocalDeviceID, func(fi db.FileIntf) bool {
|
||||
need := fi.(protocol.FileInfo)
|
||||
if len(batch) == indexBatchSize {
|
||||
m.updateLocals(folder, batch)
|
||||
m.updateLocalsFromScanning(folder, batch)
|
||||
batch = batch[:0]
|
||||
}
|
||||
|
||||
@@ -1678,7 +1747,7 @@ func (m *Model) Override(folder string) {
|
||||
return true
|
||||
})
|
||||
if len(batch) > 0 {
|
||||
m.updateLocals(folder, batch)
|
||||
m.updateLocalsFromScanning(folder, batch)
|
||||
}
|
||||
runner.setState(FolderIdle)
|
||||
}
|
||||
|
||||
@@ -245,6 +245,18 @@ func TestSendDownloadProgressMessages(t *testing.T) {
|
||||
expect(1, state1, protocol.UpdateTypeAppend, v2, []int32{1, 2}, true)
|
||||
expectEmpty()
|
||||
|
||||
// Returns forget and append if sharedPullerState creation timer changes.
|
||||
|
||||
state1.available = []int32{1}
|
||||
state1.availableUpdated = tick()
|
||||
state1.created = tick()
|
||||
|
||||
p.sendDownloadProgressMessages()
|
||||
|
||||
expect(0, state1, protocol.UpdateTypeForget, v2, nil, false)
|
||||
expect(1, state1, protocol.UpdateTypeAppend, v2, []int32{1}, true)
|
||||
expectEmpty()
|
||||
|
||||
// Sends an empty update if new file exists, but does not have any blocks yet. (To indicate that the old blocks are no longer available)
|
||||
state1.file.Version = v1
|
||||
state1.available = nil
|
||||
|
||||
@@ -676,8 +676,9 @@ func (f *rwFolder) deleteDir(file protocol.FileInfo, matcher *ignore.Matcher) {
|
||||
if dir != nil {
|
||||
files, _ := dir.Readdirnames(-1)
|
||||
for _, dirFile := range files {
|
||||
if defTempNamer.IsTemporary(dirFile) || (matcher != nil && matcher.Match(filepath.Join(file.Name, dirFile)).IsDeletable()) {
|
||||
osutil.InWritableDir(osutil.Remove, filepath.Join(realName, dirFile))
|
||||
fullDirFile := filepath.Join(file.Name, dirFile)
|
||||
if defTempNamer.IsTemporary(dirFile) || (matcher != nil && matcher.Match(fullDirFile).IsDeletable()) {
|
||||
osutil.RemoveAll(fullDirFile)
|
||||
}
|
||||
}
|
||||
dir.Close()
|
||||
@@ -1011,6 +1012,7 @@ func (f *rwFolder) handleFile(file protocol.FileInfo, copyChan chan<- copyBlocks
|
||||
version: curFile.Version,
|
||||
mut: sync.NewRWMutex(),
|
||||
sparse: f.allowSparse,
|
||||
created: time.Now(),
|
||||
}
|
||||
|
||||
l.Debugf("%v need file %s; copy %d, reused %v", f, file.Name, len(blocks), reused)
|
||||
@@ -1297,8 +1299,9 @@ func (f *rwFolder) performFinish(state *sharedPullerState) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Replace the original content with the new one
|
||||
if err := osutil.Rename(state.tempName, state.realName); err != nil {
|
||||
// Replace the original content with the new one. If it didn't work,
|
||||
// leave the temp file in place for reuse.
|
||||
if err := osutil.TryRename(state.tempName, state.realName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1398,7 +1401,9 @@ func (f *rwFolder) dbUpdaterRoutine() {
|
||||
lastFile = job.file
|
||||
}
|
||||
|
||||
f.model.updateLocals(f.folderID, files)
|
||||
// All updates to file/folder objects that originated remotely
|
||||
// (across the network) use this call to updateLocals
|
||||
f.model.updateLocalsFromPulling(f.folderID, files)
|
||||
|
||||
if found {
|
||||
f.model.receivedFile(f.folderID, lastFile)
|
||||
|
||||
@@ -62,7 +62,7 @@ func setUpModel(file protocol.FileInfo) *Model {
|
||||
model := NewModel(defaultConfig, protocol.LocalDeviceID, "device", "syncthing", "dev", db, nil)
|
||||
model.AddFolder(defaultFolderConfig)
|
||||
// Update index
|
||||
model.updateLocals("default", []protocol.FileInfo{file})
|
||||
model.updateLocalsFromScanning("default", []protocol.FileInfo{file})
|
||||
return model
|
||||
}
|
||||
|
||||
@@ -255,7 +255,7 @@ func TestCopierCleanup(t *testing.T) {
|
||||
file.Blocks = []protocol.BlockInfo{blocks[1]}
|
||||
file.Version = file.Version.Update(protocol.LocalDeviceID.Short())
|
||||
// Update index (removing old blocks)
|
||||
m.updateLocals("default", []protocol.FileInfo{file})
|
||||
m.updateLocalsFromScanning("default", []protocol.FileInfo{file})
|
||||
|
||||
if m.finder.Iterate(folders, blocks[0].Hash, iterFn) {
|
||||
t.Error("Unexpected block found")
|
||||
@@ -268,7 +268,7 @@ func TestCopierCleanup(t *testing.T) {
|
||||
file.Blocks = []protocol.BlockInfo{blocks[0]}
|
||||
file.Version = file.Version.Update(protocol.LocalDeviceID.Short())
|
||||
// Update index (removing old blocks)
|
||||
m.updateLocals("default", []protocol.FileInfo{file})
|
||||
m.updateLocalsFromScanning("default", []protocol.FileInfo{file})
|
||||
|
||||
if !m.finder.Iterate(folders, blocks[0].Hash, iterFn) {
|
||||
t.Error("Unexpected block found")
|
||||
|
||||
@@ -18,6 +18,7 @@ type sentFolderFileDownloadState struct {
|
||||
blockIndexes []int32
|
||||
version protocol.Vector
|
||||
updated time.Time
|
||||
created time.Time
|
||||
}
|
||||
|
||||
// sentFolderDownloadState represents a state of what we've announced as available
|
||||
@@ -41,6 +42,7 @@ func (s *sentFolderDownloadState) update(pullers []*sharedPullerState) []protoco
|
||||
pullerBlockIndexes := puller.Available()
|
||||
pullerVersion := puller.file.Version
|
||||
pullerBlockIndexesUpdated := puller.AvailableUpdated()
|
||||
pullerCreated := puller.created
|
||||
|
||||
localFile, ok := s.files[name]
|
||||
|
||||
@@ -52,6 +54,7 @@ func (s *sentFolderDownloadState) update(pullers []*sharedPullerState) []protoco
|
||||
blockIndexes: pullerBlockIndexes,
|
||||
updated: pullerBlockIndexesUpdated,
|
||||
version: pullerVersion,
|
||||
created: pullerCreated,
|
||||
}
|
||||
|
||||
updates = append(updates, protocol.FileDownloadProgressUpdate{
|
||||
@@ -70,9 +73,9 @@ func (s *sentFolderDownloadState) update(pullers []*sharedPullerState) []protoco
|
||||
continue
|
||||
}
|
||||
|
||||
if !pullerVersion.Equal(localFile.version) {
|
||||
// The version has changed, clean up whatever we had for the old
|
||||
// file, and advertise the new file.
|
||||
if !pullerVersion.Equal(localFile.version) || !pullerCreated.Equal(localFile.created) {
|
||||
// The version has changed or the puller was reconstrcuted due to failure.
|
||||
// Clean up whatever we had for the old file, and advertise the new file.
|
||||
updates = append(updates, protocol.FileDownloadProgressUpdate{
|
||||
Name: name,
|
||||
Version: localFile.version,
|
||||
@@ -87,6 +90,7 @@ func (s *sentFolderDownloadState) update(pullers []*sharedPullerState) []protoco
|
||||
localFile.blockIndexes = pullerBlockIndexes
|
||||
localFile.updated = pullerBlockIndexesUpdated
|
||||
localFile.version = pullerVersion
|
||||
localFile.created = pullerCreated
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ type sharedPullerState struct {
|
||||
ignorePerms bool
|
||||
version protocol.Vector // The current (old) version
|
||||
sparse bool
|
||||
created time.Time
|
||||
|
||||
// Mutable, must be locked for access
|
||||
err error // The first error we hit
|
||||
|
||||
@@ -1,7 +1,31 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// Modified by Zillode to fix https://github.com/syncthing/syncthing/issues/1822
|
||||
// Sync with https://github.com/golang/go/blob/master/src/os/path.go
|
||||
// See https://github.com/golang/go/issues/10900
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/calmh/du"
|
||||
"github.com/syncthing/syncthing/lib/sync"
|
||||
@@ -107,6 +108,78 @@ func Remove(path string) error {
|
||||
return os.Remove(path)
|
||||
}
|
||||
|
||||
// RemoveAll is a copy of os.RemoveAll, but uses osutil.Remove.
|
||||
// RemoveAll removes path and any children it contains.
|
||||
// It removes everything it can but returns the first error
|
||||
// it encounters. If the path does not exist, RemoveAll
|
||||
// returns nil (no error).
|
||||
func RemoveAll(path string) error {
|
||||
// Simple case: if Remove works, we're done.
|
||||
err := Remove(path)
|
||||
if err == nil || os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Otherwise, is this a directory we need to recurse into?
|
||||
dir, serr := os.Lstat(path)
|
||||
if serr != nil {
|
||||
if serr, ok := serr.(*os.PathError); ok && (os.IsNotExist(serr.Err) || serr.Err == syscall.ENOTDIR) {
|
||||
return nil
|
||||
}
|
||||
return serr
|
||||
}
|
||||
if !dir.IsDir() {
|
||||
// Not a directory; return the error from Remove.
|
||||
return err
|
||||
}
|
||||
|
||||
// Directory.
|
||||
fd, err := os.Open(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// Race. It was deleted between the Lstat and Open.
|
||||
// Return nil per RemoveAll's docs.
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove contents & return first error.
|
||||
err = nil
|
||||
for {
|
||||
names, err1 := fd.Readdirnames(100)
|
||||
for _, name := range names {
|
||||
err1 := RemoveAll(path + string(os.PathSeparator) + name)
|
||||
if err == nil {
|
||||
err = err1
|
||||
}
|
||||
}
|
||||
if err1 == io.EOF {
|
||||
break
|
||||
}
|
||||
// If Readdirnames returned an error, use it.
|
||||
if err == nil {
|
||||
err = err1
|
||||
}
|
||||
if len(names) == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Close directory, because windows won't remove opened directory.
|
||||
fd.Close()
|
||||
|
||||
// Remove directory.
|
||||
err1 := Remove(path)
|
||||
if err1 == nil || os.IsNotExist(err1) {
|
||||
return nil
|
||||
}
|
||||
if err == nil {
|
||||
err = err1
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func ExpandTilde(path string) (string, error) {
|
||||
if path == "~" {
|
||||
return getHomeDir()
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/AudriusButkevicius/go-nat-pmp"
|
||||
"github.com/jackpal/gateway"
|
||||
"github.com/calmh/gateway"
|
||||
"github.com/syncthing/syncthing/lib/nat"
|
||||
)
|
||||
|
||||
|
||||
@@ -98,7 +98,7 @@ func (v Vector) GreaterEqual(b Vector) bool {
|
||||
return comp == Greater || comp == Equal
|
||||
}
|
||||
|
||||
// Concurrent returns true when the two vectors are concrurrent.
|
||||
// Concurrent returns true when the two vectors are concurrent.
|
||||
func (v Vector) Concurrent(b Vector) bool {
|
||||
comp := v.Compare(b)
|
||||
return comp == ConcurrentGreater || comp == ConcurrentLesser
|
||||
|
||||
75
lib/rand/random.go
Normal file
75
lib/rand/random.go
Normal file
@@ -0,0 +1,75 @@
|
||||
// Copyright (C) 2014 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 rand implements functions similar to math/rand in the standard
|
||||
// library, but on top of a secure random number generator.
|
||||
package rand
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
cryptoRand "crypto/rand"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
mathRand "math/rand"
|
||||
)
|
||||
|
||||
// Reader is the standard crypto/rand.Reader, re-exported for convenience
|
||||
var Reader = cryptoRand.Reader
|
||||
|
||||
// randomCharset contains the characters that can make up a randomString().
|
||||
const randomCharset = "2345679abcdefghijkmnopqrstuvwxyzACDEFGHJKLMNPQRSTUVWXYZ"
|
||||
|
||||
var (
|
||||
// defaultSecureSource is a concurrency safe math/rand.Source with a
|
||||
// cryptographically sound base.
|
||||
defaltSecureSource = newSecureSource()
|
||||
|
||||
// defaultSecureRand is a math/rand.Rand based on the secure source.
|
||||
defaultSecureRand = mathRand.New(defaltSecureSource)
|
||||
)
|
||||
|
||||
// String returns a strongly random string of characters (taken from
|
||||
// randomCharset) of the specified length. The returned string contains ~5.8
|
||||
// bits of entropy per character, due to the character set used.
|
||||
func String(l int) string {
|
||||
bs := make([]byte, l)
|
||||
for i := range bs {
|
||||
bs[i] = randomCharset[defaultSecureRand.Intn(len(randomCharset))]
|
||||
}
|
||||
return string(bs)
|
||||
}
|
||||
|
||||
// Int63 returns a strongly random int63
|
||||
func Int63() int64 {
|
||||
return defaltSecureSource.Int63()
|
||||
}
|
||||
|
||||
// Int64 returns a strongly random int64
|
||||
func Int64() int64 {
|
||||
var bs [8]byte
|
||||
_, err := io.ReadFull(cryptoRand.Reader, bs[:])
|
||||
if err != nil {
|
||||
panic("randomness failure: " + err.Error())
|
||||
}
|
||||
return int64(binary.BigEndian.Uint64(bs[:]))
|
||||
}
|
||||
|
||||
// Intn returns, as an int, a non-negative strongly random number in [0,n).
|
||||
// It panics if n <= 0.
|
||||
func Intn(n int) int {
|
||||
return defaultSecureRand.Intn(n)
|
||||
}
|
||||
|
||||
// SeedFromBytes calculates a weak 64 bit hash from the given byte slice,
|
||||
// suitable for use a predictable random seed.
|
||||
func SeedFromBytes(bs []byte) int64 {
|
||||
h := md5.New()
|
||||
h.Write(bs)
|
||||
s := h.Sum(nil)
|
||||
// The MD5 hash of the byte slice is 16 bytes long. We interpret it as two
|
||||
// uint64s and XOR them together.
|
||||
return int64(binary.BigEndian.Uint64(s[0:]) ^ binary.BigEndian.Uint64(s[8:]))
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
// 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 util
|
||||
package rand
|
||||
|
||||
import "testing"
|
||||
|
||||
@@ -27,7 +27,7 @@ func TestSeedFromBytes(t *testing.T) {
|
||||
|
||||
func TestRandomString(t *testing.T) {
|
||||
for _, l := range []int{0, 1, 2, 3, 4, 8, 42} {
|
||||
s := RandomString(l)
|
||||
s := String(l)
|
||||
if len(s) != l {
|
||||
t.Errorf("Incorrect length %d != %d", len(s), l)
|
||||
}
|
||||
@@ -35,7 +35,7 @@ func TestRandomString(t *testing.T) {
|
||||
|
||||
strings := make([]string, 1000)
|
||||
for i := range strings {
|
||||
strings[i] = RandomString(8)
|
||||
strings[i] = String(8)
|
||||
for j := range strings {
|
||||
if i == j {
|
||||
continue
|
||||
@@ -50,7 +50,7 @@ func TestRandomString(t *testing.T) {
|
||||
func TestRandomInt64(t *testing.T) {
|
||||
ints := make([]int64, 1000)
|
||||
for i := range ints {
|
||||
ints[i] = RandomInt64()
|
||||
ints[i] = Int64()
|
||||
for j := range ints {
|
||||
if i == j {
|
||||
continue
|
||||
57
lib/rand/securesource.go
Normal file
57
lib/rand/securesource.go
Normal file
@@ -0,0 +1,57 @@
|
||||
// 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 rand
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// The secureSource is a math/rand.Source that reads bytes from
|
||||
// crypto/rand.Reader. It means we can use the convenience functions
|
||||
// provided by math/rand.Rand on top of a secure source of numbers. It is
|
||||
// concurrency safe for ease of use.
|
||||
type secureSource struct {
|
||||
rd io.Reader
|
||||
mut sync.Mutex
|
||||
}
|
||||
|
||||
func newSecureSource() *secureSource {
|
||||
return &secureSource{
|
||||
// Using buffering on top of the rand.Reader increases our
|
||||
// performance by about 20%, even though it means we must use
|
||||
// locking.
|
||||
rd: bufio.NewReader(rand.Reader),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *secureSource) Seed(int64) {
|
||||
panic("SecureSource is not seedable")
|
||||
}
|
||||
|
||||
func (s *secureSource) Int63() int64 {
|
||||
var buf [8]byte
|
||||
|
||||
// Read eight bytes of entropy from the buffered, secure random number
|
||||
// generator. The buffered reader isn't concurrency safe, so we lock
|
||||
// around that.
|
||||
s.mut.Lock()
|
||||
_, err := io.ReadFull(s.rd, buf[:])
|
||||
s.mut.Unlock()
|
||||
if err != nil {
|
||||
panic("randomness failure: " + err.Error())
|
||||
}
|
||||
|
||||
// Grab those bytes as an uint64
|
||||
v := binary.BigEndian.Uint64(buf[:])
|
||||
|
||||
// Mask of the high bit and return the resulting int63
|
||||
return int64(v & (1<<63 - 1))
|
||||
}
|
||||
96
lib/rand/securesource_test.go
Normal file
96
lib/rand/securesource_test.go
Normal file
@@ -0,0 +1,96 @@
|
||||
// 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 rand
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestSecureSource(t *testing.T) {
|
||||
// This is not a test to verify that the random numbers are secure,
|
||||
// merely that the numbers look random at all and that we've haven't
|
||||
// broken the implementation by masking off half the bits or always
|
||||
// returning "4" (chosen by fair dice roll),
|
||||
|
||||
const nsamples = 10000
|
||||
|
||||
// Create a new source and sample values from it.
|
||||
s := newSecureSource()
|
||||
res0 := make([]int64, nsamples)
|
||||
for i := range res0 {
|
||||
res0[i] = s.Int63()
|
||||
}
|
||||
|
||||
// Do it again
|
||||
s = newSecureSource()
|
||||
res1 := make([]int64, nsamples)
|
||||
for i := range res1 {
|
||||
res1[i] = s.Int63()
|
||||
}
|
||||
|
||||
// There should (statistically speaking) be no repetition of the values,
|
||||
// neither within the samples from a source nor between sources.
|
||||
for _, v0 := range res0 {
|
||||
for _, v1 := range res1 {
|
||||
if v0 == v1 {
|
||||
t.Errorf("Suspicious coincidence, %d repeated between res0/res1", v0)
|
||||
}
|
||||
}
|
||||
}
|
||||
for i, v0 := range res0 {
|
||||
for _, v1 := range res0[i+1:] {
|
||||
if v0 == v1 {
|
||||
t.Errorf("Suspicious coincidence, %d repeated within res0", v0)
|
||||
}
|
||||
}
|
||||
}
|
||||
for i, v0 := range res1 {
|
||||
for _, v1 := range res1[i+1:] {
|
||||
if v0 == v1 {
|
||||
t.Errorf("Suspicious coincidence, %d repeated within res1", v0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Count how many times each bit was set. On average each bit ought to
|
||||
// be set in half of the samples, except the topmost bit which must
|
||||
// never be set (int63). We raise an alarm if a single bit is set in
|
||||
// fewer than 1/3 of the samples or more often than 2/3 of the samples.
|
||||
var bits [64]int
|
||||
for _, v := range res0 {
|
||||
for i := range bits {
|
||||
if v&1 == 1 {
|
||||
bits[i]++
|
||||
}
|
||||
v >>= 1
|
||||
}
|
||||
}
|
||||
for bit, count := range bits {
|
||||
switch bit {
|
||||
case 63:
|
||||
// The topmost bit is never set
|
||||
if count != 0 {
|
||||
t.Errorf("The topmost bit was set %d times in %d samples (should be 0)", count, nsamples)
|
||||
}
|
||||
default:
|
||||
if count < nsamples/3 {
|
||||
t.Errorf("Bit %d was set only %d times out of %d", bit, count, nsamples)
|
||||
}
|
||||
if count > nsamples/3*2 {
|
||||
t.Errorf("Bit %d was set fully %d times out of %d", bit, count, nsamples)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var sink int64
|
||||
|
||||
func BenchmarkSecureSource(b *testing.B) {
|
||||
s := newSecureSource()
|
||||
for i := 0; i < b.N; i++ {
|
||||
sink = s.Int63()
|
||||
}
|
||||
b.ReportAllocs()
|
||||
}
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"bufio"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
@@ -19,10 +18,11 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
mr "math/rand"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/rand"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -48,7 +48,7 @@ func NewCertificate(certFile, keyFile, tlsDefaultCommonName string, tlsRSABits i
|
||||
notAfter := time.Date(2049, 12, 31, 23, 59, 59, 0, time.UTC)
|
||||
|
||||
template := x509.Certificate{
|
||||
SerialNumber: new(big.Int).SetInt64(mr.Int63()),
|
||||
SerialNumber: new(big.Int).SetInt64(rand.Int63()),
|
||||
Subject: pkix.Name{
|
||||
CommonName: tlsDefaultCommonName,
|
||||
},
|
||||
|
||||
@@ -129,11 +129,31 @@ func SelectLatestRelease(version string, rels []Release) (Release, error) {
|
||||
return Release{}, ErrNoVersionToSelect
|
||||
}
|
||||
|
||||
sort.Sort(SortByRelease(rels))
|
||||
// Sort the releases, lowest version number first
|
||||
sort.Sort(sort.Reverse(SortByRelease(rels)))
|
||||
// Check for a beta build
|
||||
beta := strings.Contains(version, "-")
|
||||
|
||||
var selected Release
|
||||
for _, rel := range rels {
|
||||
switch CompareVersions(rel.Tag, version) {
|
||||
case Older, MajorOlder:
|
||||
// This is older than what we're already running
|
||||
continue
|
||||
|
||||
case MajorNewer:
|
||||
// We've found a new major version. That's fine, but if we've
|
||||
// already found a minor upgrade that is acceptable we should go
|
||||
// with that one first and then revisit in the future.
|
||||
if selected.Tag != "" && CompareVersions(selected.Tag, version) == Newer {
|
||||
return selected, nil
|
||||
}
|
||||
// else it may be viable, do the needful below
|
||||
|
||||
default:
|
||||
// New minor release, do the usual processing
|
||||
}
|
||||
|
||||
if rel.Prerelease && !beta {
|
||||
continue
|
||||
}
|
||||
@@ -144,11 +164,16 @@ func SelectLatestRelease(version string, rels []Release) (Release, error) {
|
||||
l.Debugf("expected release asset %q", expectedRelease)
|
||||
l.Debugln("considering release", assetName)
|
||||
if strings.HasPrefix(assetName, expectedRelease) {
|
||||
return rel, nil
|
||||
selected = rel
|
||||
}
|
||||
}
|
||||
}
|
||||
return Release{}, ErrNoReleaseDownload
|
||||
|
||||
if selected.Tag == "" {
|
||||
return Release{}, ErrNoReleaseDownload
|
||||
}
|
||||
|
||||
return selected, nil
|
||||
}
|
||||
|
||||
// Upgrade to the given release, saving the previous binary with a ".old" extension.
|
||||
@@ -180,11 +205,7 @@ func upgradeToURL(archiveName, binary string, url string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = os.Rename(fname, binary)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return os.Rename(fname, binary)
|
||||
}
|
||||
|
||||
func readRelease(archiveName, dir, url string) (string, error) {
|
||||
|
||||
@@ -11,6 +11,7 @@ package upgrade
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@@ -58,16 +59,15 @@ func TestCompareVersions(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
var upgrades = map[string]string{
|
||||
"v0.10.21": "v0.10.30",
|
||||
"v0.10.29": "v0.10.30",
|
||||
"v0.10.31": "v0.10.30",
|
||||
"v0.10.0-alpha": "v0.11.0-beta0",
|
||||
"v0.10.0-beta": "v0.11.0-beta0",
|
||||
"v0.11.0-beta0+40-g53cb66e-dirty": "v0.11.0-beta0",
|
||||
}
|
||||
|
||||
func TestGithubRelease(t *testing.T) {
|
||||
var upgrades = map[string]string{
|
||||
"v0.10.21": "v0.10.30",
|
||||
"v0.10.29": "v0.10.30",
|
||||
"v0.10.0-alpha": "v0.10.30",
|
||||
"v0.10.0-beta": "v0.10.30",
|
||||
"v0.11.0-beta0+40-g53cb66e-dirty": "v0.11.0-beta0",
|
||||
}
|
||||
|
||||
fd, err := os.Open("testdata/github-releases.json")
|
||||
if err != nil {
|
||||
t.Errorf("Missing github-release test data")
|
||||
@@ -94,3 +94,48 @@ func TestErrorRelease(t *testing.T) {
|
||||
t.Error("Should return an error when no release were available")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectedRelease(t *testing.T) {
|
||||
testcases := []struct {
|
||||
current string
|
||||
candidates []string
|
||||
selected string
|
||||
}{
|
||||
// Within the same "major" (minor, in this case) select the newest
|
||||
{"v0.12.24", []string{"v0.12.23", "v0.12.24", "v0.12.25", "v0.12.26"}, "v0.12.26"},
|
||||
// Do no select beta versions when we are not a beta
|
||||
{"v0.12.24", []string{"v0.12.26", "v0.12.27-beta.42"}, "v0.12.26"},
|
||||
// Do select beta versions when we are a beta
|
||||
{"v0.12.24-beta.0", []string{"v0.12.26", "v0.12.27-beta.42"}, "v0.12.27-beta.42"},
|
||||
// Select the best within the current major when there is a minor upgrade available
|
||||
{"v0.12.24", []string{"v0.12.23", "v0.12.24", "v0.12.25", "v0.13.0"}, "v0.12.25"},
|
||||
{"v1.12.24", []string{"v1.12.23", "v1.12.24", "v1.14.2", "v2.0.0"}, "v1.14.2"},
|
||||
// Select the next major when we are at the best minor
|
||||
{"v0.12.25", []string{"v0.12.23", "v0.12.24", "v0.12.25", "v0.13.0"}, "v0.13.0"},
|
||||
{"v1.14.2", []string{"v0.12.23", "v0.12.24", "v1.14.2", "v2.0.0"}, "v2.0.0"},
|
||||
}
|
||||
|
||||
for i, tc := range testcases {
|
||||
// Prepare a list of candidate releases
|
||||
var rels []Release
|
||||
for _, c := range tc.candidates {
|
||||
rels = append(rels, Release{
|
||||
Tag: c,
|
||||
Prerelease: strings.Contains(c, "-"),
|
||||
Assets: []Asset{
|
||||
// There must be a matching asset or it will not get selected
|
||||
{Name: releaseName(c)},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Check the selection
|
||||
sel, err := SelectLatestRelease(tc.current, rels)
|
||||
if err != nil {
|
||||
t.Fatal("Unexpected error:", err)
|
||||
}
|
||||
if sel.Tag != tc.selected {
|
||||
t.Errorf("Test case %d: expected %s to be selected, but got %s", i, tc.selected, sel.Tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,34 @@
|
||||
// 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/.
|
||||
|
||||
// Adapted from https://github.com/jackpal/Taipei-Torrent/blob/dd88a8bfac6431c01d959ce3c745e74b8a911793/IGD.go
|
||||
// Copyright (c) 2010 Jack Palevich (https://github.com/jackpal/Taipei-Torrent/blob/dd88a8bfac6431c01d959ce3c745e74b8a911793/LICENSE)
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
|
||||
package upnp
|
||||
|
||||
|
||||
@@ -1,11 +1,34 @@
|
||||
// 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/.
|
||||
|
||||
// Adapted from https://github.com/jackpal/Taipei-Torrent/blob/dd88a8bfac6431c01d959ce3c745e74b8a911793/IGD.go
|
||||
// Copyright (c) 2010 Jack Palevich (https://github.com/jackpal/Taipei-Torrent/blob/dd88a8bfac6431c01d959ce3c745e74b8a911793/LICENSE)
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
|
||||
package upnp
|
||||
|
||||
@@ -64,12 +87,7 @@ func (s *IGDService) DeletePortMapping(protocol nat.Protocol, externalPort int)
|
||||
body := fmt.Sprintf(tpl, s.URN, externalPort, protocol)
|
||||
|
||||
_, err := soapRequest(s.URL, s.URN, "DeletePortMapping", body)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return err
|
||||
}
|
||||
|
||||
// GetExternalIPAddress queries the IGD service for its external IP address.
|
||||
|
||||
@@ -1,11 +1,33 @@
|
||||
// Copyright (C) 2014 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/.
|
||||
|
||||
// Adapted from https://github.com/jackpal/Taipei-Torrent/blob/dd88a8bfac6431c01d959ce3c745e74b8a911793/IGD.go
|
||||
// Copyright (c) 2010 Jack Palevich (https://github.com/jackpal/Taipei-Torrent/blob/dd88a8bfac6431c01d959ce3c745e74b8a911793/LICENSE)
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Package upnp implements UPnP InternetGatewayDevice discovery, querying, and port mapping.
|
||||
package upnp
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
// Copyright (C) 2014 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 util
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
cryptoRand "crypto/rand"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
mathRand "math/rand"
|
||||
)
|
||||
|
||||
// randomCharset contains the characters that can make up a randomString().
|
||||
const randomCharset = "2345679abcdefghijkmnopqrstuvwxyzACDEFGHJKLMNPQRSTUVWXYZ"
|
||||
|
||||
func init() {
|
||||
// The default RNG should be seeded with something good.
|
||||
mathRand.Seed(RandomInt64())
|
||||
}
|
||||
|
||||
// RandomString returns a string of random characters (taken from
|
||||
// randomCharset) of the specified length.
|
||||
func RandomString(l int) string {
|
||||
bs := make([]byte, l)
|
||||
for i := range bs {
|
||||
bs[i] = randomCharset[mathRand.Intn(len(randomCharset))]
|
||||
}
|
||||
return string(bs)
|
||||
}
|
||||
|
||||
// RandomInt64 returns a strongly random int64, slowly
|
||||
func RandomInt64() int64 {
|
||||
var bs [8]byte
|
||||
_, err := io.ReadFull(cryptoRand.Reader, bs[:])
|
||||
if err != nil {
|
||||
panic("randomness failure: " + err.Error())
|
||||
}
|
||||
return SeedFromBytes(bs[:])
|
||||
}
|
||||
|
||||
// SeedFromBytes calculates a weak 64 bit hash from the given byte slice,
|
||||
// suitable for use a predictable random seed.
|
||||
func SeedFromBytes(bs []byte) int64 {
|
||||
h := md5.New()
|
||||
h.Write(bs)
|
||||
s := h.Sum(nil)
|
||||
// The MD5 hash of the byte slice is 16 bytes long. We interpret it as two
|
||||
// uint64s and XOR them together.
|
||||
return int64(binary.BigEndian.Uint64(s[0:]) ^ binary.BigEndian.Uint64(s[8:]))
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-BEP" "7" "May 17, 2016" "v0.12" "Syncthing"
|
||||
.TH "SYNCTHING-BEP" "7" "May 21, 2016" "v0.12" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-bep \- Block Exchange Protocol v1
|
||||
.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-CONFIG" "5" "May 17, 2016" "v0.12" "Syncthing"
|
||||
.TH "SYNCTHING-CONFIG" "5" "May 21, 2016" "v0.12" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-config \- Syncthing Configuration
|
||||
.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-DEVICE-IDS" "7" "May 17, 2016" "v0.12" "Syncthing"
|
||||
.TH "SYNCTHING-DEVICE-IDS" "7" "May 21, 2016" "v0.12" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-device-ids \- Understanding Device IDs
|
||||
.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-EVENT-API" "7" "May 17, 2016" "v0.12" "Syncthing"
|
||||
.TH "SYNCTHING-EVENT-API" "7" "May 21, 2016" "v0.12" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-event-api \- Event API
|
||||
.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-FAQ" "7" "May 17, 2016" "v0.12" "Syncthing"
|
||||
.TH "SYNCTHING-FAQ" "7" "May 21, 2016" "v0.12" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-faq \- Frequently Asked Questions
|
||||
.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-GLOBALDISCO" "7" "May 17, 2016" "v0.12" "Syncthing"
|
||||
.TH "SYNCTHING-GLOBALDISCO" "7" "May 21, 2016" "v0.12" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-globaldisco \- Global Discovery Protocol v3
|
||||
.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
.\" Man page generated from reStructuredText.
|
||||
.
|
||||
.TH "SYNCTHING-LOCALDISCO" "7" "May 17, 2016" "v0.12" "Syncthing"
|
||||
.TH "SYNCTHING-LOCALDISCO" "7" "May 21, 2016" "v0.12" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-localdisco \- Local Discovery Protocol v3
|
||||
.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user