Compare commits

...

54 Commits

Author SHA1 Message Date
Jakob Borg
ee000dabfd Translation update 2015-02-09 23:03:31 +01:00
Jakob Borg
a73a011ee0 Merge pull request #1323 from AudriusButkevicius/finished
Add ItemFinished event (fixes #1258)
2015-02-09 15:24:10 +01:00
Jakob Borg
2a8e5e2c14 Merge pull request #1304 from AudriusButkevicius/pprof
Add STBLOCKPROFILE
2015-02-09 15:18:42 +01:00
Jakob Borg
5d9a41f712 Merge pull request #1319 from AudriusButkevicius/renames
Fix issues with renames
2015-02-09 15:14:47 +01:00
Jakob Borg
ebcf4b60f6 Merge pull request #1320 from AudriusButkevicius/cache
Remove fd cache (ref #1308)
2015-02-09 15:10:42 +01:00
Jakob Borg
f976b78917 Merge pull request #1322 from AudriusButkevicius/browser
Opening a browser happens in it's own routine (fixes #1273)
2015-02-09 15:06:56 +01:00
Audrius Butkevicius
b88c5a89a8 Add rumpelsepp 2015-02-03 00:15:57 +00:00
Audrius Butkevicius
57028e3acc Merge pull request #1325 from rumpelsepp/master
Added exit code definitions to systemd service files (fixes #1324)
2015-02-02 08:04:27 +00:00
Stefan Tatschner
c586a17926 Added exit code definitions to systemd service files (fixes #1324) 2015-02-01 23:55:54 +01:00
Audrius Butkevicius
9d078bac54 Add STBLOCKPROFILE 2015-02-01 19:00:24 +00:00
Audrius Butkevicius
38eaefcabd Add ItemFinished event (fixes #1258) 2015-02-01 18:59:29 +00:00
Audrius Butkevicius
ba8cadc2f1 Opening a browser happens in it's own routine (fixes #1273) 2015-02-01 18:59:28 +00:00
Audrius Butkevicius
380d5dfa6d Remove fd cache (ref #1308) 2015-02-01 18:59:24 +00:00
Audrius Butkevicius
32af626630 Fix issues with renames (fixes #1302)
Extra comments explain current issues.
2015-02-01 18:58:27 +00:00
Audrius Butkevicius
8358fedaf4 Fix failing integration tests 2015-02-01 18:57:46 +00:00
Audrius Butkevicius
ec82b0c648 Merge pull request #1309 from uok/fix-override
Fix button "override changes" line break (fixes #1144)
2015-01-29 10:40:14 +00:00
Ben Schulz
81a87f873f Fix button "override changes" line break 2015-01-29 11:28:59 +01:00
Audrius Butkevicius
d91b8ac444 Merge pull request #1299 from krozycki/master
Show information in folder panel if ignore patterns are active fixes #1279
2015-01-27 19:54:09 +00:00
Karol Różycki
952e51ac75 Show information in folder panel if ignore patterns are active, fixes #1279 2015-01-27 15:27:44 +01:00
Audrius Butkevicius
11267cd44f Merge pull request #1298 from uok/link-usage
Make links to usage report clickable
2015-01-26 20:59:20 +00:00
Ben Schulz
0e59e0aebd Make links to usage report clickable 2015-01-26 17:20:55 +01:00
Audrius Butkevicius
ae1d3b3dd3 Merge pull request #1293 from uok/move-panels
Move panels to top of page (ref #1270)
2015-01-25 09:53:03 +00:00
Ben Schulz
1a91dbee5f Move panels to top of page
- Move panels (new device, new folder, notice) to top of page
- Add icons to panel headers (restart, new folder, notice)
2015-01-23 16:28:30 +01:00
Jakob Borg
fd507e3e41 Merge pull request #1290 from krozycki/master
Ensuring path separator at the end of the folder path. (fixes #1262)
2015-01-22 15:36:32 -08:00
Jakob Borg
9c1a67cf47 Add krozycki 2015-01-22 15:35:58 -08:00
Jakob Borg
69e3824840 Add test for #1262 2015-01-22 15:34:22 -08:00
Karol Różycki
fcb1a98129 Ensuring path separator at the end of the folder path. (fixes #1262) 2015-01-23 00:22:30 +01:00
Audrius Butkevicius
6d942635af Merge pull request #1269 from uok/master
Small improvements for job queue
2015-01-22 22:58:33 +00:00
Audrius Butkevicius
cda2c5d459 Fix integration tests 2015-01-22 22:42:39 +00:00
Jakob Borg
969bb5a742 Fix protocol dependency hash 2015-01-22 13:29:54 -08:00
Jakob Borg
4bccc611c3 Update dependencies 2015-01-22 12:53:10 -08:00
Jakob Borg
d18c4ece0c Merge pull request #1282 from syncthing/integ
Improvements to integration tests
2015-01-22 08:22:46 -08:00
Audrius Butkevicius
25c664b13a Improvements to integration tests 2015-01-22 00:18:08 +00:00
Audrius Butkevicius
7c680c955f Merge pull request #1274 from calmh/minor-tweaks
Integer type policy
2015-01-19 20:40:36 +00:00
Ben Schulz
f037d1b6ca improve job queue 2015-01-19 20:49:19 +01:00
Jakob Borg
2c8b627008 Integer type policy
Integers are for numbers, enabling arithmetic like subtractions and for
loops without getting shot in the foot. Unsigneds are for bitfields.

- "int" for numbers that will always be laughably smaller than four
  billion, and where we don't care about the serialization format.

- "int32" for numbers that will always be laughably smaller than four
  billion, and will be serialized to four bytes.

- "int64" for numbers that may approach four billion or will be
  serialized to eight bytes.

- "uint32" and "uint64" for bitfields, depending on required number of
  bits and serialization format. Likewise "uint8" and "uint16", although
  rare in this project since they don't exist in XDR.

- "int8", "int16" and plain "uint" are almost never useful.
2015-01-19 10:34:36 -08:00
Jakob Borg
221e3eddd5 Remove leveldb panic workaround
Haven't seen this triggered for a long time...
2015-01-19 10:23:00 -08:00
Jakob Borg
74c39c677b Actually remove test file after test run 2015-01-19 10:23:00 -08:00
Jakob Borg
e6558832bf check-contrib 2015-01-19 10:18:28 -08:00
Jakob Borg
9c50625c55 Merge pull request #1267 from rumpelsepp/doc
Fix documentation link
2015-01-19 10:16:43 -08:00
Jakob Borg
d372435e92 Merge pull request #1264 from AudriusButkevicius/tempclean
Put temporary files in the OS temp directory (fixes #1239)
2015-01-19 10:12:32 -08:00
Audrius Butkevicius
a53facf709 Cleanup temporary files (fixes #1239) 2015-01-18 20:34:47 +00:00
Stefan Tatschner
ffc39dfbcb Fix documentation link 2015-01-18 18:17:55 +01:00
Audrius Butkevicius
cba38b15a9 Check for deleted files 2015-01-18 13:44:10 +00:00
Audrius Butkevicius
5ac7564bfe Merge pull request #1259 from calmh/check-folder-shared
Verify folder<->device permission in Request
2015-01-16 12:07:37 +00:00
Jakob Borg
53cd289b90 Verify folder<->device permission in Request
Requests from valid devices for valid folders should be rejected if the
folder is not shared with that device.
2015-01-16 12:50:51 +01:00
Jakob Borg
8dc13bcf1a go1.4.1 2015-01-16 10:18:54 +01:00
Audrius Butkevicius
261825a89b Merge pull request #1256 from calmh/fix-963
Adds File Versioning to folder info (fixes #963)
2015-01-15 17:25:28 +00:00
Jakob Borg
f47a5a309d Add File Versioning to folder info (fixes #963)
Could potentially use shorter value strings ("Simple" vs "Simple File
Versioning"), but this avoid introducing unnecessary strings to
translate - can always be changed in the future.
2015-01-15 15:50:49 +01:00
Jakob Borg
a40f2b9fa0 Merge pull request #1237 from syncthing/renamer
Efficient renames (fixes #1217)
2015-01-14 00:28:58 +01:00
Jakob Borg
cfcd3892f7 More concise changelog 2015-01-14 00:27:29 +01:00
Jakob Borg
703987f61c Changelog should read in chronological order from the top 2015-01-13 23:11:46 +01:00
Audrius Butkevicius
e50a8917ec Add renames to integration tests 2015-01-13 22:07:14 +00:00
Audrius Butkevicius
74d7c8e625 Efficient renames (fixes #1217) 2015-01-13 22:06:13 +00:00
73 changed files with 1430 additions and 1307 deletions

View File

@@ -24,6 +24,7 @@ Jakob Borg <jakob@nym.se>
James Patterson <jamespatterson@operamail.com> <jpjp@users.noreply.github.com>
Jens Diemer <github.com@jensdiemer.de> <git@jensdiemer.de>
Jochen Voss <voss@seehuhn.de>
Karol Różycki <rozycki.karol@gmail.com>
Lode Hoste <zillode@zillode.be>
Marcin Dziadus <dziadus.marcin@gmail.com>
Michael Jephcote <rewt0r@gmx.com> <Rewt0r@users.noreply.github.com>
@@ -33,6 +34,7 @@ Philippe Schommers <philippe@schommers.be>
Phill Luby <phill.luby@newredo.com>
Piotr Bejda <piotrb10@gmail.com>
Ryan Sullivan <kayoticsully@gmail.com>
Stefan Tatschner <stefan@sevenbyte.org>
Tim Abell <tim@timwise.co.uk>
Tomas Cerveny <kozec@kozec.com>
Tully Robinson <tully@tojr.org>

View File

@@ -33,8 +33,8 @@ latest info on Transifex.
Every contribution is welcome. If you want to contribute but are unsure
where to start, any open issues are fair game! Be prepared for a
[certain amount of review](https://discourse.syncthing.net/t/733); it's
all in the name of quality. :) Following the points below will make this
[certain amount of review](https://github.com/syncthing/syncthing/wiki/FAQ#why-are-you-being-so-hard-on-my-pull-request);
it's all in the name of quality. :) Following the points below will make this
a smoother process.
Individuals making significant and valuable contributions are given
@@ -105,8 +105,8 @@ add yourself as a separate commit in your first pull request.
## Building
[See the documentation](http://discourse.syncthing.net/t/44) on how to
get started with a build environment.
[See the documentation](https://github.com/syncthing/syncthing/wiki/Building)
on how to get started with a build environment.
## Branches
@@ -133,7 +133,7 @@ Yes please!
## Documentation
[Over here!](http://discourse.syncthing.net/category/documentation)
[Over here!](https://github.com/syncthing/syncthing/wiki)
## License

16
Godeps/Godeps.json generated
View File

@@ -5,10 +5,6 @@
"./cmd/..."
],
"Deps": [
{
"ImportPath": "github.com/AudriusButkevicius/lfu-go",
"Rev": "164bcecceb92fd6037f4d18a8d97b495ec6ef669"
},
{
"ImportPath": "github.com/bkaradzic/go-lz4",
"Rev": "93a831dcee242be64a9cc9803dda84af25932de7"
@@ -27,7 +23,7 @@
},
{
"ImportPath": "github.com/calmh/xdr",
"Rev": "214788d8fedfc310c18eca9ed12be408a5054cd5"
"Rev": "ff948d7666c5e0fd18d398f6278881724d36a90b"
},
{
"ImportPath": "github.com/juju/ratelimit",
@@ -35,7 +31,7 @@
},
{
"ImportPath": "github.com/syncthing/protocol",
"Rev": "f76b5d800208131015b14cfc1cfd3e6dd1d5e979"
"Rev": "442e93d3fc7728d7560c8d039a4199a77879b9f6"
},
{
"ImportPath": "github.com/syndtr/goleveldb/leveldb",
@@ -59,19 +55,19 @@
},
{
"ImportPath": "golang.org/x/crypto/bcrypt",
"Rev": "731db29863ea7213d9556d0170afb38987f401d4"
"Rev": "4ed45ec682102c643324fae5dff8dab085b6c300"
},
{
"ImportPath": "golang.org/x/crypto/blowfish",
"Rev": "731db29863ea7213d9556d0170afb38987f401d4"
"Rev": "4ed45ec682102c643324fae5dff8dab085b6c300"
},
{
"ImportPath": "golang.org/x/text/transform",
"Rev": "985ee5acfaf1ff6712c7c99438752f8e09416ccb"
"Rev": "c980adc4a823548817b9c47d38c6ca6b7d7d8b6a"
},
{
"ImportPath": "golang.org/x/text/unicode/norm",
"Rev": "985ee5acfaf1ff6712c7c99438752f8e09416ccb"
"Rev": "c980adc4a823548817b9c47d38c6ca6b7d7d8b6a"
}
]
}

View File

@@ -1,19 +0,0 @@
Copyright (C) 2012 Dave Grijalva
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.

View File

@@ -1,19 +0,0 @@
A simple LFU cache for golang. Based on the paper [An O(1) algorithm for implementing the LFU cache eviction scheme](http://dhruvbird.com/lfu.pdf).
Usage:
```go
import "github.com/dgrijalva/lfu-go"
// Make a new thing
c := lfu.New()
// Set some values
c.Set("myKey", myValue)
// Retrieve some values
myValue = c.Get("myKey")
// Evict some values
c.Evict(1)
```

View File

@@ -1,156 +0,0 @@
package lfu
import (
"container/list"
"sync"
)
type Eviction struct {
Key string
Value interface{}
}
type Cache struct {
// If len > UpperBound, cache will automatically evict
// down to LowerBound. If either value is 0, this behavior
// is disabled.
UpperBound int
LowerBound int
values map[string]*cacheEntry
freqs *list.List
len int
lock *sync.Mutex
EvictionChannel chan<- Eviction
}
type cacheEntry struct {
key string
value interface{}
freqNode *list.Element
}
type listEntry struct {
entries map[*cacheEntry]byte
freq int
}
func New() *Cache {
c := new(Cache)
c.values = make(map[string]*cacheEntry)
c.freqs = list.New()
c.lock = new(sync.Mutex)
return c
}
func (c *Cache) Get(key string) interface{} {
c.lock.Lock()
defer c.lock.Unlock()
if e, ok := c.values[key]; ok {
c.increment(e)
return e.value
}
return nil
}
func (c *Cache) Set(key string, value interface{}) {
c.lock.Lock()
defer c.lock.Unlock()
if e, ok := c.values[key]; ok {
// value already exists for key. overwrite
e.value = value
c.increment(e)
} else {
// value doesn't exist. insert
e := new(cacheEntry)
e.key = key
e.value = value
c.values[key] = e
c.increment(e)
c.len++
// bounds mgmt
if c.UpperBound > 0 && c.LowerBound > 0 {
if c.len > c.UpperBound {
c.evict(c.len - c.LowerBound)
}
}
}
}
func (c *Cache) Len() int {
c.lock.Lock()
defer c.lock.Unlock()
return c.len
}
func (c *Cache) Evict(count int) int {
c.lock.Lock()
defer c.lock.Unlock()
return c.evict(count)
}
func (c *Cache) evict(count int) int {
// No lock here so it can be called
// from within the lock (during Set)
var evicted int
for i := 0; i < count; {
if place := c.freqs.Front(); place != nil {
for entry, _ := range place.Value.(*listEntry).entries {
if i < count {
if c.EvictionChannel != nil {
c.EvictionChannel <- Eviction{
Key: entry.key,
Value: entry.value,
}
}
delete(c.values, entry.key)
c.remEntry(place, entry)
evicted++
c.len--
i++
}
}
}
}
return evicted
}
func (c *Cache) increment(e *cacheEntry) {
currentPlace := e.freqNode
var nextFreq int
var nextPlace *list.Element
if currentPlace == nil {
// new entry
nextFreq = 1
nextPlace = c.freqs.Front()
} else {
// move up
nextFreq = currentPlace.Value.(*listEntry).freq + 1
nextPlace = currentPlace.Next()
}
if nextPlace == nil || nextPlace.Value.(*listEntry).freq != nextFreq {
// create a new list entry
li := new(listEntry)
li.freq = nextFreq
li.entries = make(map[*cacheEntry]byte)
if currentPlace != nil {
nextPlace = c.freqs.InsertAfter(li, currentPlace)
} else {
nextPlace = c.freqs.PushFront(li)
}
}
e.freqNode = nextPlace
nextPlace.Value.(*listEntry).entries[e] = 1
if currentPlace != nil {
// remove from current position
c.remEntry(currentPlace, e)
}
}
func (c *Cache) remEntry(place *list.Element, entry *cacheEntry) {
entries := place.Value.(*listEntry).entries
delete(entries, entry)
if len(entries) == 0 {
c.freqs.Remove(place)
}
}

View File

@@ -1,68 +0,0 @@
package lfu
import (
"fmt"
"testing"
)
func TestLFU(t *testing.T) {
c := New()
c.Set("a", "a")
if v := c.Get("a"); v != "a" {
t.Errorf("Value was not saved: %v != 'a'", v)
}
if l := c.Len(); l != 1 {
t.Errorf("Length was not updated: %v != 1", l)
}
c.Set("b", "b")
if v := c.Get("b"); v != "b" {
t.Errorf("Value was not saved: %v != 'b'", v)
}
if l := c.Len(); l != 2 {
t.Errorf("Length was not updated: %v != 2", l)
}
c.Get("a")
evicted := c.Evict(1)
if v := c.Get("a"); v != "a" {
t.Errorf("Value was improperly evicted: %v != 'a'", v)
}
if v := c.Get("b"); v != nil {
t.Errorf("Value was not evicted: %v", v)
}
if l := c.Len(); l != 1 {
t.Errorf("Length was not updated: %v != 1", l)
}
if evicted != 1 {
t.Errorf("Number of evicted items is wrong: %v != 1", evicted)
}
}
func TestBoundsMgmt(t *testing.T) {
c := New()
c.UpperBound = 10
c.LowerBound = 5
for i := 0; i < 100; i++ {
c.Set(fmt.Sprintf("%v", i), i)
}
if c.Len() > 10 {
t.Errorf("Bounds management failed to evict properly: %v", c.Len())
}
}
func TestEviction(t *testing.T) {
ch := make(chan Eviction, 1)
c := New()
c.EvictionChannel = ch
c.Set("a", "b")
c.Evict(1)
ev := <-ch
if ev.Key != "a" || ev.Value.(string) != "b" {
t.Error("Incorrect item")
}
}

View File

@@ -1,194 +1,194 @@
/*
* Copyright 2011-2012 Branimir Karadzic. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER ``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 COPYRIGHT HOLDER 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 lz4
import (
"encoding/binary"
"errors"
"io"
)
var (
// ErrCorrupt indicates the input was corrupt
ErrCorrupt = errors.New("corrupt input")
)
const (
mlBits = 4
mlMask = (1 << mlBits) - 1
runBits = 8 - mlBits
runMask = (1 << runBits) - 1
)
type decoder struct {
src []byte
dst []byte
spos uint32
dpos uint32
ref uint32
}
func (d *decoder) readByte() (uint8, error) {
if int(d.spos) == len(d.src) {
return 0, io.EOF
}
b := d.src[d.spos]
d.spos++
return b, nil
}
func (d *decoder) getLen() (uint32, error) {
length := uint32(0)
ln, err := d.readByte()
if err != nil {
return 0, ErrCorrupt
}
for ln == 255 {
length += 255
ln, err = d.readByte()
if err != nil {
return 0, ErrCorrupt
}
}
length += uint32(ln)
return length, nil
}
func (d *decoder) cp(length, decr uint32) {
if int(d.ref+length) < int(d.dpos) {
copy(d.dst[d.dpos:], d.dst[d.ref:d.ref+length])
} else {
for ii := uint32(0); ii < length; ii++ {
d.dst[d.dpos+ii] = d.dst[d.ref+ii]
}
}
d.dpos += length
d.ref += length - decr
}
func (d *decoder) finish(err error) error {
if err == io.EOF {
return nil
}
return err
}
// Decode returns the decoded form of src. The returned slice may be a
// subslice of dst if it was large enough to hold the entire decoded block.
func Decode(dst, src []byte) ([]byte, error) {
if len(src) < 4 {
return nil, ErrCorrupt
}
uncompressedLen := binary.LittleEndian.Uint32(src)
if uncompressedLen == 0 {
return nil, nil
}
if uncompressedLen > MaxInputSize {
return nil, ErrTooLarge
}
if dst == nil || len(dst) < int(uncompressedLen) {
dst = make([]byte, uncompressedLen)
}
d := decoder{src: src, dst: dst[:uncompressedLen], spos: 4}
decr := []uint32{0, 3, 2, 3}
for {
code, err := d.readByte()
if err != nil {
return d.dst, d.finish(err)
}
length := uint32(code >> mlBits)
if length == runMask {
ln, err := d.getLen()
if err != nil {
return nil, ErrCorrupt
}
length += ln
}
if int(d.spos+length) > len(d.src) {
return nil, ErrCorrupt
}
for ii := uint32(0); ii < length; ii++ {
d.dst[d.dpos+ii] = d.src[d.spos+ii]
}
d.spos += length
d.dpos += length
if int(d.spos) == len(d.src) {
return d.dst, nil
}
if int(d.spos+2) >= len(d.src) {
return nil, ErrCorrupt
}
back := uint32(d.src[d.spos]) | uint32(d.src[d.spos+1])<<8
if back > d.dpos {
return nil, ErrCorrupt
}
d.spos += 2
d.ref = d.dpos - back
length = uint32(code & mlMask)
if length == mlMask {
ln, err := d.getLen()
if err != nil {
return nil, ErrCorrupt
}
length += ln
}
literal := d.dpos - d.ref
if literal < 4 {
d.cp(4, decr[literal])
} else {
length += 4
}
if d.dpos+length > uncompressedLen {
return nil, ErrCorrupt
}
d.cp(length, 0)
}
}
/*
* Copyright 2011-2012 Branimir Karadzic. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER ``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 COPYRIGHT HOLDER 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 lz4
import (
"encoding/binary"
"errors"
"io"
)
var (
// ErrCorrupt indicates the input was corrupt
ErrCorrupt = errors.New("corrupt input")
)
const (
mlBits = 4
mlMask = (1 << mlBits) - 1
runBits = 8 - mlBits
runMask = (1 << runBits) - 1
)
type decoder struct {
src []byte
dst []byte
spos uint32
dpos uint32
ref uint32
}
func (d *decoder) readByte() (uint8, error) {
if int(d.spos) == len(d.src) {
return 0, io.EOF
}
b := d.src[d.spos]
d.spos++
return b, nil
}
func (d *decoder) getLen() (uint32, error) {
length := uint32(0)
ln, err := d.readByte()
if err != nil {
return 0, ErrCorrupt
}
for ln == 255 {
length += 255
ln, err = d.readByte()
if err != nil {
return 0, ErrCorrupt
}
}
length += uint32(ln)
return length, nil
}
func (d *decoder) cp(length, decr uint32) {
if int(d.ref+length) < int(d.dpos) {
copy(d.dst[d.dpos:], d.dst[d.ref:d.ref+length])
} else {
for ii := uint32(0); ii < length; ii++ {
d.dst[d.dpos+ii] = d.dst[d.ref+ii]
}
}
d.dpos += length
d.ref += length - decr
}
func (d *decoder) finish(err error) error {
if err == io.EOF {
return nil
}
return err
}
// Decode returns the decoded form of src. The returned slice may be a
// subslice of dst if it was large enough to hold the entire decoded block.
func Decode(dst, src []byte) ([]byte, error) {
if len(src) < 4 {
return nil, ErrCorrupt
}
uncompressedLen := binary.LittleEndian.Uint32(src)
if uncompressedLen == 0 {
return nil, nil
}
if uncompressedLen > MaxInputSize {
return nil, ErrTooLarge
}
if dst == nil || len(dst) < int(uncompressedLen) {
dst = make([]byte, uncompressedLen)
}
d := decoder{src: src, dst: dst[:uncompressedLen], spos: 4}
decr := []uint32{0, 3, 2, 3}
for {
code, err := d.readByte()
if err != nil {
return d.dst, d.finish(err)
}
length := uint32(code >> mlBits)
if length == runMask {
ln, err := d.getLen()
if err != nil {
return nil, ErrCorrupt
}
length += ln
}
if int(d.spos+length) > len(d.src) {
return nil, ErrCorrupt
}
for ii := uint32(0); ii < length; ii++ {
d.dst[d.dpos+ii] = d.src[d.spos+ii]
}
d.spos += length
d.dpos += length
if int(d.spos) == len(d.src) {
return d.dst, nil
}
if int(d.spos+2) >= len(d.src) {
return nil, ErrCorrupt
}
back := uint32(d.src[d.spos]) | uint32(d.src[d.spos+1])<<8
if back > d.dpos {
return nil, ErrCorrupt
}
d.spos += 2
d.ref = d.dpos - back
length = uint32(code & mlMask)
if length == mlMask {
ln, err := d.getLen()
if err != nil {
return nil, ErrCorrupt
}
length += ln
}
literal := d.dpos - d.ref
if literal < 4 {
d.cp(4, decr[literal])
} else {
length += 4
}
if d.dpos+length > uncompressedLen {
return nil, ErrCorrupt
}
d.cp(length, 0)
}
}

View File

@@ -1,188 +1,188 @@
/*
* Copyright 2011-2012 Branimir Karadzic. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER ``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 COPYRIGHT HOLDER 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 lz4
import "encoding/binary"
import "errors"
const (
minMatch = 4
hashLog = 17
hashTableSize = 1 << hashLog
hashShift = (minMatch * 8) - hashLog
incompressible uint32 = 128
uninitHash = 0x88888888
// MaxInputSize is the largest buffer than can be compressed in a single block
MaxInputSize = 0x7E000000
)
var (
// ErrTooLarge indicates the input buffer was too large
ErrTooLarge = errors.New("input too large")
)
type encoder struct {
src []byte
dst []byte
hashTable []uint32
pos uint32
anchor uint32
dpos uint32
}
// CompressBound returns the maximum length of a lz4 block, given it's uncompressed length
func CompressBound(isize int) int {
if isize > MaxInputSize {
return 0
}
return isize + ((isize) / 255) + 16 + 4
}
func (e *encoder) writeLiterals(length, mlLen, pos uint32) {
ln := length
var code byte
if ln > runMask-1 {
code = runMask
} else {
code = byte(ln)
}
if mlLen > mlMask-1 {
e.dst[e.dpos] = (code << mlBits) + byte(mlMask)
} else {
e.dst[e.dpos] = (code << mlBits) + byte(mlLen)
}
e.dpos++
if code == runMask {
ln -= runMask
for ; ln > 254; ln -= 255 {
e.dst[e.dpos] = 255
e.dpos++
}
e.dst[e.dpos] = byte(ln)
e.dpos++
}
for ii := uint32(0); ii < length; ii++ {
e.dst[e.dpos+ii] = e.src[pos+ii]
}
e.dpos += length
}
// Encode returns the encoded form of src. The returned array may be a
// sub-slice of dst if it was large enough to hold the entire output.
func Encode(dst, src []byte) ([]byte, error) {
if len(src) >= MaxInputSize {
return nil, ErrTooLarge
}
if n := CompressBound(len(src)); len(dst) < n {
dst = make([]byte, n)
}
e := encoder{src: src, dst: dst, hashTable: make([]uint32, hashTableSize)}
binary.LittleEndian.PutUint32(dst, uint32(len(src)))
e.dpos = 4
var (
step uint32 = 1
limit = incompressible
)
for {
if int(e.pos)+12 >= len(e.src) {
e.writeLiterals(uint32(len(e.src))-e.anchor, 0, e.anchor)
return e.dst[:e.dpos], nil
}
sequence := uint32(e.src[e.pos+3])<<24 | uint32(e.src[e.pos+2])<<16 | uint32(e.src[e.pos+1])<<8 | uint32(e.src[e.pos+0])
hash := (sequence * 2654435761) >> hashShift
ref := e.hashTable[hash] + uninitHash
e.hashTable[hash] = e.pos - uninitHash
if ((e.pos-ref)>>16) != 0 || uint32(e.src[ref+3])<<24|uint32(e.src[ref+2])<<16|uint32(e.src[ref+1])<<8|uint32(e.src[ref+0]) != sequence {
if e.pos-e.anchor > limit {
limit <<= 1
step += 1 + (step >> 2)
}
e.pos += step
continue
}
if step > 1 {
e.hashTable[hash] = ref - uninitHash
e.pos -= step - 1
step = 1
continue
}
limit = incompressible
ln := e.pos - e.anchor
back := e.pos - ref
anchor := e.anchor
e.pos += minMatch
ref += minMatch
e.anchor = e.pos
for int(e.pos) < len(e.src)-5 && e.src[e.pos] == e.src[ref] {
e.pos++
ref++
}
mlLen := e.pos - e.anchor
e.writeLiterals(ln, mlLen, anchor)
e.dst[e.dpos] = uint8(back)
e.dst[e.dpos+1] = uint8(back >> 8)
e.dpos += 2
if mlLen > mlMask-1 {
mlLen -= mlMask
for mlLen > 254 {
mlLen -= 255
e.dst[e.dpos] = 255
e.dpos++
}
e.dst[e.dpos] = byte(mlLen)
e.dpos++
}
e.anchor = e.pos
}
}
/*
* Copyright 2011-2012 Branimir Karadzic. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. 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.
*
* THIS SOFTWARE IS PROVIDED BY COPYRIGHT HOLDER ``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 COPYRIGHT HOLDER 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 lz4
import "encoding/binary"
import "errors"
const (
minMatch = 4
hashLog = 17
hashTableSize = 1 << hashLog
hashShift = (minMatch * 8) - hashLog
incompressible uint32 = 128
uninitHash = 0x88888888
// MaxInputSize is the largest buffer than can be compressed in a single block
MaxInputSize = 0x7E000000
)
var (
// ErrTooLarge indicates the input buffer was too large
ErrTooLarge = errors.New("input too large")
)
type encoder struct {
src []byte
dst []byte
hashTable []uint32
pos uint32
anchor uint32
dpos uint32
}
// CompressBound returns the maximum length of a lz4 block, given it's uncompressed length
func CompressBound(isize int) int {
if isize > MaxInputSize {
return 0
}
return isize + ((isize) / 255) + 16 + 4
}
func (e *encoder) writeLiterals(length, mlLen, pos uint32) {
ln := length
var code byte
if ln > runMask-1 {
code = runMask
} else {
code = byte(ln)
}
if mlLen > mlMask-1 {
e.dst[e.dpos] = (code << mlBits) + byte(mlMask)
} else {
e.dst[e.dpos] = (code << mlBits) + byte(mlLen)
}
e.dpos++
if code == runMask {
ln -= runMask
for ; ln > 254; ln -= 255 {
e.dst[e.dpos] = 255
e.dpos++
}
e.dst[e.dpos] = byte(ln)
e.dpos++
}
for ii := uint32(0); ii < length; ii++ {
e.dst[e.dpos+ii] = e.src[pos+ii]
}
e.dpos += length
}
// Encode returns the encoded form of src. The returned array may be a
// sub-slice of dst if it was large enough to hold the entire output.
func Encode(dst, src []byte) ([]byte, error) {
if len(src) >= MaxInputSize {
return nil, ErrTooLarge
}
if n := CompressBound(len(src)); len(dst) < n {
dst = make([]byte, n)
}
e := encoder{src: src, dst: dst, hashTable: make([]uint32, hashTableSize)}
binary.LittleEndian.PutUint32(dst, uint32(len(src)))
e.dpos = 4
var (
step uint32 = 1
limit = incompressible
)
for {
if int(e.pos)+12 >= len(e.src) {
e.writeLiterals(uint32(len(e.src))-e.anchor, 0, e.anchor)
return e.dst[:e.dpos], nil
}
sequence := uint32(e.src[e.pos+3])<<24 | uint32(e.src[e.pos+2])<<16 | uint32(e.src[e.pos+1])<<8 | uint32(e.src[e.pos+0])
hash := (sequence * 2654435761) >> hashShift
ref := e.hashTable[hash] + uninitHash
e.hashTable[hash] = e.pos - uninitHash
if ((e.pos-ref)>>16) != 0 || uint32(e.src[ref+3])<<24|uint32(e.src[ref+2])<<16|uint32(e.src[ref+1])<<8|uint32(e.src[ref+0]) != sequence {
if e.pos-e.anchor > limit {
limit <<= 1
step += 1 + (step >> 2)
}
e.pos += step
continue
}
if step > 1 {
e.hashTable[hash] = ref - uninitHash
e.pos -= step - 1
step = 1
continue
}
limit = incompressible
ln := e.pos - e.anchor
back := e.pos - ref
anchor := e.anchor
e.pos += minMatch
ref += minMatch
e.anchor = e.pos
for int(e.pos) < len(e.src)-5 && e.src[e.pos] == e.src[ref] {
e.pos++
ref++
}
mlLen := e.pos - e.anchor
e.writeLiterals(ln, mlLen, anchor)
e.dst[e.dpos] = uint8(back)
e.dst[e.dpos+1] = uint8(back >> 8)
e.dpos += 2
if mlLen > mlMask-1 {
mlLen -= mlMask
for mlLen > 254 {
mlLen -= 255
e.dst[e.dpos] = 255
e.dpos++
}
e.dst[e.dpos] = byte(mlLen)
e.dpos++
}
e.anchor = e.pos
}
}

View File

@@ -321,10 +321,10 @@ func generateDiagram(output io.Writer, s structInfo) {
case "bool":
fmt.Fprintf(output, "| %s |V|\n", center(name+" (V=0 or 1)", 59))
fmt.Fprintln(output, line)
case "uint16":
case "int16", "uint16":
fmt.Fprintf(output, "| %s | %s |\n", center("0x0000", 29), center(name, 29))
fmt.Fprintln(output, line)
case "uint32":
case "int32", "uint32":
fmt.Fprintf(output, "| %s |\n", center(name, 61))
fmt.Fprintln(output, line)
case "int64", "uint64":
@@ -374,6 +374,8 @@ func generateXdr(output io.Writer, s structInfo) {
}
switch tn {
case "int16", "int32":
fmt.Fprintf(output, "\tint %s%s;\n", fn, suf)
case "uint16", "uint32":
fmt.Fprintf(output, "\tunsigned int %s%s;\n", fn, suf)
case "int64":

View File

@@ -10,25 +10,25 @@ import (
type countingReader struct {
io.Reader
tot uint64 // bytes
last int64 // unix nanos
tot int64 // bytes
last int64 // unix nanos
}
var (
totalIncoming uint64
totalOutgoing uint64
totalIncoming int64
totalOutgoing int64
)
func (c *countingReader) Read(bs []byte) (int, error) {
n, err := c.Reader.Read(bs)
atomic.AddUint64(&c.tot, uint64(n))
atomic.AddUint64(&totalIncoming, uint64(n))
atomic.AddInt64(&c.tot, int64(n))
atomic.AddInt64(&totalIncoming, int64(n))
atomic.StoreInt64(&c.last, time.Now().UnixNano())
return n, err
}
func (c *countingReader) Tot() uint64 {
return atomic.LoadUint64(&c.tot)
func (c *countingReader) Tot() int64 {
return atomic.LoadInt64(&c.tot)
}
func (c *countingReader) Last() time.Time {
@@ -37,26 +37,26 @@ func (c *countingReader) Last() time.Time {
type countingWriter struct {
io.Writer
tot uint64 // bytes
last int64 // unix nanos
tot int64 // bytes
last int64 // unix nanos
}
func (c *countingWriter) Write(bs []byte) (int, error) {
n, err := c.Writer.Write(bs)
atomic.AddUint64(&c.tot, uint64(n))
atomic.AddUint64(&totalOutgoing, uint64(n))
atomic.AddInt64(&c.tot, int64(n))
atomic.AddInt64(&totalOutgoing, int64(n))
atomic.StoreInt64(&c.last, time.Now().UnixNano())
return n, err
}
func (c *countingWriter) Tot() uint64 {
return atomic.LoadUint64(&c.tot)
func (c *countingWriter) Tot() int64 {
return atomic.LoadInt64(&c.tot)
}
func (c *countingWriter) Last() time.Time {
return time.Unix(0, atomic.LoadInt64(&c.last))
}
func TotalInOut() (uint64, uint64) {
return atomic.LoadUint64(&totalIncoming), atomic.LoadUint64(&totalOutgoing)
func TotalInOut() (int64, int64) {
return atomic.LoadInt64(&totalIncoming), atomic.LoadInt64(&totalOutgoing)
}

View File

@@ -1,6 +1,5 @@
// Copyright (C) 2014 The Protocol Authors.
//go:generate -command genxdr go run ../../Godeps/_workspace/src/github.com/calmh/xdr/cmd/genxdr/main.go
//go:generate genxdr -o message_xdr.go message.go
package protocol
@@ -18,8 +17,8 @@ type FileInfo struct {
Name string // max:8192
Flags uint32
Modified int64
Version uint64
LocalVersion uint64
Version int64
LocalVersion int64
Blocks []BlockInfo
}
@@ -60,7 +59,7 @@ func (f FileInfo) HasPermissionBits() bool {
type BlockInfo struct {
Offset int64 // noencode (cache only)
Size uint32
Size int32
Hash []byte // max:64
}
@@ -71,8 +70,8 @@ func (b BlockInfo) String() string {
type RequestMessage struct {
Folder string // max:64
Name string // max:8192
Offset uint64
Size uint32
Offset int64
Size int32
Hash []byte // max:64
Flags uint32
Options []Option // max:64
@@ -80,7 +79,7 @@ type RequestMessage struct {
type ResponseMessage struct {
Data []byte
Error uint32
Error int32
}
type ClusterConfigMessage struct {
@@ -107,7 +106,7 @@ type Folder struct {
type Device struct {
ID []byte // max:32
Flags uint32
MaxLocalVersion uint64
MaxLocalVersion int64
}
type Option struct {
@@ -117,7 +116,7 @@ type Option struct {
type CloseMessage struct {
Reason string // max:1024
Code uint32
Code int32
}
type EmptyMessage struct{}

View File

@@ -168,8 +168,8 @@ struct FileInfo {
string Name<8192>;
unsigned int Flags;
hyper Modified;
unsigned hyper Version;
unsigned hyper LocalVersion;
hyper Version;
hyper LocalVersion;
BlockInfo Blocks<>;
}
@@ -206,8 +206,8 @@ func (o FileInfo) encodeXDR(xw *xdr.Writer) (int, error) {
xw.WriteString(o.Name)
xw.WriteUint32(o.Flags)
xw.WriteUint64(uint64(o.Modified))
xw.WriteUint64(o.Version)
xw.WriteUint64(o.LocalVersion)
xw.WriteUint64(uint64(o.Version))
xw.WriteUint64(uint64(o.LocalVersion))
xw.WriteUint32(uint32(len(o.Blocks)))
for i := range o.Blocks {
_, err := o.Blocks[i].encodeXDR(xw)
@@ -233,8 +233,8 @@ func (o *FileInfo) decodeXDR(xr *xdr.Reader) error {
o.Name = xr.ReadStringMax(8192)
o.Flags = xr.ReadUint32()
o.Modified = int64(xr.ReadUint64())
o.Version = xr.ReadUint64()
o.LocalVersion = xr.ReadUint64()
o.Version = int64(xr.ReadUint64())
o.LocalVersion = int64(xr.ReadUint64())
_BlocksSize := int(xr.ReadUint32())
o.Blocks = make([]BlockInfo, _BlocksSize)
for i := range o.Blocks {
@@ -261,7 +261,7 @@ BlockInfo Structure:
struct BlockInfo {
unsigned int Size;
int Size;
opaque Hash<64>;
}
@@ -292,7 +292,7 @@ func (o BlockInfo) AppendXDR(bs []byte) ([]byte, error) {
}
func (o BlockInfo) encodeXDR(xw *xdr.Writer) (int, error) {
xw.WriteUint32(o.Size)
xw.WriteUint32(uint32(o.Size))
if l := len(o.Hash); l > 64 {
return xw.Tot(), xdr.ElementSizeExceeded("Hash", l, 64)
}
@@ -312,7 +312,7 @@ func (o *BlockInfo) UnmarshalXDR(bs []byte) error {
}
func (o *BlockInfo) decodeXDR(xr *xdr.Reader) error {
o.Size = xr.ReadUint32()
o.Size = int32(xr.ReadUint32())
o.Hash = xr.ReadBytesMax(64)
return xr.Error()
}
@@ -361,8 +361,8 @@ RequestMessage Structure:
struct RequestMessage {
string Folder<64>;
string Name<8192>;
unsigned hyper Offset;
unsigned int Size;
hyper Offset;
int Size;
opaque Hash<64>;
unsigned int Flags;
Option Options<64>;
@@ -403,8 +403,8 @@ func (o RequestMessage) encodeXDR(xw *xdr.Writer) (int, error) {
return xw.Tot(), xdr.ElementSizeExceeded("Name", l, 8192)
}
xw.WriteString(o.Name)
xw.WriteUint64(o.Offset)
xw.WriteUint32(o.Size)
xw.WriteUint64(uint64(o.Offset))
xw.WriteUint32(uint32(o.Size))
if l := len(o.Hash); l > 64 {
return xw.Tot(), xdr.ElementSizeExceeded("Hash", l, 64)
}
@@ -437,8 +437,8 @@ func (o *RequestMessage) UnmarshalXDR(bs []byte) error {
func (o *RequestMessage) decodeXDR(xr *xdr.Reader) error {
o.Folder = xr.ReadStringMax(64)
o.Name = xr.ReadStringMax(8192)
o.Offset = xr.ReadUint64()
o.Size = xr.ReadUint32()
o.Offset = int64(xr.ReadUint64())
o.Size = int32(xr.ReadUint32())
o.Hash = xr.ReadBytesMax(64)
o.Flags = xr.ReadUint32()
_OptionsSize := int(xr.ReadUint32())
@@ -471,7 +471,7 @@ ResponseMessage Structure:
struct ResponseMessage {
opaque Data<>;
unsigned int Error;
int Error;
}
*/
@@ -502,7 +502,7 @@ func (o ResponseMessage) AppendXDR(bs []byte) ([]byte, error) {
func (o ResponseMessage) encodeXDR(xw *xdr.Writer) (int, error) {
xw.WriteBytes(o.Data)
xw.WriteUint32(o.Error)
xw.WriteUint32(uint32(o.Error))
return xw.Tot(), xw.Error()
}
@@ -519,7 +519,7 @@ func (o *ResponseMessage) UnmarshalXDR(bs []byte) error {
func (o *ResponseMessage) decodeXDR(xr *xdr.Reader) error {
o.Data = xr.ReadBytes()
o.Error = xr.ReadUint32()
o.Error = int32(xr.ReadUint32())
return xr.Error()
}
@@ -766,7 +766,7 @@ Device Structure:
struct Device {
opaque ID<32>;
unsigned int Flags;
unsigned hyper MaxLocalVersion;
hyper MaxLocalVersion;
}
*/
@@ -801,7 +801,7 @@ func (o Device) encodeXDR(xw *xdr.Writer) (int, error) {
}
xw.WriteBytes(o.ID)
xw.WriteUint32(o.Flags)
xw.WriteUint64(o.MaxLocalVersion)
xw.WriteUint64(uint64(o.MaxLocalVersion))
return xw.Tot(), xw.Error()
}
@@ -819,7 +819,7 @@ func (o *Device) UnmarshalXDR(bs []byte) error {
func (o *Device) decodeXDR(xr *xdr.Reader) error {
o.ID = xr.ReadBytesMax(32)
o.Flags = xr.ReadUint32()
o.MaxLocalVersion = xr.ReadUint64()
o.MaxLocalVersion = int64(xr.ReadUint64())
return xr.Error()
}
@@ -923,7 +923,7 @@ CloseMessage Structure:
struct CloseMessage {
string Reason<1024>;
unsigned int Code;
int Code;
}
*/
@@ -957,7 +957,7 @@ func (o CloseMessage) encodeXDR(xw *xdr.Writer) (int, error) {
return xw.Tot(), xdr.ElementSizeExceeded("Reason", l, 1024)
}
xw.WriteString(o.Reason)
xw.WriteUint32(o.Code)
xw.WriteUint32(uint32(o.Code))
return xw.Tot(), xw.Error()
}
@@ -974,7 +974,7 @@ func (o *CloseMessage) UnmarshalXDR(bs []byte) error {
func (o *CloseMessage) decodeXDR(xr *xdr.Reader) error {
o.Reason = xr.ReadStringMax(1024)
o.Code = xr.ReadUint32()
o.Code = int32(xr.ReadUint32())
return xr.Error()
}

View File

@@ -222,8 +222,8 @@ func (c *rawConnection) Request(folder string, name string, offset int64, size i
ok := c.send(id, messageTypeRequest, RequestMessage{
Folder: folder,
Name: name,
Offset: uint64(offset),
Size: uint32(size),
Offset: offset,
Size: int32(size),
})
if !ok {
return nil, ErrClosed
@@ -706,8 +706,8 @@ func (c *rawConnection) pingerLoop() {
type Statistics struct {
At time.Time
InBytesTotal uint64
OutBytesTotal uint64
InBytesTotal int64
OutBytesTotal int64
}
func (c *rawConnection) Statistics() Statistics {

2
NICKS
View File

@@ -24,6 +24,7 @@ gillisig <gilli@vx.is>
jedie <github.com@jensdiemer.de> <git@jensdiemer.de>
jpjp <jamespatterson@operamail.com> <jpjp@users.noreply.github.com>
kozec <kozec@kozec.com>
krozycki <rozycki.karol@gmail.com>
marcindziadus <dziadus.marcin@gmail.com>
mvdan <mvdan@mvdan.cc>
peterhoeg <peter@speartail.com>
@@ -31,6 +32,7 @@ philips <brandon@ifup.org>
piobpl <piotrb10@gmail.com>
pluby <phill.luby@newredo.com>
pyfisch <pyfisch@gmail.com>
rumpelsepp <stefan@sevenbyte.org>
qbit <qbit@deftly.net>
seehuhn <voss@seehuhn.de>
snnd <dw@risu.io>

View File

@@ -301,7 +301,7 @@ func assets() {
}
func xdr() {
runPrint("go", "generate", "./internal/discover", "./internal/db", "./internal/protocol")
runPrint("go", "generate", "./internal/discover", "./internal/db")
}
func translate() {

View File

@@ -2,7 +2,8 @@
set -euo pipefail
IFS=$'\n\t'
DOCKERIMGV=1.4-5
DOCKERIMGV=1.4.1-1
STTRACE=${STTRACE:-}
case "${1:-default}" in
default)
@@ -110,6 +111,7 @@ case "${1:-default}" in
docker run --rm -h syncthing-builder -u $(id -u) -t \
-v $(pwd):/go/src/github.com/syncthing/syncthing \
-w /go/src/github.com/syncthing/syncthing \
-e "STTRACE=$STTRACE" \
syncthing/build:$DOCKERIMGV \
sh -c './build.sh clean \
&& go vet ./cmd/... ./internal/... \
@@ -122,6 +124,7 @@ case "${1:-default}" in
docker run --rm -h syncthing-builder -u $(id -u) -t \
-v $(pwd):/go/src/github.com/syncthing/syncthing \
-w /go/src/github.com/syncthing/syncthing \
-e "STTRACE=$STTRACE" \
syncthing/build:$DOCKERIMGV \
sh -euxc './build.sh clean \
&& go run build.go -race \

View File

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

View File

@@ -16,7 +16,8 @@ no-docs-typos() {
grep -v f1120d7aa936c0658429edef0037792520b46334 |\
grep -v a9339d0627fff439879d157c75077f02c9fac61b |\
grep -v 254c63763a3ad42fd82259f1767db526cff94a14 |\
grep -v 4b76ec40c07078beaa2c5e250ed7d9bd6276a718
grep -v 4b76ec40c07078beaa2c5e250ed7d9bd6276a718 |\
grep -v ffc39dfbcb34eacc3ea12327a02b6e7741a2c207
}
print-missing-authors() {

View File

@@ -0,0 +1,59 @@
// Copyright (C) 2014 The Syncthing Authors.
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along
// with this program. If not, see <http://www.gnu.org/licenses/>.
package main
import (
"fmt"
"os"
"runtime"
"runtime/pprof"
"syscall"
"time"
)
func init() {
if innerProcess && os.Getenv("STBLOCKPROFILE") != "" {
profiler := pprof.Lookup("block")
if profiler == nil {
panic("Couldn't find block profiler")
}
l.Debugln("Starting block profiling")
go saveBlockingProfiles(profiler)
}
}
func saveBlockingProfiles(profiler *pprof.Profile) {
runtime.SetBlockProfileRate(1)
t0 := time.Now()
for t := range time.NewTicker(20 * time.Second).C {
startms := int(t.Sub(t0).Seconds() * 1000)
fd, err := os.Create(fmt.Sprintf("block-%05d-%07d.pprof", syscall.Getpid(), startms))
if err != nil {
panic(err)
}
err = profiler.WriteTo(fd, 0)
if err != nil {
panic(err)
}
err = fd.Close()
if err != nil {
panic(err)
}
}
}

View File

@@ -302,6 +302,15 @@ func restGetModel(m *model.Model, w http.ResponseWriter, r *http.Request) {
res["state"], res["stateChanged"] = m.State(folder)
res["version"] = m.CurrentLocalVersion(folder) + m.RemoteLocalVersion(folder)
ignorePatterns, _, _ := m.GetIgnores(folder)
res["ignorePatterns"] = false
for _, line := range ignorePatterns {
if len(line) > 0 && !strings.HasPrefix(line, "//") {
res["ignorePatterns"] = true
break
}
}
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(res)
}
@@ -794,7 +803,7 @@ func toNeedSlice(fs []db.FileInfoTruncated) []map[string]interface{} {
"Version": file.Version,
"LocalVersion": file.LocalVersion,
"NumBlocks": file.NumBlocks,
"Size": db.BlocksToSize(file.NumBlocks),
"Size": db.BlocksToSize(int(file.NumBlocks)),
}
}
return output

View File

@@ -142,38 +142,41 @@ Development Settings
The following environment variables modify syncthing's behavior in ways that
are mostly useful for developers. Use with care.
STGUIASSETS Directory to load GUI assets from. Overrides compiled in assets.
STGUIASSETS Directory to load GUI assets from. Overrides compiled in assets.
STTRACE A comma separated string of facilities to trace. The valid
facility strings are:
STTRACE A comma separated string of facilities to trace. The valid
facility strings are:
- "beacon" (the beacon package)
- "discover" (the discover package)
- "events" (the events package)
- "files" (the files package)
- "net" (the main package; connections & network messages)
- "model" (the model package)
- "scanner" (the scanner package)
- "stats" (the stats package)
- "upnp" (the upnp package)
- "xdr" (the xdr package)
- "all" (all of the above)
- "beacon" (the beacon package)
- "discover" (the discover package)
- "events" (the events package)
- "files" (the files package)
- "net" (the main package; connections & network messages)
- "model" (the model package)
- "scanner" (the scanner package)
- "stats" (the stats package)
- "upnp" (the upnp package)
- "xdr" (the xdr package)
- "all" (all of the above)
STPROFILER Set to a listen address such as "127.0.0.1:9090" to start the
profiler with HTTP access.
STPROFILER Set to a listen address such as "127.0.0.1:9090" to start the
profiler with HTTP access.
STCPUPROFILE Write a CPU profile to cpu-$pid.pprof on exit.
STCPUPROFILE Write a CPU profile to cpu-$pid.pprof on exit.
STHEAPPROFILE Write heap profiles to heap-$pid-$timestamp.pprof each time
heap usage increases.
STHEAPPROFILE Write heap profiles to heap-$pid-$timestamp.pprof each time
heap usage increases.
STPERFSTATS Write running performance statistics to perf-$pid.csv. Not
supported on Windows.
STBLOCKPROFILE Write block profiles to block-$pid-$timestamp.pprof every 20
seconds.
STNOUPGRADE Disable automatic upgrades.
STPERFSTATS Write running performance statistics to perf-$pid.csv. Not
supported on Windows.
GOMAXPROCS Set the maximum number of CPU cores to use. Defaults to all
available CPU cores.`
STNOUPGRADE Disable automatic upgrades.
GOMAXPROCS Set the maximum number of CPU cores to use. Defaults to all
available CPU cores.`
)
// Command line and environment options
@@ -658,7 +661,9 @@ func setupGUI(cfg *config.Wrapper, m *model.Model) {
}
if opts.StartBrowser && !noBrowser && !stRestarting {
urlOpen := fmt.Sprintf("%s://%s/", proto, net.JoinHostPort(hostOpen, strconv.Itoa(addr.Port)))
openURL(urlOpen)
// Can potentially block if the utility we are invoking doesn't
// fork, and just execs, hence keep it in it's own routine.
go openURL(urlOpen)
}
}
}
@@ -874,8 +879,10 @@ func resetFolders() {
suffix := fmt.Sprintf(".syncthing-reset-%d", time.Now().UnixNano())
for _, folder := range cfg.Folders() {
if _, err := os.Stat(folder.Path); err == nil {
l.Infof("Reset: Moving %s -> %s", folder.Path, folder.Path+suffix)
os.Rename(folder.Path, folder.Path+suffix)
base := filepath.Base(folder.Path)
dir := filepath.Dir(filepath.Join(folder.Path, ".."))
l.Infof("Reset: Moving %s -> %s", folder.Path, filepath.Join(dir, base+suffix))
os.Rename(folder.Path, filepath.Join(dir, base+suffix))
}
}

View File

@@ -22,7 +22,7 @@ import (
"strings"
)
func memorySize() (uint64, error) {
func memorySize() (int64, error) {
cmd := exec.Command("sysctl", "hw.memsize")
out, err := cmd.Output()
if err != nil {
@@ -32,7 +32,7 @@ func memorySize() (uint64, error) {
if len(fs) != 2 {
return 0, errors.New("sysctl parse error")
}
bytes, err := strconv.ParseUint(fs[1], 10, 64)
bytes, err := strconv.ParseInt(fs[1], 10, 64)
if err != nil {
return 0, err
}

View File

@@ -23,7 +23,7 @@ import (
"strings"
)
func memorySize() (uint64, error) {
func memorySize() (int64, error) {
f, err := os.Open("/proc/meminfo")
if err != nil {
return 0, err
@@ -40,7 +40,7 @@ func memorySize() (uint64, error) {
return 0, errors.New("/proc/meminfo parse error 2")
}
kb, err := strconv.ParseUint(fs[1], 10, 64)
kb, err := strconv.ParseInt(fs[1], 10, 64)
if err != nil {
return 0, err
}

View File

@@ -22,14 +22,14 @@ import (
"strconv"
)
func memorySize() (uint64, error) {
func memorySize() (int64, error) {
cmd := exec.Command("prtconf", "-m")
out, err := cmd.CombinedOutput()
if err != nil {
return 0, err
}
mb, err := strconv.ParseUint(string(out), 10, 64)
mb, err := strconv.ParseInt(string(out), 10, 64)
if err != nil {
return 0, err
}

View File

@@ -19,6 +19,6 @@ package main
import "errors"
func memorySize() (uint64, error) {
func memorySize() (int64, error) {
return 0, errors.New("not implemented")
}

View File

@@ -26,7 +26,7 @@ var (
globalMemoryStatusEx, _ = syscall.GetProcAddress(kernel32, "GlobalMemoryStatusEx")
)
func memorySize() (uint64, error) {
func memorySize() (int64, error) {
var memoryStatusEx [64]byte
binary.LittleEndian.PutUint32(memoryStatusEx[:], 64)
p := uintptr(unsafe.Pointer(&memoryStatusEx[0]))
@@ -36,5 +36,5 @@ func memorySize() (uint64, error) {
return 0, callErr
}
return binary.LittleEndian.Uint64(memoryStatusEx[8:]), nil
return int64(binary.LittleEndian.Uint64(memoryStatusEx[8:])), nil
}

View File

@@ -43,7 +43,7 @@ func savePerfStats(file string) {
var prevTime int64
var rusage syscall.Rusage
var memstats runtime.MemStats
var prevIn, prevOut uint64
var prevIn, prevOut int64
t0 := time.Now()
for t := range time.NewTicker(250 * time.Millisecond).C {

View File

@@ -1,7 +1,7 @@
FROM debian:squeeze
MAINTAINER Jakob Borg <jakob@nym.se>
ENV GOLANG_VERSION 1.4
ENV GOLANG_VERSION 1.4.1
# SCMs for "go get", gcc for cgo
RUN apt-get update && apt-get install -y \
@@ -34,14 +34,14 @@ RUN go get github.com/calmh/gonative \
# Rebuild the special and missing versions, using patches as appropriate
RUN mkdir /tmp/patches
ADD *.patch /tmp/patches/
RUN bash -xec '\
cd /usr/local/go ; \
for patch in /tmp/patches/*.patch ; do \
patch -p0 < "$patch" ; \
done \
'
# RUN mkdir /tmp/patches
# ADD *.patch /tmp/patches/
# RUN bash -xec '\
# cd /usr/local/go ; \
# for patch in /tmp/patches/*.patch ; do \
# patch -p0 < "$patch" ; \
# done \
# '
RUN bash -xec '\
cd /usr/local/go/src; \

View File

@@ -1,22 +0,0 @@
--- src/syscall/route_openbsd.go.orig Fri Jul 25 23:38:47 2014
+++ src/syscall/route_openbsd.go Fri Jul 25 23:39:20 2014
@@ -12,16 +12,16 @@ func (any *anyMessage) toRoutingMessage(b []byte) Rout
switch any.Type {
case RTM_ADD, RTM_DELETE, RTM_CHANGE, RTM_GET, RTM_LOSING, RTM_REDIRECT, RTM_MISS, RTM_LOCK, RTM_RESOLVE:
p := (*RouteMessage)(unsafe.Pointer(any))
- return &RouteMessage{Header: p.Header, Data: b[SizeofRtMsghdr:any.Msglen]}
+ return &RouteMessage{Header: p.Header, Data: b[p.Header.Hdrlen:any.Msglen]}
case RTM_IFINFO:
p := (*InterfaceMessage)(unsafe.Pointer(any))
- return &InterfaceMessage{Header: p.Header, Data: b[SizeofIfMsghdr:any.Msglen]}
+ return &InterfaceMessage{Header: p.Header, Data: b[p.Header.Hdrlen:any.Msglen]}
case RTM_IFANNOUNCE:
p := (*InterfaceAnnounceMessage)(unsafe.Pointer(any))
return &InterfaceAnnounceMessage{Header: p.Header}
case RTM_NEWADDR, RTM_DELADDR:
p := (*InterfaceAddrMessage)(unsafe.Pointer(any))
- return &InterfaceAddrMessage{Header: p.Header, Data: b[SizeofIfaMsghdr:any.Msglen]}
+ return &InterfaceAddrMessage{Header: p.Header, Data: b[p.Header.Hdrlen:any.Msglen]}
}
return nil
}

View File

@@ -9,6 +9,9 @@ EnvironmentFile=-/etc/default/syncthing
Environment=STNORESTART=yes
ExecStart=/usr/bin/syncthing ${STARGS}
Restart=on-failure
RestartPreventExitStatus=1
SuccessExitStatus=2
RestartForceExitStatus=3 4
[Install]
WantedBy=multi-user.target

View File

@@ -7,6 +7,9 @@ EnvironmentFile=-%h/.config/syncthing/environment
Environment=STNORESTART=yes
ExecStart=/usr/bin/syncthing ${STARGS}
Restart=on-failure
RestartPreventExitStatus=1
SuccessExitStatus=2
RestartForceExitStatus=3 4
[Install]
WantedBy=cmdline.target

View File

@@ -1,40 +1,40 @@
{
"API Key": "Ключ API",
"About": "Аб праграме",
"Add": "Add",
"Add": "Дадаць",
"Add Device": "Дадаць прыладу",
"Add Folder": "Дадаць каталёг",
"Add new folder?": "Add new folder?",
"Add new folder?": "Дадаць новы каталёг ?",
"Address": "Адрас",
"Addresses": "Адрасы",
"Allow Anonymous Usage Reporting?": "Allow Anonymous Usage Reporting?",
"Anonymous Usage Reporting": "Anonymous Usage Reporting",
"Any devices configured on an introducer device will be added to this device as well.": "Any devices configured on an introducer device will be added to this device as well.",
"Automatic upgrades": "Automatic upgrades",
"Bugs": "Bugs",
"CPU Utilization": "CPU Utilization",
"Changelog": "Changelog",
"Close": "Close",
"Bugs": "Памылкі",
"CPU Utilization": "Выкарыстаньне працэсара",
"Changelog": "Сьпіс зьменаў",
"Close": "Зачыніць",
"Comment, when used at the start of a line": "Comment, when used at the start of a line",
"Compression is recommended in most setups.": "Compression is recommended in most setups.",
"Connection Error": "Connection Error",
"Copied from elsewhere": "Copied from elsewhere",
"Copied from original": "Copied from original",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Copyright © 2014 Jakob Borg and the following Contributors:",
"Delete": "Delete",
"Device ID": "Device ID",
"Delete": "Выдаліць",
"Device ID": "ID прылады",
"Device Identification": "Device Identification",
"Device Name": "Device Name",
"Device Name": "Назва прылады",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
"Devices": "Devices",
"Devices": "Прылады",
"Disconnected": "Disconnected",
"Documentation": "Documentation",
"Download Rate": "Download Rate",
"Documentation": "Дакумэнтацыя",
"Download Rate": "Хуткасьць спампоўваньня",
"Downloaded": "Downloaded",
"Downloading": "Downloading",
"Edit": "Edit",
"Edit Device": "Edit Device",
"Edit Folder": "Edit Folder",
"Edit": "Зьмяніць",
"Edit Device": "Зьмяніць прыладу",
"Edit Folder": "Зьмяніць каталёг",
"Editing": "Editing",
"Enable UPnP": "Enable UPnP",
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.",
@@ -44,32 +44,32 @@
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "File permission bits are ignored when looking for changes. Use on FAT filesystems.",
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.": "Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.",
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.",
"Folder ID": "Folder ID",
"Folder ID": "ID каталёгу",
"Folder Master": "Folder Master",
"Folder Path": "Folder Path",
"Folders": "Folders",
"Folder Path": "Шлях каталёгу",
"Folders": "Каталёгі",
"GUI Authentication Password": "GUI Authentication Password",
"GUI Authentication User": "GUI Authentication User",
"GUI Listen Addresses": "GUI Listen Addresses",
"Generate": "Generate",
"Global Discovery": "Global Discovery",
"Global Discovery Server": "Global Discovery Server",
"Global State": "Global State",
"Global State": "Глябальны стан",
"Idle": "Idle",
"Ignore": "Ignore",
"Ignore Patterns": "Ignore Patterns",
"Ignore Permissions": "Ignore Permissions",
"Ignore Permissions": "Ігнараваць правы",
"Incoming Rate Limit (KiB/s)": "Incoming Rate Limit (KiB/s)",
"Introducer": "Introducer",
"Inversion of the given condition (i.e. do not exclude)": "Inversion of the given condition (i.e. do not exclude)",
"Keep Versions": "Keep Versions",
"Last File Received": "Last File Received",
"Last File Synced": "Last File Synced",
"Last File Synced": "Апошні файл",
"Last seen": "Last seen",
"Later": "Later",
"Latest Release": "Latest Release",
"Local Discovery": "Local Discovery",
"Local State": "Local State",
"Local State": "Лякальны стан",
"Maximum Age": "Maximum Age",
"Multi level wildcard (matches multiple directory levels)": "Multi level wildcard (matches multiple directory levels)",
"Never": "Never",
@@ -81,8 +81,8 @@
"OK": "OK",
"Offline": "Offline",
"Online": "Online",
"Out Of Sync": "Out Of Sync",
"Out of Sync Items": "Out of Sync Items",
"Out Of Sync": "Несынхранізавана",
"Out of Sync Items": "Несынхранізаваныя складнікі",
"Outgoing Rate Limit (KiB/s)": "Outgoing Rate Limit (KiB/s)",
"Override Changes": "Override Changes",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for",
@@ -91,18 +91,18 @@
"Preview": "Preview",
"Preview Usage Report": "Preview Usage Report",
"Quick guide to supported patterns": "Quick guide to supported patterns",
"RAM Utilization": "RAM Utilization",
"Rescan": "Rescan",
"Rescan Interval": "Rescan Interval",
"Restart": "Restart",
"Restart Needed": "Restart Needed",
"Restarting": "Restarting",
"RAM Utilization": "Выкарыстаньне памяці",
"Rescan": "Перачытаць",
"Rescan Interval": "Інтэрвал перачытваньня",
"Restart": "Перастартаваць",
"Restart Needed": "Патрэбна перастартоўваньне",
"Restarting": "Перастартоўваньне",
"Reused": "Reused",
"Save": "Save",
"Save": "Захаваць",
"Scanning": "Scanning",
"Select the devices to share this folder with.": "Select the devices to share this folder with.",
"Select the folders to share with this device.": "Select the folders to share with this device.",
"Settings": "Settings",
"Settings": "Налады",
"Share": "Share",
"Share Folder": "Share Folder",
"Share Folders With Device": "Share Folders With Device",
@@ -110,25 +110,25 @@
"Share this folder?": "Share this folder?",
"Shared With": "Shared With",
"Short identifier for the folder. Must be the same on all cluster devices.": "Short identifier for the folder. Must be the same on all cluster devices.",
"Show ID": "Show ID",
"Show ID": "Паказаць ID",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.",
"Shutdown": "Shutdown",
"Shutdown Complete": "Shutdown Complete",
"Shutdown": "Выключыць",
"Shutdown Complete": "Выключэньне завершанае",
"Simple File Versioning": "Simple File Versioning",
"Single level wildcard (matches within a directory only)": "Single level wildcard (matches within a directory only)",
"Source Code": "Source Code",
"Source Code": "Зыходнікі",
"Staggered File Versioning": "Staggered File Versioning",
"Start Browser": "Start Browser",
"Stopped": "Stopped",
"Support": "Support",
"Support / Forum": "Support / Forum",
"Support": "Падтрымка",
"Support / Forum": "Падтрымка / Форум",
"Sync Protocol Listen Addresses": "Sync Protocol Listen Addresses",
"Synchronization": "Synchronization",
"Synchronization": "Сынхранізацыя",
"Syncing": "Syncing",
"Syncthing has been shut down.": "Syncthing has been shut down.",
"Syncthing includes the following software or portions thereof:": "Syncthing includes the following software or portions thereof:",
"Syncthing is restarting.": "Syncthing is restarting.",
"Syncthing is restarting.": "Syncthing перастартоўвае.",
"Syncthing is upgrading.": "Syncthing is upgrading.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please reload your browser or restart Syncthing if the problem persists.",
@@ -152,13 +152,13 @@
"Unknown": "Unknown",
"Unshared": "Unshared",
"Unused": "Unused",
"Up to Date": "Up to Date",
"Up to Date": "Найноўшае",
"Upgrade To {%version%}": "Upgrade To {{version}}",
"Upgrading": "Upgrading",
"Upload Rate": "Upload Rate",
"Upload Rate": "Хуткасьць запампоўваньня",
"Use Compression": "Use Compression",
"Use HTTPS for GUI": "Use HTTPS for GUI",
"Version": "Version",
"Version": "Вэрсія",
"Versions Path": "Versions Path",
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.",
"When adding a new device, keep in mind that this device must be added on the other side too.": "When adding a new device, keep in mind that this device must be added on the other side too.",
@@ -166,6 +166,6 @@
"Yes": "Yes",
"You must keep at least one version.": "You must keep at least one version.",
"full documentation": "full documentation",
"items": "items",
"items": "складнікаў",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\"."
}

View File

@@ -1,10 +1,10 @@
{
"API Key": "API Ключ",
"About": "За Програмата",
"Add": "Add",
"Add": "Добави",
"Add Device": "Добави устройство",
"Add Folder": "Добави папка",
"Add new folder?": "Add new folder?",
"Add new folder?": "Добави нова папка?",
"Address": "Адрес",
"Addresses": "Адреси",
"Allow Anonymous Usage Reporting?": "Разреши анонимен доклад за ползване на програмата?",
@@ -13,7 +13,7 @@
"Automatic upgrades": "Автоматични ъпдейти",
"Bugs": "Бъгове",
"CPU Utilization": "Натоварване на Процесора",
"Changelog": "Changelog",
"Changelog": "Сипъск с промени",
"Close": "Затвори",
"Comment, when used at the start of a line": "Коментар, използван в началото на реда",
"Compression is recommended in most setups.": "Компресията е препоръчителна в повечето конфигурации.",
@@ -25,8 +25,8 @@
"Device ID": "Идентификатор на устройство",
"Device Identification": "Идентификация на устройство",
"Device Name": "Име на устройство",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
"Devices": "Devices",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Устройство {{device}} ({{address}}) желае да се свърже. Добави ново устройство?",
"Devices": "Устройства",
"Disconnected": "Прекрати Връзката",
"Documentation": "Документация",
"Download Rate": "Скорост на Теглене",
@@ -56,25 +56,25 @@
"Global Discovery Server": "Сървър за Глобално Откриване",
"Global State": "Глобално състояние",
"Idle": "Без Работа",
"Ignore": "Ignore",
"Ignore": "Игнорирай",
"Ignore Patterns": "Шаблони за Игнориране",
"Ignore Permissions": "Игнорирай Права за Достъп",
"Incoming Rate Limit (KiB/s)": "Входящ Лимит на Скоростта (KiB/s)",
"Introducer": "Introducer",
"Inversion of the given condition (i.e. do not exclude)": "Обратното на даденото условие (пр. не изключвай)",
"Keep Versions": "Пази Версии",
"Last File Received": "Last File Received",
"Last File Received": "Последния получен файл",
"Last File Synced": "Последния синхронизиран файл",
"Last seen": "Последно видян",
"Later": "Later",
"Later": "По-късно",
"Latest Release": "Най-новата Версия",
"Local Discovery": "Локално Откриване",
"Local State": "Локално състояние",
"Maximum Age": "Максимална Възраст",
"Multi level wildcard (matches multiple directory levels)": "Маска на много нива (покрива папки с много нива)",
"Never": "Никога",
"New Device": "New Device",
"New Folder": "New Folder",
"New Device": "Ново устройство",
"New Folder": "Нова папка",
"No": "Не",
"No File Versioning": "Няма Файлови Версии",
"Notice": "Известие",
@@ -82,7 +82,7 @@
"Offline": "Не е на линия",
"Online": "На линия",
"Out Of Sync": "Не Синхронизиран",
"Out of Sync Items": "Out of Sync Items",
"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": "Пътят до папката на този компютър. Ще бъде създадена ако не съществува. Символът тилда (~) може да бъде използван като заместител на",
@@ -103,35 +103,35 @@
"Select the devices to share this folder with.": "Избери устройствата, с които да споделиш тази папка.",
"Select the folders to share with this device.": "Изберете папките за споделяне с това устройство.",
"Settings": "Настройки",
"Share": "Share",
"Share Folder": "Share Folder",
"Share": "Сподели",
"Share Folder": "Сподели папка",
"Share Folders With Device": "Сподели папки с това устройство",
"Share With Devices": "Сподели с устройства",
"Share this folder?": "Share this folder?",
"Share this folder?": "Сподели тази папка?",
"Shared With": "Споделена със",
"Short identifier for the folder. Must be the same on all cluster devices.": "Кратък идентификатор на папката. Трябва да бъде същият на всички компютри.",
"Show ID": "Покажи Идентификатора",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Покажи вместо идентификатор на устройството в статус на клъстъра. Ще бъде предлагано на други комютри като име по подразбиране.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Покажи вместо идентификатор на устройството в статус на клъстъра. Ще бъде обновено с името по подразбиране изпратено от другия компютър.",
"Shutdown": "Спри Програмата",
"Shutdown Complete": "Shutdown Complete",
"Shutdown Complete": "Спирането завършено",
"Simple File Versioning": "Опростени Файлови Версии",
"Single level wildcard (matches within a directory only)": "Маска на едно ниво (покрива само в папка)",
"Source Code": "Сорс Код",
"Staggered File Versioning": "Наслагващи се Файлови Версии",
"Start Browser": "Стартирай Браузъра",
"Stopped": "Спряна",
"Support": "Support",
"Support": "Помощ",
"Support / Forum": "Помощ / Форум",
"Sync Protocol Listen Addresses": "Адрес за слушане на синхронизиращия протокол",
"Synchronization": "Синхронизация",
"Syncing": "Синхронизиране",
"Syncthing has been shut down.": "Syncthing е спрян.",
"Syncthing includes the following software or portions thereof:": "Syncthing включва следният софтуер пълно или частично:",
"Syncthing is restarting.": "Syncthing се рестартирва",
"Syncthing is 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 seems to be experiencing a problem processing your request. Please reload your browser or restart Syncthing if the problem persists.",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing има проблем при обработването на заявката. Моля, презаредете браузъра или рестартирайте Syncthing ако проблемът продължи.",
"The aggregated statistics are publicly available at {%url%}.": "Сумарната статистика е публично достъпна на {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Конфигурацията е запазена, но не е активирана. Syncthing трябва да рестартира, за да се активира новата конфигурация.",
"The device ID cannot be blank.": "Полето идентификатор на устройство не може да бъде празно.",
@@ -167,5 +167,5 @@
"You must keep at least one version.": "Трябва да пазиш поне една версия.",
"full documentation": "пълна документация",
"items": "артикула",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\"."
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} желае да сподели папка \"{{folder}}\"."
}

View File

@@ -166,6 +166,6 @@
"Yes": "Sí",
"You must keep at least one version.": "Debe mantener al menos una versión",
"full documentation": "documentación completa",
"items": "Ítems",
"items": "ítems",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} quiere compartir repositorio \"{{folder}}\"."
}

View File

@@ -26,7 +26,7 @@
"Device Identification": "Identification de l'appareil",
"Device Name": "Nom du périphérique",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "La machine {{device}} ({{address}}) veut se connecter. Voulez-vous ajouter cette machine ?",
"Devices": "Machine",
"Devices": "Machines",
"Disconnected": "Déconnecté",
"Documentation": "Documentation",
"Download Rate": "Débit de réception",
@@ -49,7 +49,7 @@
"Folder Path": "Chemin du répertoire",
"Folders": "Répertoires",
"GUI Authentication Password": "Mot de passe d'authentification GUI",
"GUI Authentication User": "Utilistateur autorisé GUI",
"GUI Authentication User": "Utilisateur autorisé GUI",
"GUI Listen Addresses": "Adresse du GUI",
"Generate": "Générer",
"Global Discovery": "Recherche globale",

View File

@@ -26,7 +26,7 @@
"Device Identification": "Identificazione Dispositivo",
"Device Name": "Nome Dispositivo",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Il dispositivo {{device}} ({{address}}) chiede di connettersi. Aggiungere il nuovo dispositivo?",
"Devices": "Devices",
"Devices": "Dispositivi",
"Disconnected": "Disconnesso",
"Documentation": "Documentazione",
"Download Rate": "Velocità Download",
@@ -63,7 +63,7 @@
"Introducer": "Introduttore",
"Inversion of the given condition (i.e. do not exclude)": "Inversione della condizione indicata (ad es. non escludere)",
"Keep Versions": "Versioni Mantenute",
"Last File Received": "Last File Received",
"Last File Received": "Ultimo File Ricevuto",
"Last File Synced": "Ultimo File Sincronizzato",
"Last seen": "Ultima connessione",
"Later": "Più Tardi",
@@ -82,7 +82,7 @@
"Offline": "Non in linea",
"Online": "In linea",
"Out Of Sync": "Non Sincronizzati",
"Out of Sync Items": "Out of Sync Items",
"Out of Sync Items": "Elementi Non Sincronizzati",
"Outgoing Rate Limit (KiB/s)": "Limite Velocità in Uscita (KiB/s)",
"Override Changes": "Ignora Modifiche",
"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": "Percorso della cartella nel computer locale. Verrà creata se non esiste già. Il carattere tilde (~) può essere utilizzato come scorciatoia per",
@@ -114,14 +114,14 @@
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Visibile al posto dell'ID Dispositivo nello stato del cluster. Negli altri dispositivi verrà presentato come nome predefinito opzionale.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Visibile al posto dell'ID Dispositivo nello stato del cluster. Se viene lasciato vuoto, verrà utilizzato il nome proposto dal dispositivo.",
"Shutdown": "Arresta",
"Shutdown Complete": "Shutdown Complete",
"Shutdown Complete": "Arresto Eseguito",
"Simple File Versioning": "Controllo Versione Semplice",
"Single level wildcard (matches within a directory only)": "Metacarattere di singolo livello (corrisponde solo all'interno di una cartella)",
"Source Code": "Codice Sorgente",
"Staggered File Versioning": "Controllo Versione Cadenzato",
"Start Browser": "Avvia Browser",
"Stopped": "Fermato",
"Support": "Support",
"Support": "Supporto",
"Support / Forum": "Supporto / Forum",
"Sync Protocol Listen Addresses": "Indirizzi del Protocollo di Sincronizzazione",
"Synchronization": "Sincronizzazione",

View File

@@ -1,10 +1,10 @@
{
"API Key": "API-nøkkel",
"About": "Om",
"Add": "Add",
"Add": "Legg til",
"Add Device": "Legg Til Enhet",
"Add Folder": "Legg Til Mappe",
"Add new folder?": "Add new folder?",
"Add new folder?": "Legg til ny mappe?",
"Address": "Adresse",
"Addresses": "Adresser",
"Allow Anonymous Usage Reporting?": "Tillat Anonym Innsamling Av Brukerdata?",
@@ -13,25 +13,25 @@
"Automatic upgrades": "Automatiske oppdateringer",
"Bugs": "Programfeil",
"CPU Utilization": "CPU-utnyttelse",
"Changelog": "Changelog",
"Changelog": "Endringslog",
"Close": "Lukk",
"Comment, when used at the start of a line": "Kommentar, når det blir brukt i starten av en linje.",
"Compression is recommended in most setups.": "Komprimering er anbefalt i de fleste tilfeller.",
"Connection Error": "Tilkoblingsfeil",
"Copied from elsewhere": "Copied from elsewhere",
"Copied from original": "Copied from original",
"Copied from elsewhere": "Kopiert fra et annet sted",
"Copied from original": "Kopiert fra original",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Copyright © 2014 Jakob Borg og følgende Bidragsytere:",
"Delete": "Slett",
"Device ID": "Enhet ID",
"Device Identification": "Enhetskjennemerke",
"Device Name": "Navn På Enhet",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
"Devices": "Devices",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Enhet {{device}} ({{address}}) ønsker å koble seg til. Legg til ny enhet?",
"Devices": "Enheter",
"Disconnected": "Frakoblet",
"Documentation": "Dokumentasjon",
"Download Rate": "Nedlastingsrate",
"Downloaded": "Downloaded",
"Downloading": "Downloading",
"Downloaded": "Lasted ned",
"Downloading": "Laster ned",
"Edit": "Rediger",
"Edit Device": "Rediger Enhet",
"Edit Folder": "Rediger Mappe",
@@ -47,7 +47,7 @@
"Folder ID": "Mappe ID",
"Folder Master": "Styrende Mappe",
"Folder Path": "Mappeplassering",
"Folders": "Folders",
"Folders": "Mapper",
"GUI Authentication Password": "GUI Passord",
"GUI Authentication User": "GUI Bruker",
"GUI Listen Addresses": "GUI Lytteadresse",
@@ -56,25 +56,25 @@
"Global Discovery Server": "Global Søkemotor",
"Global State": "Global Tilstand",
"Idle": "Pause",
"Ignore": "Ignore",
"Ignore": "Ignorer",
"Ignore Patterns": "Utelatelsesmønster",
"Ignore Permissions": "Ignorer Tilgangsbit",
"Incoming Rate Limit (KiB/s)": "Innkommende Hastighetsbegrensning (KiB/s)",
"Introducer": "Introduktør",
"Inversion of the given condition (i.e. do not exclude)": "Invers av den gitte tilstanden (t.d. ikke ekskluder)",
"Keep Versions": "Behold Versjoner",
"Last File Received": "Last File Received",
"Last File Synced": "Last File Synced",
"Last File Received": "Sist Mottatte Fil",
"Last File Synced": "Sist Synkroniserte Fil",
"Last seen": "Sist sett",
"Later": "Later",
"Later": "Senere",
"Latest Release": "Nyeste Versjon",
"Local Discovery": "Lokal Søking",
"Local State": "Lokal Tilstand",
"Maximum Age": "Maksimal Levetid",
"Multi level wildcard (matches multiple directory levels)": "Multinivåsøk (søker på flere mappenivå)",
"Never": "Aldri",
"New Device": "New Device",
"New Folder": "New Folder",
"New Device": "Ny Enhet",
"New Folder": "Ny Mappe",
"No": "Nei",
"No File Versioning": "Ingen Versjonskontroll",
"Notice": "Merknad",
@@ -82,7 +82,7 @@
"Offline": "Frakobla",
"Online": "Tilkobla",
"Out Of Sync": "Ikke Synkronisert",
"Out of Sync Items": "Out of Sync Items",
"Out of Sync Items": "Ikke Synkroniserte Element",
"Outgoing Rate Limit (KiB/s)": "Utgående Hastighetsbegrensning (KiB/s)",
"Override Changes": "Overstyr Endringer",
"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": "Plasseringen av mappen på datamaskinen. Blir opprettet om den ikke finnes. Krøllstrektegnet (~) kan brukes som forkortelse for",
@@ -97,17 +97,17 @@
"Restart": "Omstart",
"Restart Needed": "Omstart Kreves",
"Restarting": "Starter På Ny",
"Reused": "Reused",
"Reused": "Gjenbrukt",
"Save": "Lagre",
"Scanning": "Skanner",
"Select the devices to share this folder with.": "Velg enhetene du vil dele denne mappen med.",
"Select the folders to share with this device.": "Select the folders to share with this device.",
"Select the folders to share with this device.": "Velg hvilke mapper som skal deles med denne enheten.",
"Settings": "Innstillinger",
"Share": "Share",
"Share Folder": "Share Folder",
"Share Folders With Device": "Share Folders With Device",
"Share": "Del",
"Share Folder": "Del Mappe",
"Share Folders With Device": "Del Mapper Med Enhet",
"Share With Devices": "Del Med Enheter",
"Share this folder?": "Share this folder?",
"Share this folder?": "Dele denne mappen?",
"Shared With": "Del Med",
"Short identifier for the folder. Must be the same on all cluster devices.": "Kort kjennemerke på mappen. Må være det samme på alle enheter i en gruppe.",
"Show ID": "Vis ID",
@@ -121,7 +121,7 @@
"Staggered File Versioning": "Forskjøvet Versjonskontroll",
"Start Browser": "Start Nettleser",
"Stopped": "Stoppa",
"Support": "Support",
"Support": "Brukerstøtte",
"Support / Forum": "Brukerstøtte / Forum",
"Sync Protocol Listen Addresses": "Lytteadresse For Synkroniseringsprotokoll",
"Synchronization": "Synkronisering",
@@ -150,8 +150,8 @@
"The rescan interval must be a non-negative number of seconds.": "Antall sekund i skanneintervallet kan ikke være negativt.",
"The rescan interval must be at least 5 seconds.": "Skanneintervallet må være minst 5 sekund.",
"Unknown": "Ukjent",
"Unshared": "Unshared",
"Unused": "Unused",
"Unshared": "Ikke delt",
"Unused": "Ikke i bruk",
"Up to Date": "Oppdatert",
"Upgrade To {%version%}": "Oppgrader Til {{version}}",
"Upgrading": "Oppgraderer",
@@ -167,5 +167,5 @@
"You must keep at least one version.": "Du må beholde minst én versjon",
"full documentation": "all dokumentasjon",
"items": "element",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\"."
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} ønsker å dele mappen \"{{folder}}\"."
}

View File

@@ -1,10 +1,10 @@
{
"API Key": "API-nøkkel",
"About": "Om",
"Add": "Add",
"Add": "Legg til",
"Add Device": "Legg Til Eining",
"Add Folder": "Legg Til Mappe",
"Add new folder?": "Add new folder?",
"Add new folder?": "Legg til ny mappe?",
"Address": "Adresse",
"Addresses": "Adresser",
"Allow Anonymous Usage Reporting?": "Tillat Anonym Innsamling Av Brukardata?",
@@ -13,25 +13,25 @@
"Automatic upgrades": "Automatiske oppdateringar",
"Bugs": "Programfeil",
"CPU Utilization": "CPU-utnytting",
"Changelog": "Changelog",
"Changelog": "Endringslog",
"Close": "Lukk",
"Comment, when used at the start of a line": "Kommentar, når det vert brukt i starten av ei linje.",
"Compression is recommended in most setups.": "Komprimering er tilrådd i dei fleste høve.",
"Connection Error": "Tilkoplingsfeil",
"Copied from elsewhere": "Copied from elsewhere",
"Copied from original": "Copied from original",
"Copied from elsewhere": "Kopiert frå ein annan stad",
"Copied from original": "Kopiert frå original",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Copyright © 2014 Jakob Borg og følgjande Bidragsytarar:",
"Delete": "Slett",
"Device ID": "Eining ID",
"Device Identification": "Einingskjennemerke",
"Device Name": "Namn På Eining",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
"Devices": "Devices",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Eining {{device}} ({{address}}) vil kople seg til. Legg til eining?",
"Devices": "Einingar",
"Disconnected": "Fråkopla",
"Documentation": "Dokumentasjon",
"Download Rate": "Nedlastingsrate",
"Downloaded": "Downloaded",
"Downloading": "Downloading",
"Downloaded": "Lasta ned",
"Downloading": "Lastar ned",
"Edit": "Rediger",
"Edit Device": "Rediger Eining",
"Edit Folder": "Rediger Mappe",
@@ -47,7 +47,7 @@
"Folder ID": "Mappe ID",
"Folder Master": "Styrande Mappe",
"Folder Path": "Mappeplassering",
"Folders": "Folders",
"Folders": "Mapper",
"GUI Authentication Password": "GUI Passord",
"GUI Authentication User": "GUI Brukar",
"GUI Listen Addresses": "GUI Lytteadresse",
@@ -56,25 +56,25 @@
"Global Discovery Server": "Global Søkjemotor",
"Global State": "Global Tilstand",
"Idle": "Pause",
"Ignore": "Ignore",
"Ignore": "Ignorer",
"Ignore Patterns": "Utelatingsmønster",
"Ignore Permissions": "Ignorer Tilgangsbit",
"Incoming Rate Limit (KiB/s)": "Innkomande Hastigheitsgrense (KiB/s)",
"Introducer": "Introduktør",
"Inversion of the given condition (i.e. do not exclude)": "Invers av den gitte tilstanden (t.d. ikkje ekskluder)",
"Keep Versions": "Behald Versjonar",
"Last File Received": "Last File Received",
"Last File Synced": "Last File Synced",
"Last File Received": "Sist Mottatte Fil",
"Last File Synced": "Sist Synkroniserte Fil",
"Last seen": "Sist sett",
"Later": "Later",
"Later": "Seinare",
"Latest Release": "Nyaste Versjon",
"Local Discovery": "Lokal Søking",
"Local State": "Lokal Tilstand",
"Maximum Age": "Maksimal Levetid",
"Multi level wildcard (matches multiple directory levels)": "Multinivåsøk (søkjer på fleire mappenivå)",
"Never": "Aldri",
"New Device": "New Device",
"New Folder": "New Folder",
"New Device": "Ny Eining",
"New Folder": "Ny Mappe",
"No": "Nei",
"No File Versioning": "Ingen Versjonskontroll",
"Notice": "Merknad",
@@ -82,7 +82,7 @@
"Offline": "Fråkopla",
"Online": "Tilkopla",
"Out Of Sync": "Ikkje Synkronisert",
"Out of Sync Items": "Out of Sync Items",
"Out of Sync Items": "Ikkje Synkroniserte Element",
"Outgoing Rate Limit (KiB/s)": "Utgåande Hastigheitsgrense (KiB/s)",
"Override Changes": "Overstyr Endringar",
"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": "Plasseringa av mappa på datamaskinen. Vert oppretta om ho ikkje finst. Krøllstrekteiknet (~) kan brukast som forkorting for",
@@ -97,17 +97,17 @@
"Restart": "Omstart",
"Restart Needed": "Omstart Trengs",
"Restarting": "Startar På Ny",
"Reused": "Reused",
"Reused": "Gjenbrukt",
"Save": "Lagre",
"Scanning": "Skannar",
"Select the devices to share this folder with.": "Vel einingane du vil dela denne mappa med.",
"Select the folders to share with this device.": "Select the folders to share with this device.",
"Select the folders to share with this device.": "Vel mappene du vil dela med denne eininga.",
"Settings": "Innstillingar",
"Share": "Share",
"Share Folder": "Share Folder",
"Share Folders With Device": "Share Folders With Device",
"Share": "Del",
"Share Folder": "Del Mappe",
"Share Folders With Device": "Del Mapper Med Eining",
"Share With Devices": "Del Med Einingar",
"Share this folder?": "Share this folder?",
"Share this folder?": "Del denne mappa?",
"Shared With": "Delt Med",
"Short identifier for the folder. Must be the same on all cluster devices.": "Kort kjennemerke på mappa. Må vera det same på alle einingar i ei gruppe.",
"Show ID": "Vis ID",
@@ -121,7 +121,7 @@
"Staggered File Versioning": "Forskyvd Versjonskontroll",
"Start Browser": "Start Nettlesar",
"Stopped": "Stoppa",
"Support": "Support",
"Support": "Brukarstøtte",
"Support / Forum": "Brukarstøtte / Forum",
"Sync Protocol Listen Addresses": "Lytteadresse For Synkroniseringsprotokoll",
"Synchronization": "Synkronisering",
@@ -150,8 +150,8 @@
"The rescan interval must be a non-negative number of seconds.": "Talet på sekund i skanneintervallet kan ikkje vera negativt.",
"The rescan interval must be at least 5 seconds.": "Skanneintervallet må vera minst 5 sekund.",
"Unknown": "Ukjent",
"Unshared": "Unshared",
"Unused": "Unused",
"Unshared": "Ikkje delt",
"Unused": "Ubrukt",
"Up to Date": "Oppdatert",
"Upgrade To {%version%}": "Oppgrader Til {{version}}",
"Upgrading": "Oppgraderer",
@@ -167,5 +167,5 @@
"You must keep at least one version.": "Du må behalda minst ein versjon.",
"full documentation": "all dokumentasjon",
"items": "element",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\"."
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} vil dela mappa \"{{folder}}\"."
}

View File

@@ -1,10 +1,10 @@
{
"API Key": "Klucz API",
"About": "O Syncthing",
"Add": "Add",
"Add": "Dodaj",
"Add Device": "Dodaj urządzenie",
"Add Folder": "Dodaj folder",
"Add new folder?": "Add new folder?",
"Add new folder?": "Dodać nowy folder?",
"Address": "Adres",
"Addresses": "Adresy",
"Allow Anonymous Usage Reporting?": "Zezwalaj na anonimowe statystyki użycia",
@@ -13,7 +13,7 @@
"Automatic upgrades": "Automatyczne aktualizacje",
"Bugs": "Błędy",
"CPU Utilization": "Użycie CPU",
"Changelog": "Changelog",
"Changelog": "Historia zmian",
"Close": "Zamknij",
"Comment, when used at the start of a line": "Komentarz, jeżeli użyty na początku linii",
"Compression is recommended in most setups.": "Kompresja jest zalecana w większości przypadków",
@@ -26,12 +26,12 @@
"Device Identification": "Identyfikator urządzenia",
"Device Name": "Nazwa urządzenia",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
"Devices": "Devices",
"Devices": "Urządzenia",
"Disconnected": "Rozłączony",
"Documentation": "Dokumentacja",
"Download Rate": "Prędkość pobierania",
"Downloaded": "Downloaded",
"Downloading": "Downloading",
"Downloaded": "Pobrane",
"Downloading": "Pobieranie",
"Edit": "Edytuj",
"Edit Device": "Edytuj urządzenie",
"Edit Folder": "Edytuj folder",
@@ -47,7 +47,7 @@
"Folder ID": "ID folderu",
"Folder Master": "Główny folder",
"Folder Path": "Ścieżka folderu",
"Folders": "Folders",
"Folders": "Foldery",
"GUI Authentication Password": "Hasło",
"GUI Authentication User": "Użytkownik",
"GUI Listen Addresses": "Adres nasłuchiwania",
@@ -63,8 +63,8 @@
"Introducer": "Wprowadzający",
"Inversion of the given condition (i.e. do not exclude)": "Odwrócenie podanego wzorca (np. nie wykluczaj)",
"Keep Versions": "Zachowuj wersje",
"Last File Received": "Last File Received",
"Last File Synced": "Last File Synced",
"Last File Received": "Ostatni otrzymany plik",
"Last File Synced": "Ostatni zsynchronizowany plik",
"Last seen": "Ostatnio widziany",
"Later": "Later",
"Latest Release": "Najnowsza wersja",
@@ -73,8 +73,8 @@
"Maximum Age": "Maksymalny wiek",
"Multi level wildcard (matches multiple directory levels)": "Wieloznaczność na poziomie katalogów i plików (uwzględnia nazwy folderów i plików)",
"Never": "Nigdy",
"New Device": "New Device",
"New Folder": "New Folder",
"New Device": "Nowe urządzenie",
"New Folder": "Nowy folder",
"No": "Nie",
"No File Versioning": "Bez wersjonowania pliku",
"Notice": "Wskazówka",
@@ -101,7 +101,7 @@
"Save": "Zapisz",
"Scanning": "Skanowanie",
"Select the devices to share this folder with.": "Wybierz urządzenie, któremu udostępnić folder.",
"Select the folders to share with this device.": "Select the folders to share with this device.",
"Select the folders to share with this device.": "Wybierz foldery do współdzielenia z tym urządzeniem.",
"Settings": "Ustawienia",
"Share": "Share",
"Share Folder": "Share Folder",
@@ -147,11 +147,11 @@
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Maksymalny czas zachowania wersji (w dniach, ustaw 0 aby zachować na zawsze)",
"The number of old versions to keep, per file.": "Liczba wersji pliku do zachowania.",
"The number of versions must be a number and cannot be blank.": "Liczba wersji musi być liczbą i nie może być pusta.",
"The rescan interval must be a non-negative number of seconds.": "The rescan interval must be a non-negative number of seconds.",
"The rescan interval must be a non-negative number of seconds.": "Interwał skanowania musi być niezerową liczbą sekund.",
"The rescan interval must be at least 5 seconds.": "Interwał skanowania musi wynosić co najmniej 5 sekund.",
"Unknown": "Nieznany",
"Unshared": "Unshared",
"Unused": "Unused",
"Unused": "Nieużywane",
"Up to Date": "Aktualny",
"Upgrade To {%version%}": "Aktualizuj do {{version}}",
"Upgrading": "Aktualizowanie",

View File

@@ -82,7 +82,7 @@
"Offline": "Desconectado",
"Online": "Conectado",
"Out Of Sync": "Não sincronizado",
"Out of Sync Items": "Itens dessincronizados",
"Out of Sync Items": "Itens não sincronizados",
"Outgoing Rate Limit (KiB/s)": "Limite da velocidade de envio (KiB/s)",
"Override Changes": "Sobrepor alterações",
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Caminho para a pasta no computador local. Será criada, caso não exista. O caractere (~) pode ser utilizado como atalho para",

View File

@@ -1,10 +1,10 @@
{
"API Key": "Ключ API",
"About": "О программе",
"Add": "Add",
"Add": "Добавить",
"Add Device": "Добавить устройство",
"Add Folder": "Добавить папку",
"Add new folder?": "Add new folder?",
"Add new folder?": "Добавить новую папку?",
"Address": "Адрес",
"Addresses": "Адреса",
"Allow Anonymous Usage Reporting?": "Разрешить сбор анонимной статистики использования?",
@@ -13,7 +13,7 @@
"Automatic upgrades": "Автообновление",
"Bugs": "Ошибки",
"CPU Utilization": "Загрузка ЦПУ",
"Changelog": "Changelog",
"Changelog": "Журнал изменений",
"Close": "Закрыть",
"Comment, when used at the start of a line": "Комментарий, если используется в начале строки",
"Compression is recommended in most setups.": "Сжатие рекомендуется в большинстве случаев.",
@@ -25,8 +25,8 @@
"Device ID": "ID устройства",
"Device Identification": "Идентификация устройства",
"Device Name": "Имя устройства",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
"Devices": "Devices",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Устройство {{device}} ({{address}}) хочет подключиться. Добавить новое устройство?",
"Devices": "Устройства",
"Disconnected": "Нет соединения",
"Documentation": "Документация",
"Download Rate": "Скорость загрузки",
@@ -56,25 +56,25 @@
"Global Discovery Server": "Сервер глобального обнаружения",
"Global State": "Глобальное состояние",
"Idle": "Бездействует",
"Ignore": "Ignore",
"Ignore": "Игнорировать",
"Ignore Patterns": "Шаблоны игнорирования",
"Ignore Permissions": "Игнорировать файловые права доступа",
"Incoming Rate Limit (KiB/s)": "Ограничение входящего потока (Кбит/сек)",
"Introducer": "Рекомендатель",
"Inversion of the given condition (i.e. do not exclude)": "Инвертировать текущее условие (например, исключить)",
"Keep Versions": "Количество хранимых версий",
"Last File Received": "Last File Received",
"Last File Received": "Последний полученный файл",
"Last File Synced": "Последний синхронизированный файл",
"Last seen": "Был доступен",
"Later": "Later",
"Later": "Потом",
"Latest Release": "Последняя версия",
"Local Discovery": "Локальное обнаружение",
"Local State": "Локальное состояние",
"Maximum Age": "Максимальный срок",
"Multi level wildcard (matches multiple directory levels)": "Многоуровневая маска (поиск совпадений во всех подпапках)",
"Never": "Никогда",
"New Device": "New Device",
"New Folder": "New Folder",
"New Device": "Новое устройство",
"New Folder": "Новая напка",
"No": "Нет",
"No File Versioning": "Без управления версиями файлов",
"Notice": "Внимание",
@@ -82,7 +82,7 @@
"Offline": "Оффлайн",
"Online": "Онлайн",
"Out Of Sync": "Не синхронизировано",
"Out of Sync Items": "Out of Sync Items",
"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": "Путь к папке на локальном компьютере. Если её не существует, то она будет создана. Тильда (~) может использоваться как сокращение для",
@@ -103,25 +103,25 @@
"Select the devices to share this folder with.": "Выберите устройства, для которых будет доступна эта папка.",
"Select the folders to share with this device.": "Выберите папку для предоставления доступа данному устройству",
"Settings": "Настройки",
"Share": "Share",
"Share Folder": "Share Folder",
"Share": "Предоставить доступ",
"Share Folder": "Предоставить доступ к папке",
"Share Folders With Device": "Предоставить доступ устройству к папкам",
"Share With Devices": "Предоставить доступ устройствам",
"Share this folder?": "Share this folder?",
"Share this folder?": "Предоставить доступ к этой папке?",
"Shared With": "Доступ предоставлен",
"Short identifier for the folder. Must be the same on all cluster devices.": "Короткий идентификатор папки. Должен быть одинаковым на всех устройствах кластера.",
"Show ID": "Показать ID",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Отображается вместо ID устройства в статусе группы. Будет разослан другим устройствам в качестве имени по умолчанию.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Отображается вместо ID устройства в статусе группы. Если поле не заполнено, то будет установлено имя, передаваемое этим устройством.",
"Shutdown": "Выключить",
"Shutdown Complete": "Shutdown Complete",
"Shutdown Complete": "Выключено",
"Simple File Versioning": "Простое управление версиями файлов",
"Single level wildcard (matches within a directory only)": "Одноуровневая маска (поиск совпадений только внутри папки)",
"Source Code": "Исходный код",
"Staggered File Versioning": "Ступенчатое управление версиями файлов",
"Start Browser": "Открыть браузер",
"Stopped": "Остановлено",
"Support": "Support",
"Support": "Поддержка",
"Support / Forum": "Поддержка / Форум",
"Sync Protocol Listen Addresses": "Адрес протокола синхронизации",
"Synchronization": "Синхронизация",
@@ -131,7 +131,7 @@
"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 seems to be experiencing a problem processing your request. Please reload your browser or restart Syncthing if the problem persists.",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Похоже, у Syncthing возникла проблема с обработкой запроса. Обновите страницу в обозревателе или перезапустите Syncthing, если это не поможет.",
"The aggregated statistics are publicly available at {%url%}.": "Суммарная статистика общедоступна на {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Конфигурация была сохранена но не активирована. Для активации новой конфигурации необходимо рестартовать Syncthing.",
"The device ID cannot be blank.": "ID устройства не может быть пустым.",

View File

@@ -9,7 +9,7 @@
"Addresses": "Adresser",
"Allow Anonymous Usage Reporting?": "Tillåt anonym användarstatistik?",
"Anonymous Usage Reporting": "Anonym användarstatistik",
"Any devices configured on an introducer device will be added to this device as well.": "Any devices configured on an introducer device will be added to this device as well.",
"Any devices configured on an introducer device will be added to this device as well.": "Enheter konfigurerade på en introduktörsenhet kommer också att läggas till den här enheten.",
"Automatic upgrades": "Automatisk uppgradering",
"Bugs": "Buggar",
"CPU Utilization": "CPU-användning",
@@ -20,13 +20,13 @@
"Connection Error": "Anslutningsproblem",
"Copied from elsewhere": "Kopierat utifrån",
"Copied from original": "Oförändrat",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Copyright © 2014 Jakob Borg och de följande medarbetarna:",
"Copyright © 2014 Jakob Borg and the following Contributors:": "Copyright © 2014 Jakob Borg och följande medarbetare:",
"Delete": "Radera",
"Device ID": "Enhets-ID",
"Device Identification": "Enhetsidentifikation",
"Device Name": "Enhetsnamn",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Device {{device}} ({{address}}) wants to connect. Add new device?",
"Devices": "Devices",
"Device {%device%} ({%address%}) wants to connect. Add new device?": "Enheten {{device}} ({{address}}) vill ansluta. Lägg till ny enhet?",
"Devices": "Enheter",
"Disconnected": "Ej ansluten",
"Documentation": "Dokumentation",
"Download Rate": "Nedladdningshastighet",
@@ -57,14 +57,14 @@
"Global State": "Global status",
"Idle": "Vilande",
"Ignore": "Ignorera",
"Ignore Patterns": "Filmönster",
"Ignore Patterns": "Ignorerade filmönster",
"Ignore Permissions": "Ignorera filrättigheter",
"Incoming Rate Limit (KiB/s)": "Max nedladdningshastighet (KiB/s)",
"Introducer": "Introducer",
"Introducer": "introduktör",
"Inversion of the given condition (i.e. do not exclude)": "Vänder på villkoret, d.v.s. exkluderar inte.",
"Keep Versions": "Behåll versioner",
"Last File Received": "Last File Received",
"Last File Synced": "Senast uppdaterad fil",
"Last File Received": "Senast Mottagna Fil",
"Last File Synced": "Senast uppdaterade fil",
"Last seen": "Senast online",
"Later": "Senare",
"Latest Release": "Senaste version",
@@ -77,16 +77,16 @@
"New Folder": "Ny katalog",
"No": "Nej",
"No File Versioning": "Ingen versionshantering",
"Notice": "OBS",
"Notice": "Observera",
"OK": "OK",
"Offline": "Ej tillgänglig",
"Online": "Tillgänglig",
"Out Of Sync": "Ur synk",
"Out of Sync Items": "Out of Sync Items",
"Out Of Sync": "Osynkad",
"Out of Sync Items": "Osynkade poster",
"Outgoing Rate Limit (KiB/s)": "Max uppladdningshastighet (KiB/s)",
"Override Changes": "Skriv över ändringar",
"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": "Sökväg till katalogen på din dator. Kommer att skapas om det inte finns. Tecknet tilde (~) kan användas som en genväg för",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Sökväg där versioner sparas (lämna blankt för att använda .stversions i den ordinarie katalogen).",
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Sökväg där versioner sparas (lämna tomt för att använda .stversions i den ordinarie katalogen).",
"Please wait": "Var god vänta",
"Preview": "Förhandsgranska",
"Preview Usage Report": "Förhandsgranska statistik",
@@ -101,20 +101,20 @@
"Save": "Spara",
"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älja kataloger att dela med den här enheten",
"Select the folders to share with this device.": "Välj kataloger att dela med den här enheten.",
"Settings": "Inställningar",
"Share": "Dela",
"Share Folder": "Dela katalog",
"Share Folders With Device": "Dela kataloger med enhet",
"Share With Devices": "Dela med enheter",
"Share this folder?": "Dela den här katalogen?",
"Shared With": "Delat med",
"Short identifier for the folder. Must be the same on all cluster devices.": "Kort identifieringssträng för katalogen. Måste vara samma på alla enheter i klustern.",
"Shared With": "Delad med",
"Short identifier for the folder. Must be the same on all cluster devices.": "Kort identifieringssträng för katalogen. Måste vara samma på alla enheter i klustret.",
"Show ID": "Visa ID",
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Visas i stället för enhets-ID. Skickas till andra enheter som namn på denna enhet.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Visas i stället för enhets-ID. Sätts till namnet på den andra enheten vid första anslutning om det lämnas blankt.",
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Visas i stället för enhets-ID. Sätts till namnet på den andra enheten vid första anslutning om det lämnas tomt.",
"Shutdown": "Stäng av",
"Shutdown Complete": "Shutdown Complete",
"Shutdown Complete": "Avstängning klar",
"Simple File Versioning": "Enkel versionshantering",
"Single level wildcard (matches within a directory only)": "Jokertecken som representerar noll eller fler godtyckliga tecken i ett filnamn.",
"Source Code": "Källkod",
@@ -127,30 +127,30 @@
"Synchronization": "Synkronisering",
"Syncing": "Synkroniserar",
"Syncthing has been shut down.": "Syncthing har stängts ner.",
"Syncthing includes the following software or portions thereof:": "Syncthing innehåller de följande mjukvarupaketen eller delar därav:",
"Syncthing includes the following software or portions thereof:": "Syncthing innehåller följande mjukvarupaket eller delar av dem:",
"Syncthing is restarting.": "Syncthing startar om.",
"Syncthing is upgrading.": "Syncthing uppgraderas.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing verkar avstängd, eller så är där ett problem med din Internetanslutning. Försöker igen...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing seems to be experiencing a problem processing your request. Please reload your browser or restart Syncthing if the problem persists.",
"The aggregated statistics are publicly available at {%url%}.": "Aggregerad statistik finns publikt tillgänglig på {{url}}.",
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing verkar avstängd, eller finns det problem med din Internetanslutning. Försöker igen...",
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing verkar ha problem med att genomföra din förfrågan. Ladda om sidan i din webbläsare eller starta om Syncthing ifall problemet kvarstår.",
"The aggregated statistics are publicly available at {%url%}.": "Sammanställd statistik finns publikt tillgänglig på {{url}}.",
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Konfigurationen har sparats men inte aktiverats. Syncthing måste startas om för att aktivera den nya konfigurationen.",
"The device ID cannot be blank.": "Enhets-ID kan inte vara blankt.",
"The device ID cannot be blank.": "Enhets-ID kan inte vara tomt.",
"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).": "Enhets-ID som behövs här kan du hitta i \"Redigera > Visa ID\"-dialogen på den andra enheten. Mellanrum och bindestreck är valfria (ignoreras).",
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Den krypterade användarstatistiken skickas dagligen. Den används för att spåra vanliga plattformar, katalogstorlekar och versioner. Om datan som rapporteras ändras så kommer du att bli tillfrågad igen.",
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "Det inmatade enhets-ID:t verkar inte korrekt. Det ska vara en 52 eller 56 teckens sträng bestående av siffror och bokstäver, eventuellt med mellanrum och bindestreck.",
"The folder ID cannot be blank.": "Ange ett enhets-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.": "Katalog-ID:t måste vara en kort sträng (64 tecken eller mindre), bestående av endast bokstäver, siffror, punkt (.), bindestreck (-) och understreck (_).",
"The folder ID must be unique.": "Enhets-ID måste vara unikt.",
"The folder ID must be unique.": "Katalog-ID:t måste vara unikt.",
"The folder path cannot be blank.": "Ange en sökväg.",
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "De följande intervallen används: varje 30 sekunder under den första timmen; varje timme under den första dagen; varje dag för de första 30 dagarna; varje vecka tills den maximala åldersgränsen uppnås.",
"The maximum age must be a number and cannot be blank.": "Åldersgränsen måste vara ett tal och kan inte lämnas blankt.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Den längsta tid att behålla en version (i dagar, sätt till 0 för att behålla versioner för evigt).",
"The maximum age must be a number and cannot be blank.": "Åldersgränsen måste vara ett tal och kan inte lämnas tomt.",
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "Den längsta tiden att behålla en version (i dagar, sätt till 0 för att behålla versioner för evigt).",
"The number of old versions to keep, per file.": "Antalet gamla versioner som ska behållas, per fil.",
"The number of versions must be a number and cannot be blank.": "Antalet versioner måste vara ett nummer och kan inte lämnas blankt.",
"The number of versions must be a number and cannot be blank.": "Antalet versioner måste vara ett nummer och kan inte lämnas tomt.",
"The rescan interval must be a non-negative number of seconds.": "Förnyelseintervallet måste vara ett positivt antal sekunder",
"The rescan interval must be at least 5 seconds.": "Uppdateringsintervallet måste vara minst 5 sekunder.",
"Unknown": "Okänt",
"Unshared": "Odelat",
"Unshared": "Inte delad",
"Unused": "Oanvänd",
"Up to Date": "Helt uppdaterad",
"Upgrade To {%version%}": "Uppgradera till {{version}}",
@@ -167,5 +167,5 @@
"You must keep at least one version.": "Du måste behålla åtminstone en version.",
"full documentation": "fullständig dokumentation",
"items": "poster",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} wants to share folder \"{{folder}}\"."
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} vill dela katalogen \"{{folder}}\"."
}

View File

@@ -64,12 +64,12 @@
<div class="container">
<!-- First row, only shown if necessary; Restart warning -->
<!-- Panel: Restart Needed -->
<div ng-if="!configInSync" class="row">
<div class="col-md-12">
<div class="panel panel-warning">
<div class="panel-heading"><h3 translate class="panel-title">Restart Needed</h3></div>
<div class="panel-heading"><h3 class="panel-title"><span class="glyphicon glyphicon-exclamation-sign"></span><span translate>Restart Needed</span></h3></div>
<div class="panel-body">
<p translate>The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.</p>
</div>
@@ -81,6 +81,90 @@
</div>
</div>
<!-- Panel: New Device -->
<div ng-repeat="(device, event) in deviceRejections" class="row">
<div class="col-md-12">
<div class="panel panel-warning">
<div class="panel-heading">
<h3 class="panel-title">
<identicon data-value="device"></identicon>&emsp;<span translate>New Device</span>
</h3>
</div>
<div class="panel-body">
<p>
<small>{{ event.time | date:"H:mm:ss" }}:</small>
<span translate translate-value-device="{{ device }}" translate-value-address="{{ event.data.address }}">
Device {%device%} ({%address%}) wants to connect. Add new device?
<span>
</p>
</div>
<div class="panel-footer clearfix">
<div class="pull-right">
<button class="btn btn-sm btn-success" ng-click="addNewDeviceID(device)"><span class="glyphicon glyphicon-ok"></span>&emsp;<span translate>Add</span></button>
<button class="btn btn-sm btn-danger" ng-click="ignoreRejectedDevice(device)"><span class="glyphicon glyphicon-remove"></span>&emsp;<span translate>Ignore</span></button>
<button class="btn btn-sm btn-default" ng-click="dismissDeviceRejection(device)"><span class="glyphicon glyphicon-time"></span>&emsp;<span translate>Later</span></button>
</div>
</div>
</div>
</div>
</div>
<!-- Panel: New Folder -->
<div ng-repeat="(key, event) in folderRejections" class="row reject">
<div class="col-md-12">
<div class="panel panel-warning">
<div class="panel-heading">
<h3 class="panel-title"><span class="glyphicon glyphicon-hdd"></span>
<span translate ng-if="!folders[event.data.folder]">New Folder</span>
<span translate ng-if="folders[event.data.folder]">Share Folder</span>
</h3>
</div>
<div class="panel-body">
<p>
<small>{{ event.time | date:"H:mm:ss" }}:</small>
<span translate translate-value-device="{{ deviceName(findDevice(event.data.device)) }}" translate-value-folder="{{ event.data.folder }}">
{%device%} wants to share folder "{%folder%}".
</span>
<span translate ng-if="folders[event.data.folder]">Share this folder?</span>
<span translate ng-if="!folders[event.data.folder]">Add new folder?</span>
</p>
</div>
<div class="panel-footer clearfix">
<div class="pull-right">
<button class="btn btn-sm btn-success" ng-click="addFolderAndShare(event.data.folder, event.data.device)" ng-if="!folders[event.data.folder]">
<span class="glyphicon glyphicon-ok"></span>&emsp;<span translate>Add</span>
</button>
<button class="btn btn-sm btn-success" ng-click="shareFolderWithDevice(event.data.folder, event.data.device)" ng-if="folders[event.data.folder]">
<span class="glyphicon glyphicon-ok"></span>&emsp;<span translate>Share</span>
</button>
<button class="btn btn-sm btn-default" ng-click="dismissFolderRejection(event.data.folder, event.data.device)">
<span class="glyphicon glyphicon-time"></span>&emsp;<span translate>Later</span>
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Panel: Notice -->
<div ng-if="errorList().length > 0" class="row">
<div class="col-md-12">
<div class="panel panel-warning">
<div class="panel-heading"><h3 class="panel-title"><span class="glyphicon glyphicon-exclamation-sign"></span><span translate>Notice</span></h3></div>
<div class="panel-body">
<p ng-repeat="err in errorList()"><small>{{err.Time | date:"H:mm:ss"}}:</small> {{friendlyDevices(err.Error)}}</p>
</div>
<div class="panel-footer">
<button type="button" class="pull-right btn btn-sm btn-default" ng-click="clearErrors()"><span class="glyphicon glyphicon-ok"></span>&emsp;<span translate>OK</span></button>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
<!-- First regular row -->
<div class="row">
@@ -140,6 +224,12 @@
<span translate>Yes</span>
</td>
</tr>
<tr ng-if="model[folder.ID].ignorePatterns">
<th><span class="glyphicon glyphicon-minus"></span>&emsp;<span translate>Ignore Patterns</span></th>
<td class="text-right">
<span translate>Yes</span>
</td>
</tr>
<tr ng-if="folder.IgnorePerms">
<th><span class="glyphicon glyphicon-unchecked"></span>&emsp;<span translate>Ignore Permissions</span></th>
<td class="text-right">
@@ -150,6 +240,13 @@
<th><span class="glyphicon glyphicon-refresh"></span>&emsp;<span translate>Rescan Interval</span></th>
<td class="text-right">{{folder.RescanIntervalS}} s</td>
</tr>
<tr ng-if="folder.Versioning.Type">
<th><span class="glyphicon glyphicon-tags"></span>&emsp;<span translate>File Versioning</span></th>
<td class="text-right" ng-switch="folder.Versioning.Type">
<span ng-switch-when="staggered" translate>Staggered File Versioning</span>
<span ng-switch-when="simple" translate>Simple File Versioning</span>
</td>
</tr>
<tr>
<th><span class="glyphicon glyphicon-share-alt"></span>&emsp;<span translate>Shared With</span></th>
<td class="text-right">{{sharesFolder(folder)}}</td>
@@ -166,7 +263,7 @@
</table>
</div>
<div class="panel-footer">
<button class="btn btn-sm btn-danger" ng-if="folder.ReadOnly && model[folder.ID].needFiles > 0" ng-click="override(folder.ID)" href=""><span class="glyphicon glyphicon-upload"></span>&emsp;<span translate>Override Changes</span></button>
<button class="btn btn-sm btn-danger pull-left" ng-if="folder.ReadOnly && model[folder.ID].needFiles > 0" ng-click="override(folder.ID)" href=""><span class="glyphicon glyphicon-upload"></span>&emsp;<span translate>Override Changes</span></button>
<span class="pull-right">
<button class="btn btn-sm btn-default" href="" ng-show="folderStatus(folder) == 'idle'" ng-click="rescanFolder(folder.ID)"><span class="glyphicon glyphicon-refresh"></span>&emsp;<span translate>Rescan</span></button>
<button class="btn btn-sm btn-default" href="" ng-click="editFolder(folder)"><span class="glyphicon glyphicon-pencil"></span>&emsp;<span translate>Edit</span></button>
@@ -310,90 +407,6 @@
</div>
</div> <!-- /row -->
<!-- Device Rejections -->
<div ng-repeat="(device, event) in deviceRejections" class="row">
<div class="col-md-12">
<div class="panel panel-warning">
<div class="panel-heading">
<h3 class="panel-title">
<identicon data-value="device"></identicon>&emsp;<span translate>New Device</span>
</h3>
</div>
<div class="panel-body">
<p>
<small>{{ event.time | date:"H:mm:ss" }}:</small>
<span translate translate-value-device="{{ device }}" translate-value-address="{{ event.data.address }}">
Device {%device%} ({%address%}) wants to connect. Add new device?
<span>
</p>
</div>
<div class="panel-footer clearfix">
<div class="pull-right">
<button class="btn btn-sm btn-success" ng-click="addNewDeviceID(device)"><span class="glyphicon glyphicon-ok"></span>&emsp;<span translate>Add</span></button>
<button class="btn btn-sm btn-danger" ng-click="ignoreRejectedDevice(device)"><span class="glyphicon glyphicon-remove"></span>&emsp;<span translate>Ignore</span></button>
<button class="btn btn-sm btn-default" ng-click="dismissDeviceRejection(device)"><span class="glyphicon glyphicon-time"></span>&emsp;<span translate>Later</span></button>
</div>
</div>
</div>
</div>
</div>
<!-- Folder Rejections -->
<div ng-repeat="(key, event) in folderRejections" class="row reject">
<div class="col-md-12">
<div class="panel panel-warning">
<div class="panel-heading">
<h3 class="panel-title">
<span translate ng-if="!folders[event.data.folder]">New Folder</span>
<span translate ng-if="folders[event.data.folder]">Share Folder</span>
</h3>
</div>
<div class="panel-body">
<p>
<small>{{ event.time | date:"H:mm:ss" }}:</small>
<span translate translate-value-device="{{ deviceName(findDevice(event.data.device)) }}" translate-value-folder="{{ event.data.folder }}">
{%device%} wants to share folder "{%folder%}".
</span>
<span translate ng-if="folders[event.data.folder]">Share this folder?</span>
<span translate ng-if="!folders[event.data.folder]">Add new folder?</span>
</p>
</div>
<div class="panel-footer clearfix">
<div class="pull-right">
<button class="btn btn-sm btn-success" ng-click="addFolderAndShare(event.data.folder, event.data.device)" ng-if="!folders[event.data.folder]">
<span class="glyphicon glyphicon-ok"></span>&emsp;<span translate>Add</span>
</button>
<button class="btn btn-sm btn-success" ng-click="shareFolderWithDevice(event.data.folder, event.data.device)" ng-if="folders[event.data.folder]">
<span class="glyphicon glyphicon-ok"></span>&emsp;<span translate>Share</span>
</button>
<button class="btn btn-sm btn-default" ng-click="dismissFolderRejection(event.data.folder, event.data.device)">
<span class="glyphicon glyphicon-time"></span>&emsp;<span translate>Later</span>
</button>
</div>
</div>
</div>
</div>
</div>
<!-- Errors -->
<div ng-if="errorList().length > 0" class="row">
<div class="col-md-12">
<div class="panel panel-warning">
<div class="panel-heading"><h3 class="panel-title"><span translate>Notice</span></h3></div>
<div class="panel-body">
<p ng-repeat="err in errorList()"><small>{{err.Time | date:"H:mm:ss"}}:</small> {{friendlyDevices(err.Error)}}</p>
</div>
<div class="panel-footer">
<button type="button" class="pull-right btn btn-sm btn-default" ng-click="clearErrors()"><span class="glyphicon glyphicon-ok"></span>&emsp;<span translate>OK</span></button>
<div class="clearfix"></div>
</div>
</div>
</div>
</div>
</div> <!-- /container -->
<!-- Bottom bar -->
@@ -832,7 +845,7 @@
</div>
<div class="modal-body">
<p translate>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.</p>
<p translate translate-value-url="https://data.syncthing.net">The aggregated statistics are publicly available at {%url%}.</p>
<p translate translate-value-url="<a href=&quot;https://data.syncthing.net&quot; target=&quot;_blank&quot;>https://data.syncthing.net</a>">The aggregated statistics are publicly available at {%url%}.</p>
<button translate type="button" class="btn btn-default btn-sm" ng-show="!reportPreview" ng-click="showReportPreview()">Preview Usage Report</button>
<pre ng-if="reportPreview"><small>{{reportData | json}}</small></pre>
</div>
@@ -854,7 +867,7 @@
</div>
<div class="modal-body">
<p translate>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.</p>
<p translate translate-value-url="https://data.syncthing.net">The aggregated statistics are publicly available at {%url%}.</p>
<p translate translate-value-url="<a href=&quot;https://data.syncthing.net&quot; target=&quot;_blank&quot;>https://data.syncthing.net</a>">The aggregated statistics are publicly available at {%url%}.</p>
<pre><small>{{reportData | json}}</small></pre>
</div>
<div class="modal-footer">
@@ -899,9 +912,8 @@
</tr>
<tr ng-repeat="f in needed.queued" ng-init="a = needAction(f)">
<td class="small-data"><span class="glyphicon glyphicon-{{needIcons[a]}}"></span> {{needActions[a]}}</td>
<td title="{{f.Name}}">{{f.Name | basename}}</td>
<td><a href="" ng-if="$index != 0" ng-click="bumpFile(neededFolder, f.Name)" title="{{'Move to top of queue' | translate}}"><span class="glyphicon glyphicon-eject"></span></a><span ng-if="$index != 0">&ensp;</span><span title="{{f.Name}}">{{f.Name | basename}}</span></td>
<td class="text-right small-data">
<span ng-if="$index != 0" class="glyphicon glyphicon-chevron-up" ng-click="bumpFile(neededFolder, f.Name)"></span>
<span ng-if="f.Size > 0">{{f.Size | binary}}B</span>
</td>
</tr>
@@ -947,6 +959,7 @@
<li>James Patterson</li>
<li>Jens Diemer</li>
<li>Jochen Voss</li>
<li>Karol Różycki</li>
<li>Lode Hoste</li>
<li>Marcin Dziadus</li>
<li>Michael Jephcote</li>
@@ -956,6 +969,7 @@
<li>Phill Luby</li>
<li>Piotr Bejda</li>
<li>Ryan Sullivan</li>
<li>Stefan Tatschner</li>
<li>Tim Abell</li>
<li>Tomas Cerveny</li>
<li>Tully Robinson</li>

View File

File diff suppressed because one or more lines are too long

View File

@@ -26,6 +26,7 @@ import (
"reflect"
"sort"
"strconv"
"strings"
"github.com/calmh/logger"
"github.com/syncthing/protocol"
@@ -261,6 +262,10 @@ func (cfg *Configuration) prepare(myID protocol.DeviceID) {
continue
}
if !strings.HasSuffix(folder.Path, string(filepath.Separator)) {
folder.Path += string(filepath.Separator)
}
if folder.ID == "" {
folder.ID = "default"
}

View File

@@ -19,6 +19,7 @@ import (
"fmt"
"os"
"reflect"
"runtime"
"testing"
"github.com/syncthing/protocol"
@@ -261,6 +262,23 @@ func TestVersioningConfig(t *testing.T) {
}
}
func TestIssue1262(t *testing.T) {
cfg, err := Load("testdata/issue-1262.xml", device4)
if err != nil {
t.Error(err)
}
actual := cfg.Folders()["test"].Path
expected := "e:/"
if runtime.GOOS == "windows" {
expected = `e:\`
}
if actual != expected {
t.Errorf("%q != %q", actual, expected)
}
}
func TestNewSaveLoad(t *testing.T) {
path := "testdata/temp.xml"
os.Remove(path)

View File

@@ -0,0 +1,4 @@
<configuration version="7">
<folder id="test" path="e:" ro="true" ignorePerms="false" rescanIntervalS="600">
</folder>
</configuration>

View File

@@ -264,6 +264,7 @@ func (w *Wrapper) Save() error {
if err != nil {
return err
}
defer os.Remove(fd.Name())
err = w.cfg.WriteXML(fd)
if err != nil {

View File

@@ -160,7 +160,7 @@ func (f *BlockFinder) Changed(cfg config.Configuration) error {
// the block) or false to continue iterating for whatever reason.
// The iterator finally returns the result, whether or not a satisfying block
// was eventually found.
func (f *BlockFinder) Iterate(hash []byte, iterFn func(string, string, uint32) bool) bool {
func (f *BlockFinder) Iterate(hash []byte, iterFn func(string, string, int32) bool) bool {
f.mut.RLock()
folders := f.folders
f.mut.RUnlock()
@@ -171,7 +171,7 @@ func (f *BlockFinder) Iterate(hash []byte, iterFn func(string, string, uint32) b
for iter.Next() && iter.Error() == nil {
folder, file := fromBlockKey(iter.Key())
index := binary.BigEndian.Uint32(iter.Value())
index := int32(binary.BigEndian.Uint32(iter.Value()))
if iterFn(folder, osutil.NativeFilename(file), index) {
return true
}
@@ -182,7 +182,7 @@ func (f *BlockFinder) Iterate(hash []byte, iterFn func(string, string, uint32) b
// A method for repairing incorrect blockmap entries, removes the old entry
// and replaces it with a new entry for the given block
func (f *BlockFinder) Fix(folder, file string, index uint32, oldHash, newHash []byte) error {
func (f *BlockFinder) Fix(folder, file string, index int32, oldHash, newHash []byte) error {
buf := make([]byte, 4)
binary.BigEndian.PutUint32(buf, uint32(index))

View File

@@ -32,7 +32,7 @@ func genBlocks(n int) []protocol.BlockInfo {
for j := range h {
h[j] = byte(i + j)
}
b[i].Size = uint32(i)
b[i].Size = int32(i)
b[i].Hash = h
}
return b
@@ -103,21 +103,21 @@ func TestBlockMapAddUpdateWipe(t *testing.T) {
t.Fatal(err)
}
f.Iterate(f1.Blocks[0].Hash, func(folder, file string, index uint32) bool {
f.Iterate(f1.Blocks[0].Hash, func(folder, file string, index int32) bool {
if folder != "folder1" || file != "f1" || index != 0 {
t.Fatal("Mismatch")
}
return true
})
f.Iterate(f2.Blocks[0].Hash, func(folder, file string, index uint32) bool {
f.Iterate(f2.Blocks[0].Hash, func(folder, file string, index int32) bool {
if folder != "folder1" || file != "f2" || index != 0 {
t.Fatal("Mismatch")
}
return true
})
f.Iterate(f3.Blocks[0].Hash, func(folder, file string, index uint32) bool {
f.Iterate(f3.Blocks[0].Hash, func(folder, file string, index int32) bool {
t.Fatal("Unexpected block")
return true
})
@@ -132,17 +132,17 @@ func TestBlockMapAddUpdateWipe(t *testing.T) {
t.Fatal(err)
}
f.Iterate(f1.Blocks[0].Hash, func(folder, file string, index uint32) bool {
f.Iterate(f1.Blocks[0].Hash, func(folder, file string, index int32) bool {
t.Fatal("Unexpected block")
return false
})
f.Iterate(f2.Blocks[0].Hash, func(folder, file string, index uint32) bool {
f.Iterate(f2.Blocks[0].Hash, func(folder, file string, index int32) bool {
t.Fatal("Unexpected block")
return false
})
f.Iterate(f3.Blocks[0].Hash, func(folder, file string, index uint32) bool {
f.Iterate(f3.Blocks[0].Hash, func(folder, file string, index int32) bool {
if folder != "folder1" || file != "f3" || index != 0 {
t.Fatal("Mismatch")
}
@@ -189,7 +189,7 @@ func TestBlockFinderLookup(t *testing.T) {
}
counter := 0
f.Iterate(f1.Blocks[0].Hash, func(folder, file string, index uint32) bool {
f.Iterate(f1.Blocks[0].Hash, func(folder, file string, index int32) bool {
counter++
switch counter {
case 1:
@@ -217,7 +217,7 @@ func TestBlockFinderLookup(t *testing.T) {
}
counter = 0
f.Iterate(f1.Blocks[0].Hash, func(folder, file string, index uint32) bool {
f.Iterate(f1.Blocks[0].Hash, func(folder, file string, index int32) bool {
counter++
switch counter {
case 1:
@@ -239,7 +239,7 @@ func TestBlockFinderLookup(t *testing.T) {
func TestBlockFinderFix(t *testing.T) {
db, f := setup()
iterFn := func(folder, file string, index uint32) bool {
iterFn := func(folder, file string, index int32) bool {
return true
}

View File

@@ -34,11 +34,11 @@ import (
)
var (
clockTick uint64
clockTick int64
clockMut sync.Mutex
)
func clock(v uint64) uint64 {
func clock(v int64) int64 {
clockMut.Lock()
defer clockMut.Unlock()
if v > clockTick {
@@ -56,7 +56,7 @@ const (
)
type fileVersion struct {
version uint64
version int64
device []byte
}
@@ -164,9 +164,9 @@ func globalKeyFolder(key []byte) []byte {
return folder[:izero]
}
type deletionHandler func(db dbReader, batch dbWriter, folder, device, name []byte, dbi iterator.Iterator) uint64
type deletionHandler func(db dbReader, batch dbWriter, folder, device, name []byte, dbi iterator.Iterator) int64
func ldbGenericReplace(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo, deleteFn deletionHandler) uint64 {
func ldbGenericReplace(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo, deleteFn deletionHandler) int64 {
runtime.GC()
sort.Sort(fileList(fs)) // sort list on name, same as in the database
@@ -197,7 +197,7 @@ func ldbGenericReplace(db *leveldb.DB, folder, device []byte, fs []protocol.File
moreDb := dbi.Next()
fsi := 0
var maxLocalVer uint64
var maxLocalVer int64
for {
var newName, oldName []byte
@@ -288,9 +288,9 @@ func ldbGenericReplace(db *leveldb.DB, folder, device []byte, fs []protocol.File
return maxLocalVer
}
func ldbReplace(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) uint64 {
func ldbReplace(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) int64 {
// TODO: Return the remaining maxLocalVer?
return ldbGenericReplace(db, folder, device, fs, func(db dbReader, batch dbWriter, folder, device, name []byte, dbi iterator.Iterator) uint64 {
return ldbGenericReplace(db, folder, device, fs, func(db dbReader, batch dbWriter, folder, device, name []byte, dbi iterator.Iterator) int64 {
// Database has a file that we are missing. Remove it.
if debugDB {
l.Debugf("delete; folder=%q device=%v name=%q", folder, protocol.DeviceIDFromBytes(device), name)
@@ -304,8 +304,8 @@ func ldbReplace(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) u
})
}
func ldbReplaceWithDelete(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) uint64 {
return ldbGenericReplace(db, folder, device, fs, func(db dbReader, batch dbWriter, folder, device, name []byte, dbi iterator.Iterator) uint64 {
func ldbReplaceWithDelete(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) int64 {
return ldbGenericReplace(db, folder, device, fs, func(db dbReader, batch dbWriter, folder, device, name []byte, dbi iterator.Iterator) int64 {
var tf FileInfoTruncated
err := tf.UnmarshalXDR(dbi.Value())
if err != nil {
@@ -335,7 +335,7 @@ func ldbReplaceWithDelete(db *leveldb.DB, folder, device []byte, fs []protocol.F
})
}
func ldbUpdate(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) uint64 {
func ldbUpdate(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) int64 {
runtime.GC()
batch := new(leveldb.Batch)
@@ -356,7 +356,7 @@ func ldbUpdate(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) ui
snap.Release()
}()
var maxLocalVer uint64
var maxLocalVer int64
for _, f := range fs {
name := []byte(f.Name)
fk := deviceKey(folder, device, name)
@@ -406,7 +406,7 @@ func ldbUpdate(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) ui
return maxLocalVer
}
func ldbInsert(batch dbWriter, folder, device []byte, file protocol.FileInfo) uint64 {
func ldbInsert(batch dbWriter, folder, device []byte, file protocol.FileInfo) int64 {
if debugDB {
l.Debugf("insert; folder=%q device=%v %v", folder, protocol.DeviceIDFromBytes(device), file)
}
@@ -428,7 +428,7 @@ func ldbInsert(batch dbWriter, folder, device []byte, file protocol.FileInfo) ui
// ldbUpdateGlobal adds this device+version to the version list for the given
// file. If the device is already present in the list, the version is updated.
// If the file does not have an entry in the global list, it is created.
func ldbUpdateGlobal(db dbReader, batch dbWriter, folder, device, file []byte, version uint64) bool {
func ldbUpdateGlobal(db dbReader, batch dbWriter, folder, device, file []byte, version int64) bool {
if debugDB {
l.Debugf("update global; folder=%q device=%v file=%q version=%d", folder, protocol.DeviceIDFromBytes(device), file, version)
}
@@ -798,7 +798,7 @@ outer:
have := false // If we have the file, any version
need := false // If we have a lower version of the file
var haveVersion uint64
var haveVersion int64
for _, v := range vl.versions {
if bytes.Compare(v.device, device) == 0 {
have = true

View File

@@ -31,7 +31,7 @@ fileVersion Structure:
struct fileVersion {
unsigned hyper version;
hyper version;
opaque device<>;
}
@@ -62,7 +62,7 @@ func (o fileVersion) AppendXDR(bs []byte) ([]byte, error) {
}
func (o fileVersion) encodeXDR(xw *xdr.Writer) (int, error) {
xw.WriteUint64(o.version)
xw.WriteUint64(uint64(o.version))
xw.WriteBytes(o.device)
return xw.Tot(), xw.Error()
}
@@ -79,7 +79,7 @@ func (o *fileVersion) UnmarshalXDR(bs []byte) error {
}
func (o *fileVersion) decodeXDR(xr *xdr.Reader) error {
o.version = xr.ReadUint64()
o.version = int64(xr.ReadUint64())
o.device = xr.ReadBytes()
return xr.Error()
}

View File

@@ -31,7 +31,7 @@ import (
)
type FileSet struct {
localVersion map[protocol.DeviceID]uint64
localVersion map[protocol.DeviceID]int64
mutex sync.Mutex
folder string
db *leveldb.DB
@@ -56,7 +56,7 @@ type Iterator func(f FileIntf) bool
func NewFileSet(folder string, db *leveldb.DB) *FileSet {
var s = FileSet{
localVersion: make(map[protocol.DeviceID]uint64),
localVersion: make(map[protocol.DeviceID]int64),
folder: folder,
db: db,
blockmap: NewBlockMap(db, folder),
@@ -212,7 +212,7 @@ func (s *FileSet) Availability(file string) []protocol.DeviceID {
return ldbAvailability(s.db, []byte(s.folder), []byte(osutil.NormalizedFilename(file)))
}
func (s *FileSet) LocalVersion(device protocol.DeviceID) uint64 {
func (s *FileSet) LocalVersion(device protocol.DeviceID) int64 {
s.mutex.Lock()
defer s.mutex.Unlock()
return s.localVersion[device]

View File

@@ -43,7 +43,7 @@ func genBlocks(n int) []protocol.BlockInfo {
for j := range h {
h[j] = byte(i + j)
}
b[i].Size = uint32(i)
b[i].Size = int32(i)
b[i].Hash = h
}
return b

View File

@@ -29,9 +29,20 @@ type FileInfoTruncated struct {
Name string // max:8192
Flags uint32
Modified int64
Version uint64
LocalVersion uint64
NumBlocks uint32
Version int64
LocalVersion int64
NumBlocks int32
}
func ToTruncated(file protocol.FileInfo) FileInfoTruncated {
return FileInfoTruncated{
Name: file.Name,
Flags: file.Flags,
Modified: file.Modified,
Version: file.Version,
LocalVersion: file.LocalVersion,
NumBlocks: int32(len(file.Blocks)),
}
}
func (f FileInfoTruncated) String() string {
@@ -44,7 +55,7 @@ func (f FileInfoTruncated) Size() int64 {
if f.IsDeleted() || f.IsDirectory() {
return 128
}
return BlocksToSize(f.NumBlocks)
return BlocksToSize(int(f.NumBlocks))
}
func (f FileInfoTruncated) IsDeleted() bool {
@@ -67,7 +78,7 @@ func (f FileInfoTruncated) HasPermissionBits() bool {
return f.Flags&protocol.FlagNoPermBits == 0
}
func BlocksToSize(num uint32) int64 {
func BlocksToSize(num int) int64 {
if num < 2 {
return protocol.BlockSize / 2
}

View File

@@ -46,9 +46,9 @@ struct FileInfoTruncated {
string Name<8192>;
unsigned int Flags;
hyper Modified;
unsigned hyper Version;
unsigned hyper LocalVersion;
unsigned int NumBlocks;
hyper Version;
hyper LocalVersion;
int NumBlocks;
}
*/
@@ -84,9 +84,9 @@ func (o FileInfoTruncated) encodeXDR(xw *xdr.Writer) (int, error) {
xw.WriteString(o.Name)
xw.WriteUint32(o.Flags)
xw.WriteUint64(uint64(o.Modified))
xw.WriteUint64(o.Version)
xw.WriteUint64(o.LocalVersion)
xw.WriteUint32(o.NumBlocks)
xw.WriteUint64(uint64(o.Version))
xw.WriteUint64(uint64(o.LocalVersion))
xw.WriteUint32(uint32(o.NumBlocks))
return xw.Tot(), xw.Error()
}
@@ -105,8 +105,8 @@ func (o *FileInfoTruncated) decodeXDR(xr *xdr.Reader) error {
o.Name = xr.ReadStringMax(8192)
o.Flags = xr.ReadUint32()
o.Modified = int64(xr.ReadUint64())
o.Version = xr.ReadUint64()
o.LocalVersion = xr.ReadUint64()
o.NumBlocks = xr.ReadUint32()
o.Version = int64(xr.ReadUint64())
o.LocalVersion = int64(xr.ReadUint64())
o.NumBlocks = int32(xr.ReadUint32())
return xr.Error()
}

View File

@@ -22,7 +22,7 @@ import (
"time"
)
type EventType uint64
type EventType int
const (
Ping EventType = 1 << iota
@@ -35,6 +35,7 @@ const (
LocalIndexUpdated
RemoteIndexUpdated
ItemStarted
ItemFinished
StateChanged
FolderRejected
ConfigSaved
@@ -65,6 +66,8 @@ func (t EventType) String() string {
return "RemoteIndexUpdated"
case ItemStarted:
return "ItemStarted"
case ItemFinished:
return "ItemFinished"
case StateChanged:
return "StateChanged"
case FolderRejected:

View File

@@ -21,11 +21,11 @@ import "sync"
var Default = Clock{}
type Clock struct {
val uint64
val int64
mut sync.Mutex
}
func (c *Clock) Tick(v uint64) uint64 {
func (c *Clock) Tick(v int64) int64 {
c.mut.Lock()
if v > c.val {
c.val = v + 1

View File

@@ -17,12 +17,12 @@ package lamport
import "testing"
var inputs = []uint64{0, 42, 2, 3, 4, 8, 9, 33, 44, 112, 100}
var inputs = []int64{0, 42, 2, 3, 4, 8, 9, 33, 44, 112, 100}
func TestClock(t *testing.T) {
c := Clock{}
var prev uint64
var prev int64
for _, input := range inputs {
cur := c.Tick(input)
if cur <= prev || cur <= input {

View File

@@ -298,8 +298,6 @@ func (m *Model) FolderStatistics() map[string]stats.FolderStatistics {
// Returns the completion status, in percent, for the given device and folder.
func (m *Model) Completion(device protocol.DeviceID, folder string) float64 {
defer m.leveldbPanicWorkaround()
var tot int64
m.fmut.RLock()
@@ -359,8 +357,6 @@ func sizeOfFile(f db.FileIntf) (files, deleted int, bytes int64) {
// GlobalSize returns the number of files, deleted files and total bytes for all
// files in the global model.
func (m *Model) GlobalSize(folder string) (nfiles, deleted int, bytes int64) {
defer m.leveldbPanicWorkaround()
m.fmut.RLock()
defer m.fmut.RUnlock()
if rf, ok := m.folderFiles[folder]; ok {
@@ -378,8 +374,6 @@ func (m *Model) GlobalSize(folder string) (nfiles, deleted int, bytes int64) {
// LocalSize returns the number of files, deleted files and total bytes for all
// files in the local folder.
func (m *Model) LocalSize(folder string) (nfiles, deleted int, bytes int64) {
defer m.leveldbPanicWorkaround()
m.fmut.RLock()
defer m.fmut.RUnlock()
if rf, ok := m.folderFiles[folder]; ok {
@@ -399,8 +393,6 @@ func (m *Model) LocalSize(folder string) (nfiles, deleted int, bytes int64) {
// NeedSize returns the number and total size of currently needed files.
func (m *Model) NeedSize(folder string) (nfiles int, bytes int64) {
defer m.leveldbPanicWorkaround()
m.fmut.RLock()
defer m.fmut.RUnlock()
if rf, ok := m.folderFiles[folder]; ok {
@@ -422,10 +414,9 @@ func (m *Model) NeedSize(folder string) (nfiles int, bytes int64) {
// and to be queued on next puller iteration. Also takes a soft cap which is
// only respected when adding files from the model rather than the runner queue.
func (m *Model) NeedFolderFiles(folder string, max int) ([]db.FileInfoTruncated, []db.FileInfoTruncated, []db.FileInfoTruncated) {
defer m.leveldbPanicWorkaround()
m.fmut.RLock()
defer m.fmut.RUnlock()
if rf, ok := m.folderFiles[folder]; ok {
var progress, queued, rest []db.FileInfoTruncated
var seen map[string]bool
@@ -710,9 +701,18 @@ func (m *Model) Close(device protocol.DeviceID, err error) {
// Request returns the specified data segment by reading it from local disk.
// Implements the protocol.Model interface.
func (m *Model) Request(deviceID protocol.DeviceID, folder, name string, offset int64, size int) ([]byte, error) {
if offset < 0 || size < 0 {
return nil, ErrNoSuchFile
}
if !m.folderSharedWith(folder, deviceID) {
l.Warnf("Request from %s for file %s in unshared folder %q", deviceID, name, folder)
return nil, ErrNoSuchFile
}
// Verify that the requested file exists in the local model.
m.fmut.RLock()
r, ok := m.folderFiles[folder]
folderFiles, ok := m.folderFiles[folder]
m.fmut.RUnlock()
if !ok {
@@ -720,7 +720,7 @@ func (m *Model) Request(deviceID protocol.DeviceID, folder, name string, offset
return nil, ErrNoSuchFile
}
lf, ok := r.Get(protocol.LocalDeviceID, name)
lf, ok := folderFiles.Get(protocol.LocalDeviceID, name)
if !ok {
return nil, ErrNoSuchFile
}
@@ -755,7 +755,9 @@ func (m *Model) Request(deviceID protocol.DeviceID, folder, name string, offset
}
reader = strings.NewReader(target)
} else {
reader, err = os.Open(fn) // XXX: Inefficient, should cache fd?
// Cannot easily cache fd's because we might need to delete the file
// at any moment.
reader, err = os.Open(fn)
if err != nil {
return nil, err
}
@@ -974,12 +976,12 @@ func sendIndexes(conn protocol.Connection, folder string, fs *db.FileSet, ignore
}
}
func sendIndexTo(initial bool, minLocalVer uint64, conn protocol.Connection, folder string, fs *db.FileSet, ignores *ignore.Matcher) (uint64, error) {
func sendIndexTo(initial bool, minLocalVer int64, conn protocol.Connection, folder string, fs *db.FileSet, ignores *ignore.Matcher) (int64, error) {
deviceID := conn.ID()
name := conn.Name()
batch := make([]protocol.FileInfo, 0, indexBatchSize)
currentBatchSize := 0
maxLocalVer := uint64(0)
maxLocalVer := int64(0)
var err error
fs.WithHave(protocol.LocalDeviceID, func(fi db.FileIntf) bool {
@@ -1353,7 +1355,7 @@ func (m *Model) Override(folder string) {
// CurrentLocalVersion returns the change version for the given folder.
// This is guaranteed to increment if the contents of the local folder has
// changed.
func (m *Model) CurrentLocalVersion(folder string) uint64 {
func (m *Model) CurrentLocalVersion(folder string) int64 {
m.fmut.RLock()
fs, ok := m.folderFiles[folder]
m.fmut.RUnlock()
@@ -1369,7 +1371,7 @@ func (m *Model) CurrentLocalVersion(folder string) uint64 {
// RemoteLocalVersion returns the change version for the given folder, as
// sent by remote peers. This is guaranteed to increment if the contents of
// the remote or global folder has changed.
func (m *Model) RemoteLocalVersion(folder string) uint64 {
func (m *Model) RemoteLocalVersion(folder string) int64 {
m.fmut.RLock()
defer m.fmut.RUnlock()
@@ -1380,7 +1382,7 @@ func (m *Model) RemoteLocalVersion(folder string) uint64 {
return 0
}
var ver uint64
var ver int64
for _, n := range m.folderDevices[folder] {
ver += fs.LocalVersion(n)
}
@@ -1426,27 +1428,6 @@ func (m *Model) String() string {
return fmt.Sprintf("model@%p", m)
}
func (m *Model) leveldbPanicWorkaround() {
// When an inconsistency is detected in leveldb we panic(). This is
// appropriate because it should never happen, but currently it does for
// some reason. However it only seems to trigger in the asynchronous full-
// database scans that happen due to REST and usage-reporting calls. In
// those places we defer to this workaround to catch the panic instead of
// taking down syncthing.
// This is just a band-aid and should be removed as soon as we have found
// a real root cause.
if pnc := recover(); pnc != nil {
if err, ok := pnc.(error); ok && strings.Contains(err.Error(), "leveldb") {
l.Infoln("recovered:", err)
} else {
// Any non-leveldb error is genuine and should continue panicing.
panic(err)
}
}
}
func symlinkInvalid(isLink bool) bool {
if !symlinks.Supported && isLink {
SymlinkWarning.Do(func() {

View File

@@ -68,18 +68,41 @@ func init() {
func TestRequest(t *testing.T) {
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
m := NewModel(config.Wrap("/tmp/test", config.Configuration{}), "device", "syncthing", "dev", db)
m.AddFolder(config.FolderConfiguration{ID: "default", Path: "testdata"})
// device1 shares default, but device2 doesn't
m.AddFolder(config.FolderConfiguration{ID: "default", Path: "testdata", Devices: []config.FolderDeviceConfiguration{{DeviceID: device1}}})
m.ScanFolder("default")
// Existing, shared file
bs, err := m.Request(device1, "default", "foo", 0, 6)
if err != nil {
t.Fatal(err)
t.Error(err)
}
if bytes.Compare(bs, []byte("foobar")) != 0 {
t.Errorf("Incorrect data from request: %q", string(bs))
}
// Existing, nonshared file
bs, err = m.Request(device2, "default", "foo", 0, 6)
if err == nil {
t.Error("Unexpected nil error on insecure file read")
}
if bs != nil {
t.Errorf("Unexpected non nil data on insecure file read: %q", string(bs))
}
// Nonexistent file
bs, err = m.Request(device1, "default", "nonexistent", 0, 6)
if err == nil {
t.Error("Unexpected nil error on insecure file read")
}
if bs != nil {
t.Errorf("Unexpected non nil data on insecure file read: %q", string(bs))
}
// Shared folder, but disallowed file name
bs, err = m.Request(device1, "default", "../walk.go", 0, 6)
if err == nil {
t.Error("Unexpected nil error on insecure file read")
@@ -87,6 +110,33 @@ func TestRequest(t *testing.T) {
if bs != nil {
t.Errorf("Unexpected non nil data on insecure file read: %q", string(bs))
}
// Larger block than available
bs, err = m.Request(device1, "default", "foo", 0, 42)
if err == nil {
t.Error("Unexpected nil error on insecure file read")
}
if bs != nil {
t.Errorf("Unexpected non nil data on insecure file read: %q", string(bs))
}
// Negative offset
bs, err = m.Request(device1, "default", "foo", -4, 6)
if err == nil {
t.Error("Unexpected nil error on insecure file read")
}
if bs != nil {
t.Errorf("Unexpected non nil data on insecure file read: %q", string(bs))
}
// Negative size
bs, err = m.Request(device1, "default", "foo", 4, -4)
if err == nil {
t.Error("Unexpected nil error on insecure file read")
}
if bs != nil {
t.Errorf("Unexpected non nil data on insecure file read: %q", string(bs))
}
}
func genFiles(n int) []protocol.FileInfo {

View File

@@ -25,8 +25,6 @@ import (
"sync"
"time"
"github.com/AudriusButkevicius/lfu-go"
"github.com/syncthing/protocol"
"github.com/syncthing/syncthing/internal/config"
"github.com/syncthing/syncthing/internal/db"
@@ -100,7 +98,7 @@ func (p *Puller) Serve() {
p.model.setState(p.folder, FolderIdle)
}()
var prevVer uint64
var prevVer int64
var prevIgnoreHash string
// We don't start pulling files until a scan has been completed.
@@ -297,7 +295,9 @@ func (p *Puller) pullerIteration(ignores *ignore.Matcher) int {
changed := 0
var deletions []protocol.FileInfo
fileDeletions := map[string]protocol.FileInfo{}
dirDeletions := []protocol.FileInfo{}
buckets := map[string][]protocol.FileInfo{}
folderFiles.WithNeed(protocol.LocalDeviceID, func(intf db.FileIntf) bool {
@@ -315,11 +315,6 @@ func (p *Puller) pullerIteration(ignores *ignore.Matcher) int {
return true
}
events.Default.Log(events.ItemStarted, map[string]string{
"folder": p.folder,
"item": file.Name,
})
if debug {
l.Debugln(p, "handling", file.Name)
}
@@ -327,9 +322,26 @@ func (p *Puller) pullerIteration(ignores *ignore.Matcher) int {
switch {
case file.IsDeleted():
// A deleted file, directory or symlink
deletions = append(deletions, file)
if file.IsDirectory() {
dirDeletions = append(dirDeletions, file)
} else {
fileDeletions[file.Name] = file
df, ok := p.model.CurrentFolderFile(p.folder, file.Name)
// Local file can be already deleted, but with a lower version
// number, hence the deletion coming in again as part of
// WithNeed, furthermore, the file can simply be of the wrong
// type if we haven't yet managed to pull it.
if ok && !df.IsDeleted() && !df.IsSymlink() && !df.IsDirectory() {
// Put files into buckets per first hash
key := string(df.Blocks[0].Hash)
buckets[key] = append(buckets[key], df)
}
}
case file.IsDirectory() && !file.IsSymlink():
// A new or changed directory
if debug {
l.Debugln("Creating directory", file.Name)
}
p.handleDir(file)
default:
// A new or changed file or symlink. This is the only case where we
@@ -341,17 +353,50 @@ func (p *Puller) pullerIteration(ignores *ignore.Matcher) int {
return true
})
nextFile:
for {
fileName, ok := p.queue.Pop()
if !ok {
break
}
if f, ok := p.model.CurrentGlobalFile(p.folder, fileName); ok {
p.handleFile(f, copyChan, finisherChan)
} else {
f, ok := p.model.CurrentGlobalFile(p.folder, fileName)
if !ok {
// File is no longer in the index. Mark it as done and drop it.
p.queue.Done(fileName)
continue
}
// Local file can be already deleted, but with a lower version
// number, hence the deletion coming in again as part of
// WithNeed, furthermore, the file can simply be of the wrong type if
// the global index changed while we were processing this iteration.
if !f.IsDeleted() && !f.IsSymlink() && !f.IsDirectory() {
key := string(f.Blocks[0].Hash)
for i, candidate := range buckets[key] {
if scanner.BlocksEqual(candidate.Blocks, f.Blocks) {
// Remove the candidate from the bucket
lidx := len(buckets[key]) - 1
buckets[key][i] = buckets[key][lidx]
buckets[key] = buckets[key][:lidx]
// candidate is our current state of the file, where as the
// desired state with the delete bit set is in the deletion
// map.
desired := fileDeletions[candidate.Name]
// Remove the pending deletion (as we perform it by renaming)
delete(fileDeletions, candidate.Name)
p.renameFile(desired, f)
p.queue.Done(fileName)
continue nextFile
}
}
}
// Not a rename or a symlink, deal with it.
p.handleFile(f, copyChan, finisherChan)
}
// Signal copy and puller routines that we are done with the in data for
@@ -367,13 +412,19 @@ func (p *Puller) pullerIteration(ignores *ignore.Matcher) int {
// Wait for the finisherChan to finish.
doneWg.Wait()
for i := range deletions {
deletion := deletions[len(deletions)-i-1]
if deletion.IsDirectory() {
p.deleteDir(deletion)
} else {
p.deleteFile(deletion)
for _, file := range fileDeletions {
if debug {
l.Debugln("Deleting file", file.Name)
}
p.deleteFile(file)
}
for i := range dirDeletions {
dir := dirDeletions[len(dirDeletions)-i-1]
if debug {
l.Debugln("Deleting dir", dir.Name)
}
p.deleteDir(dir)
}
return changed
@@ -381,6 +432,20 @@ func (p *Puller) pullerIteration(ignores *ignore.Matcher) int {
// handleDir creates or updates the given directory
func (p *Puller) handleDir(file protocol.FileInfo) {
var err error
events.Default.Log(events.ItemStarted, map[string]interface{}{
"folder": p.folder,
"item": file.Name,
"details": db.ToTruncated(file),
})
defer func() {
events.Default.Log(events.ItemFinished, map[string]interface{}{
"folder": p.folder,
"item": file.Name,
"error": err,
})
}()
realName := filepath.Join(p.dir, file.Name)
mode := os.FileMode(file.Flags & 0777)
if p.ignorePerms {
@@ -442,6 +507,20 @@ func (p *Puller) handleDir(file protocol.FileInfo) {
// deleteDir attempts to delete the given directory
func (p *Puller) deleteDir(file protocol.FileInfo) {
var err error
events.Default.Log(events.ItemStarted, map[string]interface{}{
"folder": p.folder,
"item": file.Name,
"details": db.ToTruncated(file),
})
defer func() {
events.Default.Log(events.ItemFinished, map[string]interface{}{
"folder": p.folder,
"item": file.Name,
"error": err,
})
}()
realName := filepath.Join(p.dir, file.Name)
// Delete any temporary files lying around in the directory
dir, _ := os.Open(realName)
@@ -453,7 +532,7 @@ func (p *Puller) deleteDir(file protocol.FileInfo) {
}
}
}
err := osutil.InWritableDir(os.Remove, realName)
err = osutil.InWritableDir(os.Remove, realName)
if err == nil || os.IsNotExist(err) {
p.model.updateLocal(p.folder, file)
} else {
@@ -463,9 +542,22 @@ func (p *Puller) deleteDir(file protocol.FileInfo) {
// deleteFile attempts to delete the given file
func (p *Puller) deleteFile(file protocol.FileInfo) {
var err error
events.Default.Log(events.ItemStarted, map[string]interface{}{
"folder": p.folder,
"item": file.Name,
"details": db.ToTruncated(file),
})
defer func() {
events.Default.Log(events.ItemFinished, map[string]interface{}{
"folder": p.folder,
"item": file.Name,
"error": err,
})
}()
realName := filepath.Join(p.dir, file.Name)
var err error
if p.versioner != nil {
err = osutil.InWritableDir(p.versioner.Archive, realName)
} else {
@@ -479,9 +571,76 @@ func (p *Puller) deleteFile(file protocol.FileInfo) {
}
}
// renameFile attempts to rename an existing file to a destination
// and set the right attributes on it.
func (p *Puller) renameFile(source, target protocol.FileInfo) {
var err error
events.Default.Log(events.ItemStarted, map[string]interface{}{
"folder": p.folder,
"item": source.Name,
"details": db.ToTruncated(source),
})
events.Default.Log(events.ItemStarted, map[string]interface{}{
"folder": p.folder,
"item": target.Name,
"details": db.ToTruncated(source),
})
defer func() {
events.Default.Log(events.ItemFinished, map[string]interface{}{
"folder": p.folder,
"item": source.Name,
"error": err,
})
events.Default.Log(events.ItemFinished, map[string]interface{}{
"folder": p.folder,
"item": target.Name,
"error": err,
})
}()
if debug {
l.Debugln(p, "taking rename shortcut", source.Name, "->", target.Name)
}
from := filepath.Join(p.dir, source.Name)
to := filepath.Join(p.dir, target.Name)
if p.versioner != nil {
err = osutil.Copy(from, to)
if err == nil {
err = osutil.InWritableDir(p.versioner.Archive, from)
}
} else {
err = osutil.TryRename(from, to)
}
if err != nil {
l.Infof("Puller (folder %q, file %q): rename from %q: %v", p.folder, target.Name, source.Name, err)
return
}
// Fix-up the metadata, and update the local index of the target file
err = p.shortcutFile(target)
if err != nil {
l.Infof("Puller (folder %q, file %q): rename from %q metadata: %v", p.folder, target.Name, source.Name, err)
return
}
// Source file already has the delete bit set.
// Because we got rid of the file (by renaming it), we just need to update
// the index, and we're done with it.
p.model.updateLocal(p.folder, source)
}
// handleFile queues the copies and pulls as necessary for a single new or
// changed file.
func (p *Puller) handleFile(file protocol.FileInfo, copyChan chan<- copyBlocksState, finisherChan chan<- *sharedPullerState) {
events.Default.Log(events.ItemStarted, map[string]interface{}{
"folder": p.folder,
"item": file.Name,
"details": db.ToTruncated(file),
})
curFile, ok := p.model.CurrentFolderFile(p.folder, file.Name)
if ok && len(curFile.Blocks) == len(file.Blocks) && scanner.BlocksEqual(curFile.Blocks, file.Blocks) {
@@ -492,11 +651,17 @@ func (p *Puller) handleFile(file protocol.FileInfo, copyChan chan<- copyBlocksSt
l.Debugln(p, "taking shortcut on", file.Name)
}
p.queue.Done(file.Name)
var err error
if file.IsSymlink() {
p.shortcutSymlink(curFile, file)
err = p.shortcutSymlink(file)
} else {
p.shortcutFile(file)
err = p.shortcutFile(file)
}
events.Default.Log(events.ItemFinished, map[string]interface{}{
"folder": p.folder,
"item": file.Name,
"error": err,
})
return
}
@@ -548,9 +713,9 @@ func (p *Puller) handleFile(file protocol.FileInfo, copyChan chan<- copyBlocksSt
folder: p.folder,
tempName: tempName,
realName: realName,
copyTotal: uint32(len(blocks)),
copyNeeded: uint32(len(blocks)),
reused: uint32(reused),
copyTotal: len(blocks),
copyNeeded: len(blocks),
reused: reused,
}
if debug {
@@ -566,10 +731,10 @@ func (p *Puller) handleFile(file protocol.FileInfo, copyChan chan<- copyBlocksSt
// shortcutFile sets file mode and modification time, when that's the only
// thing that has changed.
func (p *Puller) shortcutFile(file protocol.FileInfo) {
func (p *Puller) shortcutFile(file protocol.FileInfo) (err error) {
realName := filepath.Join(p.dir, file.Name)
if !p.ignorePerms {
err := os.Chmod(realName, os.FileMode(file.Flags&0777))
err = os.Chmod(realName, os.FileMode(file.Flags&0777))
if err != nil {
l.Infof("Puller (folder %q, file %q): shortcut: %v", p.folder, file.Name, err)
return
@@ -577,9 +742,10 @@ func (p *Puller) shortcutFile(file protocol.FileInfo) {
}
t := time.Unix(file.Modified, 0)
err := os.Chtimes(realName, t, t)
err = os.Chtimes(realName, t, t)
if err != nil {
if p.lenientMtimes {
err = nil
// We accept the failure with a warning here and allow the sync to
// continue. We'll sync the new mtime back to the other devices later.
// If they have the same problem & setting, we might never get in
@@ -592,17 +758,18 @@ func (p *Puller) shortcutFile(file protocol.FileInfo) {
}
p.model.updateLocal(p.folder, file)
return
}
// shortcutSymlink changes the symlinks type if necessery.
func (p *Puller) shortcutSymlink(curFile, file protocol.FileInfo) {
err := symlinks.ChangeType(filepath.Join(p.dir, file.Name), file.Flags)
if err != nil {
func (p *Puller) shortcutSymlink(file protocol.FileInfo) (err error) {
err = symlinks.ChangeType(filepath.Join(p.dir, file.Name), file.Flags)
if err == nil {
p.model.updateLocal(p.folder, file)
} else {
l.Infof("Puller (folder %q, file %q): symlink shortcut: %v", p.folder, file.Name, err)
return
}
p.model.updateLocal(p.folder, file)
return
}
// copierRoutine reads copierStates until the in channel closes and performs
@@ -623,19 +790,6 @@ func (p *Puller) copierRoutine(in <-chan copyBlocksState, pullChan chan<- pullBl
continue
}
evictionChan := make(chan lfu.Eviction)
fdCache := lfu.New()
fdCache.UpperBound = 50
fdCache.LowerBound = 20
fdCache.EvictionChannel = evictionChan
go func() {
for item := range evictionChan {
item.Value.(*os.File).Close()
}
}()
folderRoots := make(map[string]string)
p.model.fmut.RLock()
for folder, cfg := range p.model.folderCfgs {
@@ -645,23 +799,14 @@ func (p *Puller) copierRoutine(in <-chan copyBlocksState, pullChan chan<- pullBl
for _, block := range state.blocks {
buf = buf[:int(block.Size)]
found := p.model.finder.Iterate(block.Hash, func(folder, file string, index uint32) bool {
path := filepath.Join(folderRoots[folder], file)
var fd *os.File
fdi := fdCache.Get(path)
if fdi != nil {
fd = fdi.(*os.File)
} else {
fd, err = os.Open(path)
if err != nil {
return false
}
fdCache.Set(path, fd)
found := p.model.finder.Iterate(block.Hash, func(folder, file string, index int32) bool {
fd, err := os.Open(filepath.Join(folderRoots[folder], file))
if err != nil {
return false
}
_, err = fd.ReadAt(buf, protocol.BlockSize*int64(index))
fd.Close()
if err != nil {
return false
}
@@ -707,8 +852,6 @@ func (p *Puller) copierRoutine(in <-chan copyBlocksState, pullChan chan<- pullBl
state.copyDone()
}
}
fdCache.Evict(fdCache.Len())
close(evictionChan)
out <- state.sharedPullerState
}
}
@@ -776,6 +919,13 @@ func (p *Puller) pullerRoutine(in <-chan pullBlockState, out chan<- *sharedPulle
func (p *Puller) performFinish(state *sharedPullerState) {
var err error
defer func() {
events.Default.Log(events.ItemFinished, map[string]interface{}{
"folder": p.folder,
"item": state.file.Name,
"error": err,
})
}()
// Set the correct permission bits on the new file
if !p.ignorePerms {
err = os.Chmod(state.tempName, os.FileMode(state.file.Flags&0777))
@@ -862,6 +1012,12 @@ func (p *Puller) finisherRoutine(in <-chan *sharedPullerState) {
p.queue.Done(state.file.Name)
if state.failed() == nil {
p.performFinish(state)
} else {
events.Default.Log(events.ItemFinished, map[string]interface{}{
"folder": p.folder,
"item": state.file.Name,
"error": state.failed(),
})
}
p.model.receivedFile(p.folder, state.file.Name)
if p.progressEmitter != nil {

View File

@@ -199,7 +199,7 @@ func TestCopierFinder(t *testing.T) {
// Update index
m.updateLocal("default", existingFile)
iterFn := func(folder, file string, index uint32) bool {
iterFn := func(folder, file string, index int32) bool {
return true
}
@@ -264,7 +264,7 @@ func TestCopierFinder(t *testing.T) {
// Test that updating a file removes it's old blocks from the blockmap
func TestCopierCleanup(t *testing.T) {
iterFn := func(folder, file string, index uint32) bool {
iterFn := func(folder, file string, index int32) bool {
return true
}
@@ -341,7 +341,7 @@ func TestLastResortPulling(t *testing.T) {
// with a different name (causing to copy that particular block)
file.Name = "newfile"
iterFn := func(folder, file string, index uint32) bool {
iterFn := func(folder, file string, index int32) bool {
return true
}
@@ -393,7 +393,7 @@ func TestDeregisterOnFailInCopy(t *testing.T) {
blocks[5], blocks[0], blocks[0], blocks[8],
},
}
defer os.Remove(defTempNamer.TempName("filex"))
defer os.Remove("testdata/" + defTempNamer.TempName("filex"))
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
cw := config.Wrap("/tmp/test", config.Configuration{})
@@ -481,7 +481,7 @@ func TestDeregisterOnFailInPull(t *testing.T) {
blocks[5], blocks[0], blocks[0], blocks[8],
},
}
defer os.Remove(defTempNamer.TempName("filex"))
defer os.Remove("testdata/" + defTempNamer.TempName("filex"))
db, _ := leveldb.Open(storage.NewMemStorage(), nil)
cw := config.Wrap("/tmp/test", config.Configuration{})

View File

@@ -33,27 +33,27 @@ type sharedPullerState struct {
folder string
tempName string
realName string
reused uint32 // Number of blocks reused from temporary file
reused int // Number of blocks reused from temporary file
// Mutable, must be locked for access
err error // The first error we hit
fd *os.File // The fd of the temp file
copyTotal uint32 // Total number of copy actions for the whole job
pullTotal uint32 // Total number of pull actions for the whole job
copyOrigin uint32 // Number of blocks copied from the original file
copyNeeded uint32 // Number of copy actions still pending
pullNeeded uint32 // Number of block pulls still pending
copyTotal int // Total number of copy actions for the whole job
pullTotal int // Total number of pull actions for the whole job
copyOrigin int // Number of blocks copied from the original file
copyNeeded int // Number of copy actions still pending
pullNeeded int // Number of block pulls still pending
mut sync.Mutex // Protects the above
}
// A momentary state representing the progress of the puller
type pullerProgress struct {
Total uint32
Reused uint32
CopiedFromOrigin uint32
CopiedFromElsewhere uint32
Pulled uint32
Pulling uint32
Total int
Reused int
CopiedFromOrigin int
CopiedFromElsewhere int
Pulled int
Pulling int
BytesDone int64
BytesTotal int64
}

View File

@@ -19,6 +19,7 @@ package osutil
import (
"errors"
"fmt"
"io"
"os"
"path/filepath"
"runtime"
@@ -32,34 +33,36 @@ var ErrNoHome = errors.New("No home directory found - set $HOME (or the platform
// often enough that there is any contention on this lock.
var renameLock sync.Mutex
// Rename renames a file, while trying hard to succeed on various systems by
// temporarily tweaking directory permissions and removing the destination
// file when necessary. Will make sure to delete the from file if the
// operation fails, so use only for situations like committing a temp file to
// it's final location.
func Rename(from, to string) error {
// TryRename renames a file, leaving source file intact in case of failure.
// Tries hard to succeed on various systems by temporarily tweaking directory
// permissions and removing the destination file when necessary.
func TryRename(from, to string) error {
renameLock.Lock()
defer renameLock.Unlock()
// Make sure the destination directory is writeable
toDir := filepath.Dir(to)
if info, err := os.Stat(toDir); err == nil && info.IsDir() && info.Mode()&0200 == 0 {
os.Chmod(toDir, 0755)
defer os.Chmod(toDir, info.Mode())
}
// On Windows, make sure the destination file is writeable (or we can't delete it)
if runtime.GOOS == "windows" {
os.Chmod(to, 0666)
err := os.Remove(to)
if err != nil && !os.IsNotExist(err) {
return err
}
}
return withPreparedTarget(to, func() error {
return os.Rename(from, to)
})
}
// Rename moves a temporary file to it's final place.
// Will make sure to delete the from file if the operation fails, so use only
// for situations like committing a temp file to it's final location.
// Tries hard to succeed on various systems by temporarily tweaking directory
// permissions and removing the destination file when necessary.
func Rename(from, to string) error {
// Don't leave a dangling temp file in case of rename error
defer os.Remove(from)
return os.Rename(from, to)
return TryRename(from, to)
}
// Copy copies the file content from source to destination.
// Tries hard to succeed on various systems by temporarily tweaking directory
// permissions and removing the destination file when necessary.
func Copy(from, to string) (err error) {
return withPreparedTarget(to, func() error {
return copyFileContents(from, to)
})
}
// InWritableDir calls fn(path), while making sure that the directory
@@ -123,3 +126,51 @@ func getHomeDir() (string, error) {
return home, nil
}
// Tries hard to succeed on various systems by temporarily tweaking directory
// permissions and removing the destination file when necessary.
func withPreparedTarget(to string, f func() error) error {
// Make sure the destination directory is writeable
toDir := filepath.Dir(to)
if info, err := os.Stat(toDir); err == nil && info.IsDir() && info.Mode()&0200 == 0 {
os.Chmod(toDir, 0755)
defer os.Chmod(toDir, info.Mode())
}
// On Windows, make sure the destination file is writeable (or we can't delete it)
if runtime.GOOS == "windows" {
os.Chmod(to, 0666)
err := os.Remove(to)
if err != nil && !os.IsNotExist(err) {
return err
}
}
return f()
}
// copyFileContents copies the contents of the file named src to the file named
// by dst. The file will be created if it does not already exist. If the
// destination file exists, all it's contents will be replaced by the contents
// of the source file.
func copyFileContents(src, dst string) (err error) {
in, err := os.Open(src)
if err != nil {
return
}
defer in.Close()
out, err := os.Create(dst)
if err != nil {
return
}
defer func() {
cerr := out.Close()
if err == nil {
err = cerr
}
}()
if _, err = io.Copy(out, in); err != nil {
return
}
err = out.Sync()
return
}

View File

@@ -46,7 +46,7 @@ func Blocks(r io.Reader, blocksize int, sizehint int64) ([]protocol.BlockInfo, e
}
b := protocol.BlockInfo{
Size: uint32(n),
Size: int32(n),
Offset: offset,
Hash: hf.Sum(nil),
}

View File

@@ -95,7 +95,7 @@ func upgradeToURL(binary string, url string) error {
}
old := binary + ".old"
_ = os.Remove(old)
os.Remove(old)
err = os.Rename(binary, old)
if err != nil {
return err
@@ -193,20 +193,16 @@ fileLoop:
}
}
if tempName != "" && actualMD5 != "" {
if tempName != "" {
// We found and saved something to disk.
if expectedMD5 == "" {
if debug {
l.Debugln("there is no md5 to compare with")
}
} else if actualMD5 != expectedMD5 {
// There was an md5 file included in the archive, and it doesn't
// match what we just wrote to disk.
return "", fmt.Errorf("incorrect MD5 checksum")
if expectedMD5 == "" || actualMD5 == expectedMD5 {
return tempName, nil
}
return tempName, nil
os.Remove(tempName)
// There was an md5 file included in the archive, and it doesn't
// match what we just wrote to disk.
return "", fmt.Errorf("incorrect MD5 checksum")
}
return "", fmt.Errorf("no upgrade found")
}
@@ -274,20 +270,16 @@ fileLoop:
}
}
if tempName != "" && actualMD5 != "" {
if tempName != "" {
// We found and saved something to disk.
if expectedMD5 == "" {
if debug {
l.Debugln("there is no md5 to compare with")
}
} else if actualMD5 != expectedMD5 {
// There was an md5 file included in the archive, and it doesn't
// match what we just wrote to disk.
return "", fmt.Errorf("incorrect MD5 checksum")
if expectedMD5 == "" || actualMD5 == expectedMD5 {
return tempName, nil
}
return tempName, nil
os.Remove(tempName)
// There was an md5 file included in the archive, and it doesn't
// match what we just wrote to disk.
return "", fmt.Errorf("incorrect MD5 checksum")
}
return "", fmt.Errorf("No upgrade found")
}

View File

@@ -7,7 +7,7 @@
<lenientMtimes>false</lenientMtimes>
<copiers>1</copiers>
<pullers>16</pullers>
<finishers>1</finishers>
<hashers>0</hashers>
</folder>
<folder id="s12" path="s12-2" ro="false" rescanIntervalS="15" ignorePerms="false">
<device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU"></device>
@@ -16,7 +16,7 @@
<lenientMtimes>false</lenientMtimes>
<copiers>1</copiers>
<pullers>16</pullers>
<finishers>1</finishers>
<hashers>0</hashers>
</folder>
<folder id="s23" path="s23-2" ro="false" rescanIntervalS="15" ignorePerms="false">
<device id="JMFJCXB-GZDE4BN-OCJE3VF-65GYZNU-AIVJRET-3J6HMRQ-AUQIGJO-FKNHMQU"></device>
@@ -25,7 +25,7 @@
<lenientMtimes>false</lenientMtimes>
<copiers>1</copiers>
<pullers>16</pullers>
<finishers>1</finishers>
<hashers>0</hashers>
</folder>
<device id="I6KAH76-66SLLLB-5PFXSOA-UFJCDZC-YAOMLEK-CP2GB32-BV5RQST-3PSROAU" name="s1" compression="true" introducer="false">
<address>127.0.0.1:22001</address>

1
test/logs/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*.out

View File

@@ -105,7 +105,7 @@ func testSyncCluster(t *testing.T) {
}
// We'll use this file for appending data without modifying the time stamp.
fd, err := os.Create("s1/appendfile")
fd, err := os.Create("s1/test-appendfile")
if err != nil {
t.Fatal(err)
}
@@ -207,12 +207,12 @@ func testSyncCluster(t *testing.T) {
break
}
// Alter the "appendfile" without changing it's modification time. Sneaky!
fi, err := os.Stat("s1/appendfile")
// Alter the "test-appendfile" without changing it's modification time. Sneaky!
fi, err := os.Stat("s1/test-appendfile")
if err != nil {
t.Fatal(err)
}
fd, err := os.OpenFile("s1/appendfile", os.O_APPEND|os.O_WRONLY, 0644)
fd, err := os.OpenFile("s1/test-appendfile", os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
t.Fatal(err)
}
@@ -228,7 +228,7 @@ func testSyncCluster(t *testing.T) {
if err != nil {
t.Fatal(err)
}
err = os.Chtimes("s1/appendfile", fi.ModTime(), fi.ModTime())
err = os.Chtimes("s1/test-appendfile", fi.ModTime(), fi.ModTime())
if err != nil {
t.Fatal(err)
}

View File

@@ -50,7 +50,7 @@ type syncthingProcess struct {
func (p *syncthingProcess) start() error {
if p.logfd == nil {
logfd, err := os.Create(p.instance + ".out")
logfd, err := os.Create("logs/" + getTestName() + "-" + p.instance + ".out")
if err != nil {
return err
}
@@ -94,7 +94,7 @@ func (p *syncthingProcess) stop() error {
p.cmd.Process.Signal(os.Kill)
p.cmd.Wait()
fd, err := os.Open(p.instance + ".out")
fd, err := os.Open(p.logfd.Name())
if err != nil {
return err
}

View File

@@ -27,6 +27,7 @@ import (
"math/rand"
"os"
"path/filepath"
"runtime"
"sort"
"strings"
"time"
@@ -116,6 +117,10 @@ func alterFiles(dir string) error {
return err
}
if strings.HasPrefix(filepath.Base(path), "test-") {
return nil
}
switch filepath.Base(path) {
case ".stfolder":
return nil
@@ -161,6 +166,17 @@ func alterFiles(dir string) error {
if err != nil {
return err
}
case r < 0.3 && comps > 1 && (info.Mode().IsRegular() || rand.Float64() < 0.2):
rpath := filepath.Dir(path)
if rand.Float64() < 0.2 {
for move := rand.Intn(comps - 1); move > 0; move-- {
rpath = filepath.Join(rpath, "..")
}
}
err = os.Rename(path, filepath.Join(rpath, randomName()))
if err != nil {
return err
}
}
return nil
})
@@ -426,3 +442,21 @@ func isTimeout(err error) bool {
return strings.Contains(err.Error(), "use of closed network connection") ||
strings.Contains(err.Error(), "request cancelled while waiting")
}
func getTestName() string {
callers := make([]uintptr, 100)
runtime.Callers(1, callers)
for i, caller := range callers {
f := runtime.FuncForPC(caller)
if f != nil {
if f.Name() == "testing.tRunner" {
testf := runtime.FuncForPC(callers[i-1])
if testf != nil {
path := strings.Split(testf.Name(), ".")
return path[len(path)-1]
}
}
}
}
return time.Now().String()
}