Compare commits
5
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc0a8fcc1d | ||
|
|
3b4fe19dfb | ||
|
|
d3085a4127 | ||
|
|
0fcc193197 | ||
|
|
75d4d2df8b |
+1
-1
File diff suppressed because one or more lines are too long
+33
-24
@@ -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
@@ -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"`
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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)',
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user