Compare commits

...
Author SHA1 Message Date
Jakob Borg bc0a8fcc1d Use language from query parameter 2014-07-22 20:27:36 +02:00
Jakob Borg 3b4fe19dfb Use compiled in assets for those not in STGUIASSETS dir 2014-07-22 20:11:36 +02:00
Jakob Borg d3085a4127 Always ignore directory modification time (that stuff is nasty) 2014-07-21 10:50:15 +02:00
Jakob Borg 0fcc193197 Handle disconnected nodes better in puller 2014-07-21 10:50:10 +02:00
Jakob Borg 75d4d2df8b Remove SyncOrder, at least temporarily (sorry fREW)
Doesn't actually work very well with the batched approach to needed
files, not documented, not exposed in UI. I'll be happy to reintegrate
if this is solved.
2014-07-21 10:49:18 +02:00
10 changed files with 61 additions and 229 deletions
+1 -1
View File
File diff suppressed because one or more lines are too long
+33 -24
View File
@@ -15,6 +15,7 @@ import (
"mime"
"net"
"net/http"
"os"
"path/filepath"
"reflect"
"runtime"
@@ -128,11 +129,7 @@ func startGUI(cfg config.GUIConfiguration, assetDir string, m *model.Model) erro
mux.HandleFunc("/qr/", getQR)
// Serve compiled in assets unless an asset directory was set (for development)
if len(assetDir) > 0 {
mux.Handle("/", http.FileServer(http.Dir(assetDir)))
} else {
mux.HandleFunc("/", embeddedStatic)
}
mux.Handle("/", embeddedStatic(assetDir))
// Wrap everything in CSRF protection. The /rest prefix should be
// protected, other requests will grant cookies.
@@ -536,28 +533,40 @@ func validAPIKey(k string) bool {
return len(apiKey) > 0 && k == apiKey
}
func embeddedStatic(w http.ResponseWriter, r *http.Request) {
file := r.URL.Path
func embeddedStatic(assetDir string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
file := r.URL.Path
if file[0] == '/' {
file = file[1:]
}
if file[0] == '/' {
file = file[1:]
}
if len(file) == 0 {
file = "index.html"
}
if len(file) == 0 {
file = "index.html"
}
bs, ok := auto.Assets[file]
if !ok {
return
}
if assetDir != "" {
p := filepath.Join(assetDir, filepath.FromSlash(file))
_, err := os.Stat(p)
if err == nil {
http.ServeFile(w, r, p)
return
}
}
mtype := mime.TypeByExtension(filepath.Ext(r.URL.Path))
if len(mtype) != 0 {
w.Header().Set("Content-Type", mtype)
}
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(bs)))
w.Header().Set("Last-Modified", modt)
bs, ok := auto.Assets[file]
if !ok {
http.NotFound(w, r)
return
}
w.Write(bs)
mtype := mime.TypeByExtension(filepath.Ext(r.URL.Path))
if len(mtype) != 0 {
w.Header().Set("Content-Type", mtype)
}
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(bs)))
w.Header().Set("Last-Modified", modt)
w.Write(bs)
})
}
+7 -51
View File
@@ -11,7 +11,6 @@ import (
"io"
"os"
"reflect"
"regexp"
"sort"
"strconv"
@@ -31,42 +30,14 @@ type Configuration struct {
XMLName xml.Name `xml:"configuration" json:"-"`
}
// SyncOrderPattern allows a user to prioritize file downloading based on a
// regular expression. If a file matches the Pattern the Priority will be
// assigned to the file. If a file matches more than one Pattern the
// Priorities are summed. This allows a user to, for example, prioritize files
// in a directory, as well as prioritize based on file type. The higher the
// priority the "sooner" a file will be downloaded. Files can be deprioritized
// by giving them a negative priority. While Priority is represented as an
// integer, the expected range is something like -1000 to 1000.
type SyncOrderPattern struct {
Pattern string `xml:"pattern,attr"`
Priority int `xml:"priority,attr"`
compiledPattern *regexp.Regexp
}
func (s *SyncOrderPattern) CompiledPattern() *regexp.Regexp {
if s.compiledPattern == nil {
re, err := regexp.Compile(s.Pattern)
if err != nil {
l.Warnln("Could not compile regexp (" + s.Pattern + "): " + err.Error())
s.compiledPattern = regexp.MustCompile("^\\0$")
} else {
s.compiledPattern = re
}
}
return s.compiledPattern
}
type RepositoryConfiguration struct {
ID string `xml:"id,attr"`
Directory string `xml:"directory,attr"`
Nodes []NodeConfiguration `xml:"node"`
ReadOnly bool `xml:"ro,attr"`
IgnorePerms bool `xml:"ignorePerms,attr"`
Invalid string `xml:"-"` // Set at runtime when there is an error, not saved
Versioning VersioningConfiguration `xml:"versioning"`
SyncOrderPatterns []SyncOrderPattern `xml:"syncorder>pattern"`
ID string `xml:"id,attr"`
Directory string `xml:"directory,attr"`
Nodes []NodeConfiguration `xml:"node"`
ReadOnly bool `xml:"ro,attr"`
IgnorePerms bool `xml:"ignorePerms,attr"`
Invalid string `xml:"-"` // Set at runtime when there is an error, not saved
Versioning VersioningConfiguration `xml:"versioning"`
nodeIDs []protocol.NodeID
}
@@ -121,21 +92,6 @@ func (r *RepositoryConfiguration) NodeIDs() []protocol.NodeID {
return r.nodeIDs
}
func (r RepositoryConfiguration) FileRanker() func(protocol.FileInfo) int {
if len(r.SyncOrderPatterns) <= 0 {
return nil
}
return func(f protocol.FileInfo) int {
ret := 0
for _, v := range r.SyncOrderPatterns {
if v.CompiledPattern().MatchString(f.Name) {
ret += v.Priority
}
}
return ret
}
}
type NodeConfiguration struct {
NodeID protocol.NodeID `xml:"id,attr"`
Name string `xml:"name,attr,omitempty"`
-90
View File
@@ -11,7 +11,6 @@ import (
"reflect"
"testing"
"github.com/calmh/syncthing/files"
"github.com/calmh/syncthing/protocol"
)
@@ -233,92 +232,3 @@ func TestNodeAddresses(t *testing.T) {
t.Errorf("Nodes differ;\n E: %#v\n A: %#v", expected, cfg.Nodes)
}
}
func TestSyncOrders(t *testing.T) {
data := []byte(`
<configuration version="2">
<repository directory="~/Sync">
<syncorder>
<pattern pattern="\.jpg$" priority="1" />
</syncorder>
</repository>
</configuration>
`)
expected := []SyncOrderPattern{
{
Pattern: "\\.jpg$",
Priority: 1,
},
}
cfg, err := Load(bytes.NewReader(data), node1)
if err != nil {
t.Error(err)
}
for i := range expected {
if !reflect.DeepEqual(cfg.Repositories[0].SyncOrderPatterns[i], expected[i]) {
t.Errorf("Patterns[%d] differ;\n E: %#v\n A: %#v", i, expected[i], cfg.Repositories[0].SyncOrderPatterns[i])
}
}
}
func TestFileSorter(t *testing.T) {
rcfg := RepositoryConfiguration{
SyncOrderPatterns: []SyncOrderPattern{
{"\\.jpg$", 10, nil},
{"\\.mov$", 5, nil},
{"^camera-uploads", 100, nil},
},
}
f := []protocol.FileInfo{
{Name: "bar.mov"},
{Name: "baz.txt"},
{Name: "foo.jpg"},
{Name: "frew/foo.jpg"},
{Name: "frew/lol.go"},
{Name: "frew/rofl.copter"},
{Name: "frew/bar.mov"},
{Name: "camera-uploads/foo.jpg"},
{Name: "camera-uploads/hurr.pl"},
{Name: "camera-uploads/herp.mov"},
{Name: "camera-uploads/wee.txt"},
}
files.SortBy(rcfg.FileRanker()).Sort(f)
expected := []protocol.FileInfo{
{Name: "camera-uploads/foo.jpg"},
{Name: "camera-uploads/herp.mov"},
{Name: "camera-uploads/hurr.pl"},
{Name: "camera-uploads/wee.txt"},
{Name: "foo.jpg"},
{Name: "frew/foo.jpg"},
{Name: "bar.mov"},
{Name: "frew/bar.mov"},
{Name: "frew/lol.go"},
{Name: "baz.txt"},
{Name: "frew/rofl.copter"},
}
if !reflect.DeepEqual(f, expected) {
t.Errorf(
"\n\nexpected:\n" +
formatFiles(expected) + "\n" +
"got:\n" +
formatFiles(f) + "\n\n",
)
}
}
func formatFiles(f []protocol.FileInfo) string {
ret := ""
for _, v := range f {
ret += " " + v.Name + "\n"
}
return ret
}
-33
View File
@@ -1,33 +0,0 @@
package files
import (
"github.com/calmh/syncthing/protocol"
"sort"
)
type SortBy func(p protocol.FileInfo) int
func (by SortBy) Sort(files []protocol.FileInfo) {
ps := &fileSorter{
files: files,
by: by,
}
sort.Sort(ps)
}
type fileSorter struct {
files []protocol.FileInfo
by func(p1 protocol.FileInfo) int
}
func (s *fileSorter) Len() int {
return len(s.files)
}
func (s *fileSorter) Swap(i, j int) {
s.files[i], s.files[j] = s.files[j], s.files[i]
}
func (s *fileSorter) Less(i, j int) bool {
return s.by(s.files[i]) > s.by(s.files[j])
}
+8 -1
View File
@@ -21,7 +21,7 @@ syncthing.config(function ($httpProvider, $translateProvider) {
$translateProvider.preferredLanguage('en');
});
syncthing.controller('SyncthingCtrl', function ($scope, $http, $translate) {
syncthing.controller('SyncthingCtrl', function ($scope, $http, $translate, $location) {
var prevDate = 0;
var getOK = true;
var restarting = false;
@@ -40,6 +40,13 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http, $translate) {
$scope.reportPreview = false;
$scope.upgradeInfo = {};
$scope.$on("$locationChangeSuccess", function () {
var lang = $location.search().lang;
if (lang) {
$translate.use(lang);
}
});
$scope.needActions = {
'rm': 'Del',
'rmdir': 'Del (dir)',
-3
View File
@@ -318,9 +318,6 @@ func (m *Model) NeedFilesRepo(repo string) []protocol.FileInfo {
fs = append(fs, f)
return len(fs) < indexBatchSize
})
if r := m.repoCfgs[repo].FileRanker(); r != nil {
files.SortBy(r).Sort(fs)
}
return fs
}
return nil
+7 -4
View File
@@ -242,17 +242,20 @@ func BenchmarkRequest(b *testing.B) {
}
func TestActivityMap(t *testing.T) {
isValid := func(protocol.NodeID) bool {
return true
}
m := make(activityMap)
if node := m.leastBusyNode([]protocol.NodeID{node1}); node != node1 {
if node := m.leastBusyNode([]protocol.NodeID{node1}, isValid); node != node1 {
t.Errorf("Incorrect least busy node %q", node)
}
if node := m.leastBusyNode([]protocol.NodeID{node2}); node != node2 {
if node := m.leastBusyNode([]protocol.NodeID{node2}, isValid); node != node2 {
t.Errorf("Incorrect least busy node %q", node)
}
if node := m.leastBusyNode([]protocol.NodeID{node1, node2}); node != node1 {
if node := m.leastBusyNode([]protocol.NodeID{node1, node2}, isValid); node != node1 {
t.Errorf("Incorrect least busy node %q", node)
}
if node := m.leastBusyNode([]protocol.NodeID{node1, node2}); node != node2 {
if node := m.leastBusyNode([]protocol.NodeID{node1, node2}, isValid); node != node2 {
t.Errorf("Incorrect least busy node %q", node)
}
}
+4 -21
View File
@@ -9,7 +9,6 @@ import (
"errors"
"os"
"path/filepath"
"runtime"
"time"
"github.com/calmh/syncthing/config"
@@ -41,12 +40,12 @@ type openFile struct {
type activityMap map[protocol.NodeID]int
func (m activityMap) leastBusyNode(availability []protocol.NodeID) protocol.NodeID {
func (m activityMap) leastBusyNode(availability []protocol.NodeID, isValid func(protocol.NodeID) bool) protocol.NodeID {
var low int = 2<<30 - 1
var selected protocol.NodeID
for _, node := range availability {
usage := m[node]
if usage < low {
if usage < low && isValid(node) {
low = usage
selected = node
}
@@ -278,22 +277,6 @@ func (p *puller) fixupDirectories() {
}
}
if cur.Modified != info.ModTime().Unix() {
t := time.Unix(cur.Modified, 0)
err := os.Chtimes(path, t, t)
if err != nil {
if runtime.GOOS != "windows" {
// https://code.google.com/p/go/issues/detail?id=8090
l.Warnf("Restoring folder modtime: %q: %v", path, err)
}
} else {
changed++
if debug {
l.Debugf("restored dir modtime: %d -> %v", info.ModTime().Unix(), cur)
}
}
}
return nil
}
@@ -373,7 +356,7 @@ func (p *puller) handleBlock(b bqBlock) bool {
if debug {
l.Debugf("create dir: %v", f)
}
err = os.MkdirAll(path, 0777)
err = os.MkdirAll(path, os.FileMode(f.Flags&0777))
if err != nil {
l.Warnf("Create folder: %q: %v", path, err)
}
@@ -533,7 +516,7 @@ func (p *puller) handleRequestBlock(b bqBlock) bool {
panic("bug: request for non-open file")
}
node := p.oustandingPerNode.leastBusyNode(of.availability)
node := p.oustandingPerNode.leastBusyNode(of.availability, p.model.ConnectedTo)
if node == (protocol.NodeID{}) {
of.err = errNoNode
if of.file != nil {
+1 -1
View File
@@ -166,7 +166,7 @@ func (w *Walker) walkAndHashFiles(fchan chan protocol.FileInfo, ign map[string][
if w.CurrentFiler != nil {
cf := w.CurrentFiler.CurrentFile(rn)
permUnchanged := w.IgnorePerms || !protocol.HasPermissionBits(cf.Flags) || PermsEqual(cf.Flags, uint32(info.Mode()))
if !protocol.IsDeleted(cf.Flags) && cf.Modified == info.ModTime().Unix() && protocol.IsDirectory(cf.Flags) && permUnchanged {
if !protocol.IsDeleted(cf.Flags) && protocol.IsDirectory(cf.Flags) && permUnchanged {
if debug {
l.Debugln("unchanged:", cf)
}