mirror of
https://github.com/syncthing/syncthing.git
synced 2026-01-20 11:48:06 -05:00
Compare commits
16 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e5dc66e7e5 | ||
|
|
bb01b76582 | ||
|
|
f1aff0fd96 | ||
|
|
5656be5206 | ||
|
|
484ce8e488 | ||
|
|
dcadefd133 | ||
|
|
28366677b0 | ||
|
|
d65302742c | ||
|
|
b2cf28efdd | ||
|
|
41e20bb6b7 | ||
|
|
6e670a2499 | ||
|
|
481b2186cb | ||
|
|
1bc1c0b14f | ||
|
|
e9d27b9d2b | ||
|
|
828bbc407f | ||
|
|
e50469d84e |
@@ -16,7 +16,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"crypto/tls"
|
||||
"flag"
|
||||
"fmt"
|
||||
@@ -358,40 +357,7 @@ func syncthingMain() {
|
||||
} else {
|
||||
l.Infoln("No config file; starting with empty defaults")
|
||||
myName, _ = os.Hostname()
|
||||
defaultFolder, err := osutil.ExpandTilde("~/Sync")
|
||||
if err != nil {
|
||||
l.Fatalln("home:", err)
|
||||
}
|
||||
|
||||
newCfg := config.New(myID)
|
||||
newCfg.Folders = []config.FolderConfiguration{
|
||||
{
|
||||
ID: "default",
|
||||
Path: defaultFolder,
|
||||
RescanIntervalS: 60,
|
||||
Devices: []config.FolderDeviceConfiguration{{DeviceID: myID}},
|
||||
},
|
||||
}
|
||||
newCfg.Devices = []config.DeviceConfiguration{
|
||||
{
|
||||
DeviceID: myID,
|
||||
Addresses: []string{"dynamic"},
|
||||
Name: myName,
|
||||
},
|
||||
}
|
||||
|
||||
port, err := getFreePort("127.0.0.1", 8080)
|
||||
if err != nil {
|
||||
l.Fatalln("get free port (GUI):", err)
|
||||
}
|
||||
newCfg.GUI.Address = fmt.Sprintf("127.0.0.1:%d", port)
|
||||
|
||||
port, err = getFreePort("0.0.0.0", 22000)
|
||||
if err != nil {
|
||||
l.Fatalln("get free port (BEP):", err)
|
||||
}
|
||||
newCfg.Options.ListenAddress = []string{fmt.Sprintf("0.0.0.0:%d", port)}
|
||||
|
||||
newCfg := defaultConfig(myName)
|
||||
cfg = config.Wrap(cfgFile, newCfg)
|
||||
cfg.Save()
|
||||
l.Infof("Edit %s to taste or use the GUI\n", cfgFile)
|
||||
@@ -441,9 +407,6 @@ func syncthingMain() {
|
||||
readRateLimit = ratelimit.NewBucketWithRate(float64(1000*opts.MaxRecvKbps), int64(5*1000*opts.MaxRecvKbps))
|
||||
}
|
||||
|
||||
// If this is the first time the user runs v0.9, archive the old indexes and config.
|
||||
archiveLegacyConfig()
|
||||
|
||||
db, err := leveldb.OpenFile(filepath.Join(confDir, "index"), &opt.Options{CachedOpenFiles: 100})
|
||||
if err != nil {
|
||||
l.Fatalln("Cannot open database:", err, "- Is another copy of Syncthing already running?")
|
||||
@@ -460,82 +423,11 @@ func syncthingMain() {
|
||||
|
||||
m := model.NewModel(cfg, myName, "syncthing", Version, db)
|
||||
|
||||
nextFolder:
|
||||
for id, folder := range cfg.Folders() {
|
||||
if folder.Invalid != "" {
|
||||
continue
|
||||
}
|
||||
folder.Path, err = osutil.ExpandTilde(folder.Path)
|
||||
if err != nil {
|
||||
l.Fatalln("home:", err)
|
||||
}
|
||||
m.AddFolder(folder)
|
||||
|
||||
fi, err := os.Stat(folder.Path)
|
||||
if m.CurrentLocalVersion(id) > 0 {
|
||||
// Safety check. If the cached index contains files but the
|
||||
// folder doesn't exist, we have a problem. We would assume
|
||||
// that all files have been deleted which might not be the case,
|
||||
// so mark it as invalid instead.
|
||||
if err != nil || !fi.IsDir() {
|
||||
l.Warnf("Stopping folder %q - path does not exist, but has files in index", folder.ID)
|
||||
cfg.InvalidateFolder(id, "folder path missing")
|
||||
continue nextFolder
|
||||
}
|
||||
} else if os.IsNotExist(err) {
|
||||
// If we don't have any files in the index, and the directory
|
||||
// doesn't exist, try creating it.
|
||||
err = os.MkdirAll(folder.Path, 0700)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// If there was another error or we could not create the
|
||||
// path, the folder is invalid.
|
||||
l.Warnf("Stopping folder %q - %v", err)
|
||||
cfg.InvalidateFolder(id, err.Error())
|
||||
continue nextFolder
|
||||
}
|
||||
}
|
||||
sanityCheckFolders(cfg, m)
|
||||
|
||||
// GUI
|
||||
|
||||
guiCfg := overrideGUIConfig(cfg.GUI(), guiAddress, guiAuthentication, guiAPIKey)
|
||||
|
||||
if guiCfg.Enabled && guiCfg.Address != "" {
|
||||
addr, err := net.ResolveTCPAddr("tcp", guiCfg.Address)
|
||||
if err != nil {
|
||||
l.Fatalf("Cannot start GUI on %q: %v", guiCfg.Address, err)
|
||||
} else {
|
||||
var hostOpen, hostShow string
|
||||
switch {
|
||||
case addr.IP == nil:
|
||||
hostOpen = "localhost"
|
||||
hostShow = "0.0.0.0"
|
||||
case addr.IP.IsUnspecified():
|
||||
hostOpen = "localhost"
|
||||
hostShow = addr.IP.String()
|
||||
default:
|
||||
hostOpen = addr.IP.String()
|
||||
hostShow = hostOpen
|
||||
}
|
||||
|
||||
var proto = "http"
|
||||
if guiCfg.UseTLS {
|
||||
proto = "https"
|
||||
}
|
||||
|
||||
urlShow := fmt.Sprintf("%s://%s/", proto, net.JoinHostPort(hostShow, strconv.Itoa(addr.Port)))
|
||||
l.Infoln("Starting web GUI on", urlShow)
|
||||
err := startGUI(guiCfg, guiAssets, m)
|
||||
if err != nil {
|
||||
l.Fatalln("Cannot start GUI:", err)
|
||||
}
|
||||
if opts.StartBrowser && !noBrowser && !stRestarting {
|
||||
urlOpen := fmt.Sprintf("%s://%s/", proto, net.JoinHostPort(hostOpen, strconv.Itoa(addr.Port)))
|
||||
openURL(urlOpen)
|
||||
}
|
||||
}
|
||||
}
|
||||
setupGUI(cfg, m)
|
||||
|
||||
// Clear out old indexes for other devices. Otherwise we'll start up and
|
||||
// start needing a bunch of files which are nowhere to be found. This
|
||||
@@ -552,32 +444,6 @@ nextFolder:
|
||||
}
|
||||
}
|
||||
|
||||
// Remove all .idx* files that don't belong to an active folder.
|
||||
|
||||
validIndexes := make(map[string]bool)
|
||||
for _, folder := range cfg.Folders() {
|
||||
dir, err := osutil.ExpandTilde(folder.Path)
|
||||
if err != nil {
|
||||
l.Fatalln("home:", err)
|
||||
}
|
||||
id := fmt.Sprintf("%x", sha1.Sum([]byte(dir)))
|
||||
validIndexes[id] = true
|
||||
}
|
||||
|
||||
allIndexes, err := filepath.Glob(filepath.Join(confDir, "*.idx*"))
|
||||
if err == nil {
|
||||
for _, idx := range allIndexes {
|
||||
bn := filepath.Base(idx)
|
||||
fs := strings.Split(bn, ".")
|
||||
if len(fs) > 1 {
|
||||
if _, ok := validIndexes[fs[0]]; !ok {
|
||||
l.Infoln("Removing old index", bn)
|
||||
os.Remove(idx)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The default port we announce, possibly modified by setupUPnP next.
|
||||
|
||||
addr, err := net.ResolveTCPAddr("tcp", opts.ListenAddress[0])
|
||||
@@ -660,6 +526,125 @@ nextFolder:
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func setupGUI(cfg *config.ConfigWrapper, m *model.Model) {
|
||||
opts := cfg.Options()
|
||||
guiCfg := overrideGUIConfig(cfg.GUI(), guiAddress, guiAuthentication, guiAPIKey)
|
||||
|
||||
if guiCfg.Enabled && guiCfg.Address != "" {
|
||||
addr, err := net.ResolveTCPAddr("tcp", guiCfg.Address)
|
||||
if err != nil {
|
||||
l.Fatalf("Cannot start GUI on %q: %v", guiCfg.Address, err)
|
||||
} else {
|
||||
var hostOpen, hostShow string
|
||||
switch {
|
||||
case addr.IP == nil:
|
||||
hostOpen = "localhost"
|
||||
hostShow = "0.0.0.0"
|
||||
case addr.IP.IsUnspecified():
|
||||
hostOpen = "localhost"
|
||||
hostShow = addr.IP.String()
|
||||
default:
|
||||
hostOpen = addr.IP.String()
|
||||
hostShow = hostOpen
|
||||
}
|
||||
|
||||
var proto = "http"
|
||||
if guiCfg.UseTLS {
|
||||
proto = "https"
|
||||
}
|
||||
|
||||
urlShow := fmt.Sprintf("%s://%s/", proto, net.JoinHostPort(hostShow, strconv.Itoa(addr.Port)))
|
||||
l.Infoln("Starting web GUI on", urlShow)
|
||||
err := startGUI(guiCfg, guiAssets, m)
|
||||
if err != nil {
|
||||
l.Fatalln("Cannot start GUI:", err)
|
||||
}
|
||||
if opts.StartBrowser && !noBrowser && !stRestarting {
|
||||
urlOpen := fmt.Sprintf("%s://%s/", proto, net.JoinHostPort(hostOpen, strconv.Itoa(addr.Port)))
|
||||
openURL(urlOpen)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func sanityCheckFolders(cfg *config.ConfigWrapper, m *model.Model) {
|
||||
var err error
|
||||
|
||||
nextFolder:
|
||||
for id, folder := range cfg.Folders() {
|
||||
if folder.Invalid != "" {
|
||||
continue
|
||||
}
|
||||
folder.Path, err = osutil.ExpandTilde(folder.Path)
|
||||
if err != nil {
|
||||
l.Fatalln("home:", err)
|
||||
}
|
||||
m.AddFolder(folder)
|
||||
|
||||
fi, err := os.Stat(folder.Path)
|
||||
if m.CurrentLocalVersion(id) > 0 {
|
||||
// Safety check. If the cached index contains files but the
|
||||
// folder doesn't exist, we have a problem. We would assume
|
||||
// that all files have been deleted which might not be the case,
|
||||
// so mark it as invalid instead.
|
||||
if err != nil || !fi.IsDir() {
|
||||
l.Warnf("Stopping folder %q - path does not exist, but has files in index", folder.ID)
|
||||
cfg.InvalidateFolder(id, "folder path missing")
|
||||
continue nextFolder
|
||||
}
|
||||
} else if os.IsNotExist(err) {
|
||||
// If we don't have any files in the index, and the directory
|
||||
// doesn't exist, try creating it.
|
||||
err = os.MkdirAll(folder.Path, 0700)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// If there was another error or we could not create the
|
||||
// path, the folder is invalid.
|
||||
l.Warnf("Stopping folder %q - %v", err)
|
||||
cfg.InvalidateFolder(id, err.Error())
|
||||
continue nextFolder
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func defaultConfig(myName string) config.Configuration {
|
||||
defaultFolder, err := osutil.ExpandTilde("~/Sync")
|
||||
if err != nil {
|
||||
l.Fatalln("home:", err)
|
||||
}
|
||||
|
||||
newCfg := config.New(myID)
|
||||
newCfg.Folders = []config.FolderConfiguration{
|
||||
{
|
||||
ID: "default",
|
||||
Path: defaultFolder,
|
||||
RescanIntervalS: 60,
|
||||
Devices: []config.FolderDeviceConfiguration{{DeviceID: myID}},
|
||||
},
|
||||
}
|
||||
newCfg.Devices = []config.DeviceConfiguration{
|
||||
{
|
||||
DeviceID: myID,
|
||||
Addresses: []string{"dynamic"},
|
||||
Name: myName,
|
||||
},
|
||||
}
|
||||
|
||||
port, err := getFreePort("127.0.0.1", 8080)
|
||||
if err != nil {
|
||||
l.Fatalln("get free port (GUI):", err)
|
||||
}
|
||||
newCfg.GUI.Address = fmt.Sprintf("127.0.0.1:%d", port)
|
||||
|
||||
port, err = getFreePort("0.0.0.0", 22000)
|
||||
if err != nil {
|
||||
l.Fatalln("get free port (BEP):", err)
|
||||
}
|
||||
newCfg.Options.ListenAddress = []string{fmt.Sprintf("0.0.0.0:%d", port)}
|
||||
return newCfg
|
||||
}
|
||||
|
||||
func generateEvents() {
|
||||
for {
|
||||
time.Sleep(300 * time.Second)
|
||||
@@ -759,42 +744,6 @@ func resetFolders() {
|
||||
os.RemoveAll(idx)
|
||||
}
|
||||
|
||||
func archiveLegacyConfig() {
|
||||
pat := filepath.Join(confDir, "*.idx.gz*")
|
||||
idxs, err := filepath.Glob(pat)
|
||||
if err == nil && len(idxs) > 0 {
|
||||
// There are legacy indexes. This is probably the first time we run as v0.9.
|
||||
backupDir := filepath.Join(confDir, "backup-of-v0.8")
|
||||
err = os.MkdirAll(backupDir, 0700)
|
||||
if err != nil {
|
||||
l.Warnln("Cannot archive config/indexes:", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, idx := range idxs {
|
||||
l.Infof("Archiving %s", filepath.Base(idx))
|
||||
os.Rename(idx, filepath.Join(backupDir, filepath.Base(idx)))
|
||||
}
|
||||
|
||||
src, err := os.Open(filepath.Join(confDir, "config.xml"))
|
||||
if err != nil {
|
||||
l.Warnf("Cannot archive config:", err)
|
||||
return
|
||||
}
|
||||
defer src.Close()
|
||||
|
||||
dst, err := os.Create(filepath.Join(backupDir, "config.xml"))
|
||||
if err != nil {
|
||||
l.Warnf("Cannot archive config:", err)
|
||||
return
|
||||
}
|
||||
defer dst.Close()
|
||||
|
||||
l.Infoln("Archiving config.xml")
|
||||
io.Copy(dst, src)
|
||||
}
|
||||
}
|
||||
|
||||
func restart() {
|
||||
l.Infoln("Restarting")
|
||||
stop <- exitRestarting
|
||||
|
||||
@@ -78,6 +78,10 @@ func monitorMain() {
|
||||
l.Fatalln(err)
|
||||
}
|
||||
|
||||
// Let the next child process know that this is not the first time
|
||||
// it's starting up.
|
||||
os.Setenv("STRESTART", "yes")
|
||||
|
||||
stdoutMut.Lock()
|
||||
stdoutFirstLines = make([]string, 0, 10)
|
||||
stdoutLastLines = make([]string, 0, 50)
|
||||
@@ -123,10 +127,6 @@ func monitorMain() {
|
||||
|
||||
l.Infoln("Syncthing exited:", err)
|
||||
time.Sleep(1 * time.Second)
|
||||
|
||||
// Let the next child process know that this is not the first time
|
||||
// it's starting up.
|
||||
os.Setenv("STRESTART", "yes")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,7 +153,6 @@ func copyStderr(stderr io.ReadCloser) {
|
||||
l.Warnf("Panic detected, writing to \"%s\"", panicFd.Name())
|
||||
l.Warnln("Please create an issue at https://github.com/syncthing/syncthing/issues/ with the panic log attached")
|
||||
|
||||
panicFd.WriteString("Panic at " + time.Now().Format(time.RFC1123) + "\n")
|
||||
stdoutMut.Lock()
|
||||
for _, line := range stdoutFirstLines {
|
||||
panicFd.WriteString(line)
|
||||
@@ -162,7 +161,10 @@ func copyStderr(stderr io.ReadCloser) {
|
||||
for _, line := range stdoutLastLines {
|
||||
panicFd.WriteString(line)
|
||||
}
|
||||
stdoutMut.Unlock()
|
||||
}
|
||||
|
||||
panicFd.WriteString("Panic at " + time.Now().Format(time.RFC3339) + "\n")
|
||||
}
|
||||
|
||||
if panicFd != nil {
|
||||
@@ -182,11 +184,12 @@ func copyStdout(stderr io.ReadCloser) {
|
||||
stdoutMut.Lock()
|
||||
if len(stdoutFirstLines) < cap(stdoutFirstLines) {
|
||||
stdoutFirstLines = append(stdoutFirstLines, line)
|
||||
} else {
|
||||
if l := len(stdoutLastLines); l == cap(stdoutLastLines) {
|
||||
stdoutLastLines = stdoutLastLines[:l-1]
|
||||
}
|
||||
stdoutLastLines = append(stdoutLastLines, line)
|
||||
}
|
||||
if l := len(stdoutLastLines); l == cap(stdoutLastLines) {
|
||||
stdoutLastLines = stdoutLastLines[:l-1]
|
||||
}
|
||||
stdoutLastLines = append(stdoutLastLines, line)
|
||||
stdoutMut.Unlock()
|
||||
|
||||
os.Stdout.WriteString(line)
|
||||
|
||||
34
gui/app.js
34
gui/app.js
@@ -20,10 +20,24 @@
|
||||
|
||||
var syncthing = angular.module('syncthing', ['pascalprecht.translate']);
|
||||
var urlbase = 'rest';
|
||||
var guiVersion = null;
|
||||
|
||||
syncthing.config(function ($httpProvider, $translateProvider) {
|
||||
$httpProvider.defaults.xsrfHeaderName = 'X-CSRF-Token';
|
||||
$httpProvider.defaults.xsrfCookieName = 'CSRF-Token';
|
||||
$httpProvider.interceptors.push(function() {
|
||||
return {
|
||||
response: function(response) {
|
||||
var responseVersion = response.headers()['x-syncthing-version'];
|
||||
if (!guiVersion) {
|
||||
guiVersion = responseVersion;
|
||||
} else if (guiVersion != responseVersion) {
|
||||
document.location.reload(true);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
$translateProvider.useStaticFilesLoader({
|
||||
prefix: 'lang/lang-',
|
||||
@@ -164,17 +178,13 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http, $translate, $loca
|
||||
return;
|
||||
}
|
||||
|
||||
if (restarting) {
|
||||
document.location.reload(true);
|
||||
} else {
|
||||
console.log('UIOnline');
|
||||
$scope.init();
|
||||
online = true;
|
||||
restarting = false;
|
||||
$('#networkError').modal('hide');
|
||||
$('#restarting').modal('hide');
|
||||
$('#shutdown').modal('hide');
|
||||
}
|
||||
console.log('UIOnline');
|
||||
$scope.init();
|
||||
online = true;
|
||||
restarting = false;
|
||||
$('#networkError').modal('hide');
|
||||
$('#restarting').modal('hide');
|
||||
$('#shutdown').modal('hide');
|
||||
});
|
||||
|
||||
$scope.$on('UIOffline', function (event, arg) {
|
||||
@@ -659,7 +669,7 @@ syncthing.controller('SyncthingCtrl', function ($scope, $http, $translate, $loca
|
||||
$scope.currentDevice = {
|
||||
AddressesStr: 'dynamic',
|
||||
Compression: true,
|
||||
Introducer: true
|
||||
Introducer: false
|
||||
};
|
||||
$scope.editingExisting = false;
|
||||
$scope.editingSelf = false;
|
||||
|
||||
@@ -43,7 +43,7 @@
|
||||
"Generate": "Générer",
|
||||
"Global Discovery": "Recherche globale",
|
||||
"Global Discovery Server": "Serveur global de recherche",
|
||||
"Global State": "Etat global",
|
||||
"Global State": "État global",
|
||||
"Idle": "Au repos",
|
||||
"Ignore Patterns": "Modèles à éviter",
|
||||
"Ignore Permissions": "Ignorer les permissions",
|
||||
@@ -54,7 +54,7 @@
|
||||
"Last seen": "Dernière apparition",
|
||||
"Latest Release": "Dernière version",
|
||||
"Local Discovery": "Recherche locale",
|
||||
"Local State": "Etat local",
|
||||
"Local State": "État local",
|
||||
"Maximum Age": "Ancienneté maximum",
|
||||
"Multi level wildcard (matches multiple directory levels)": "Astérisque à plusieurs niveaux (correspond aux répertoires et sous-répertoires)",
|
||||
"Never": "Jamais",
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
{
|
||||
"API Key": "API 金鑰",
|
||||
"About": "關於",
|
||||
"Add Device": "Add Device",
|
||||
"Add Folder": "Add Folder",
|
||||
"Add Device": "增加裝置",
|
||||
"Add Folder": "增加資料夾",
|
||||
"Address": "位址",
|
||||
"Addresses": "位址",
|
||||
"Allow 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.",
|
||||
"Any devices configured on an introducer device will be added to this device as well.": "任何在引入者裝置所設置的裝置將會一併新增至此裝置",
|
||||
"Bugs": "程式錯誤",
|
||||
"CPU Utilization": "CPU 使用率",
|
||||
"Close": "關閉",
|
||||
@@ -16,15 +16,15 @@
|
||||
"Connection Error": "連線錯誤",
|
||||
"Copyright © 2014 Jakob Borg and the following Contributors:": "版權所有 © 2014 Jakob Borg 及以下貢獻者:",
|
||||
"Delete": "刪除",
|
||||
"Device ID": "Device ID",
|
||||
"Device Identification": "Device Identification",
|
||||
"Device Name": "Device Name",
|
||||
"Device ID": "裝置識別碼",
|
||||
"Device Identification": "裝置識別",
|
||||
"Device Name": "裝置名稱",
|
||||
"Disconnected": "斷線",
|
||||
"Documentation": "說明文件",
|
||||
"Download Rate": "下載速率",
|
||||
"Edit": "編輯",
|
||||
"Edit Device": "Edit Device",
|
||||
"Edit Folder": "Edit Folder",
|
||||
"Edit Device": "編輯裝置",
|
||||
"Edit Folder": "編輯資料夾",
|
||||
"Editing": "正在編輯",
|
||||
"Enable UPnP": "啟用 UPnP",
|
||||
"Enter comma separated \"ip:port\" addresses or \"dynamic\" to perform automatic discovery of the address.": "輸入以半形逗號區隔的 \"ip:連接埠\" 位址,或著輸入 \"dynamic\" 以進行位址的自動探索",
|
||||
@@ -33,17 +33,17 @@
|
||||
"File Versioning": "檔案版本控制",
|
||||
"File permission bits are ignored when looking for changes. Use on FAT filesystems.": "當在尋找變化時,檔案權限位元會被忽略。用於 FAT 檔案系統。",
|
||||
"Files are moved to date stamped versions in a .stversions folder when replaced or deleted by syncthing.": "當檔案被 syncthing 取代或刪除時,它們將被移至 .stversions 資料夾並添加日期戳記。",
|
||||
"Files are protected from changes made on other devices, but changes made on this device will be sent to the rest of the cluster.": "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 Master": "Folder Master",
|
||||
"Folder Path": "Folder Path",
|
||||
"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 Master": "主資料夾",
|
||||
"Folder Path": "資料夾路徑",
|
||||
"GUI Authentication Password": "GUI 認證密碼",
|
||||
"GUI Authentication User": "GUI 認證使用者名稱",
|
||||
"GUI Listen Addresses": "GUI 監聽位址",
|
||||
"Generate": "產生",
|
||||
"Global Discovery": "全域探索",
|
||||
"Global Discovery Server": "全域探索伺服器",
|
||||
"Global State": "Global State",
|
||||
"Global State": "全域狀態",
|
||||
"Idle": "閒置",
|
||||
"Ignore Patterns": "忽略樣式",
|
||||
"Ignore Permissions": "忽略權限",
|
||||
@@ -54,7 +54,7 @@
|
||||
"Last seen": "最後發現時間",
|
||||
"Latest Release": "最新發佈",
|
||||
"Local Discovery": "本地探索",
|
||||
"Local State": "Local State",
|
||||
"Local State": "本地狀態",
|
||||
"Maximum Age": "最長保留時間",
|
||||
"Multi level wildcard (matches multiple directory levels)": "多階層萬用字元 (可比對多層資料夾)",
|
||||
"Never": "從未",
|
||||
@@ -66,9 +66,9 @@
|
||||
"Online": "上線",
|
||||
"Out Of Sync": "不同步",
|
||||
"Outgoing Rate Limit (KiB/s)": "連出速率限制 (KiB/s)",
|
||||
"Override Changes": "覆蓋改變",
|
||||
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for",
|
||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "Path where versions should be stored (leave empty for the default .stversions folder in the folder).",
|
||||
"Override Changes": "置換改變",
|
||||
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "資料夾在本地電腦的路徑。若資料夾不存在則會建立。波浪符號 (~) 可用作下列資料夾的捷徑:",
|
||||
"Path where versions should be stored (leave empty for the default .stversions folder in the folder).": "儲存歷史版本的路徑 (若為空,則預設使用資料夾中的 .stversions 資料夾)。",
|
||||
"Please wait": "請稍後",
|
||||
"Preview": "預覽",
|
||||
"Preview Usage Report": "預覽使用資訊報告",
|
||||
@@ -81,14 +81,14 @@
|
||||
"Restarting": "正在重新啟動",
|
||||
"Save": "儲存",
|
||||
"Scanning": "正在掃描",
|
||||
"Select the devices to share this folder with.": "Select the devices to share this folder with.",
|
||||
"Select the devices to share this folder with.": "選擇要共享這個資料夾的裝置。",
|
||||
"Settings": "設定",
|
||||
"Share With Devices": "Share With Devices",
|
||||
"Share With Devices": "與這些裝置共享",
|
||||
"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.",
|
||||
"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 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.",
|
||||
"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": "關閉",
|
||||
"Simple File Versioning": "簡單檔案版本控制",
|
||||
"Single level wildcard (matches within a directory only)": "單階層萬用字元 (只在單個資料夾階層內比對)",
|
||||
@@ -107,14 +107,14 @@
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Syncthing 似乎下線了,或者您的網際網路連線出現問題。正在重試...",
|
||||
"The aggregated statistics are publicly available at {%url%}.": "匯總統計資訊公佈於 {{url}}。",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "組態已經儲存但尚未啟用。Syncthing 必須重新啟動以便啟用新的組態。",
|
||||
"The device ID cannot be blank.": "The device ID cannot be blank.",
|
||||
"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).": "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).",
|
||||
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.",
|
||||
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "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.",
|
||||
"The folder ID cannot be blank.": "The folder ID cannot be blank.",
|
||||
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.",
|
||||
"The folder ID must be unique.": "The folder ID must be unique.",
|
||||
"The folder path cannot be blank.": "The folder path cannot be blank.",
|
||||
"The device ID cannot be blank.": "裝置識別碼不能為空白。",
|
||||
"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).": "要輸入在這裡的裝置識別碼可以在其他裝置的 \"編輯 > 顯示識別碼\" 對話框找到。空白以及連接符號可不輸入 (省略)",
|
||||
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "經過加密的使用資訊報告會每天傳送。報告是用來追蹤常用的平台、資料夾的大小以及應用程式的版本。若傳送的資料集有異動,您會再次看到這個對話框。",
|
||||
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "輸入的裝置識別碼似乎無效。它應該為一串包含半形英文字母及數字,並可能會含有空白或連接符號的字串,且長度為 52 或 56 個字元。",
|
||||
"The folder ID cannot be blank.": "資料夾識別碼不能為空白。",
|
||||
"The folder ID must be a short identifier (64 characters or less) consisting of letters, numbers and the dot (.), dash (-) and underscode (_) characters only.": "資料夾識別碼必須為一段只包含半形英文字母、數字、點 (.)、連接符號 (-) 以及底線 (_) 的簡短識別碼 (不多於 64 個字元)",
|
||||
"The folder ID must be unique.": "資料夾識別碼必須為獨一無二的。",
|
||||
"The folder path cannot be blank.": "資料夾路徑不能空白。",
|
||||
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "使用下列的間隔:在第一個小時內每 30 秒保留一個版本,在第一天內每小時保留一個版本,在第 30 天內每一天保留一個版本,在達到最長保留時間前每一星期保留一個版本。",
|
||||
"The maximum age must be a number and cannot be blank.": "最長保留時間最大必須為一個數字且不得為空。",
|
||||
"The maximum time to keep a version (in days, set to 0 to keep versions forever).": "一個版本被保留的最長時間 (單位為天,若設定為 0 則表示永遠保留)。",
|
||||
@@ -123,7 +123,7 @@
|
||||
"The rescan interval must be at least 5 seconds.": "重新掃描間隔至少須為 5 秒。",
|
||||
"Unknown": "未知",
|
||||
"Up to Date": "最新",
|
||||
"Upgrade To {%version%}": "升級到 {{version}}",
|
||||
"Upgrade To {%version%}": "升級至 {{version}}",
|
||||
"Upgrading": "正在升級",
|
||||
"Upload Rate": "上載速率",
|
||||
"Use Compression": "使用壓縮",
|
||||
@@ -131,8 +131,8 @@
|
||||
"Version": "版本",
|
||||
"Versions Path": "歷史版本路徑",
|
||||
"Versions are automatically deleted if they are older than the maximum age or exceed the number of files allowed in an interval.": "當檔案歷史版本的存留時間大於設定的最大值,或是其數量在一段時間內超出允許值時,則會被刪除。",
|
||||
"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.",
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "當新增一個裝置時,請記住,這個裝置也必須被添加至另一邊。",
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "當新增一個資料夾時,請記住,資料夾識別碼是用來將裝置之間的資料夾綁定在一起的。它們有區分大小寫,且必須在所有裝置之間完全相同。",
|
||||
"Yes": "是",
|
||||
"You must keep at least one version.": "您必須保留至少一個版本。",
|
||||
"full documentation": "完整說明文件",
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -17,6 +17,7 @@ package files
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sort"
|
||||
"sync"
|
||||
@@ -59,6 +60,21 @@ type versionList struct {
|
||||
versions []fileVersion
|
||||
}
|
||||
|
||||
func (l versionList) String() string {
|
||||
var b bytes.Buffer
|
||||
var id protocol.DeviceID
|
||||
b.WriteString("{")
|
||||
for i, v := range l.versions {
|
||||
if i > 0 {
|
||||
b.WriteString(", ")
|
||||
}
|
||||
copy(id[:], v.device)
|
||||
fmt.Fprintf(&b, "{%d, %v}", v.version, id)
|
||||
}
|
||||
b.WriteString("}")
|
||||
return b.String()
|
||||
}
|
||||
|
||||
type fileList []protocol.FileInfo
|
||||
|
||||
func (l fileList) Len() int {
|
||||
@@ -151,7 +167,7 @@ type fileIterator func(f protocol.FileIntf) bool
|
||||
func ldbGenericReplace(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo, deleteFn deletionHandler) uint64 {
|
||||
runtime.GC()
|
||||
|
||||
sort.Sort(fileList(fs)) // sort list on name, same as on disk
|
||||
sort.Sort(fileList(fs)) // sort list on name, same as in the database
|
||||
|
||||
start := deviceKey(folder, device, nil) // before all folder/device files
|
||||
limit := deviceKey(folder, device, []byte{0xff, 0xff, 0xff, 0xff}) // after all folder/device files
|
||||
@@ -177,12 +193,6 @@ func ldbGenericReplace(db *leveldb.DB, folder, device []byte, fs []protocol.File
|
||||
break
|
||||
}
|
||||
|
||||
if !moreFs && deleteFn == nil {
|
||||
// We don't have any more updated files to process and deletion
|
||||
// has not been requested, so we can exit early
|
||||
break
|
||||
}
|
||||
|
||||
if moreFs {
|
||||
newName = []byte(fs[fsi].Name)
|
||||
}
|
||||
@@ -199,8 +209,8 @@ func ldbGenericReplace(db *leveldb.DB, folder, device []byte, fs []protocol.File
|
||||
|
||||
switch {
|
||||
case moreFs && (!moreDb || cmp == -1):
|
||||
// Disk is missing this file. Insert it.
|
||||
if lv := ldbInsert(batch, folder, device, newName, fs[fsi]); lv > maxLocalVer {
|
||||
// Database is missing this file. Insert it.
|
||||
if lv := ldbInsert(batch, folder, device, fs[fsi]); lv > maxLocalVer {
|
||||
maxLocalVer = lv
|
||||
}
|
||||
if fs[fsi].IsInvalid() {
|
||||
@@ -216,8 +226,9 @@ func ldbGenericReplace(db *leveldb.DB, folder, device []byte, fs []protocol.File
|
||||
// marked a file as invalid, so handle that too.
|
||||
var ef protocol.FileInfoTruncated
|
||||
ef.UnmarshalXDR(dbi.Value())
|
||||
if fs[fsi].Version > ef.Version || fs[fsi].Version != ef.Version {
|
||||
if lv := ldbInsert(batch, folder, device, newName, fs[fsi]); lv > maxLocalVer {
|
||||
if fs[fsi].Version > ef.Version ||
|
||||
(fs[fsi].Version == ef.Version && fs[fsi].Flags != ef.Flags) {
|
||||
if lv := ldbInsert(batch, folder, device, fs[fsi]); lv > maxLocalVer {
|
||||
maxLocalVer = lv
|
||||
}
|
||||
if fs[fsi].IsInvalid() {
|
||||
@@ -226,15 +237,12 @@ func ldbGenericReplace(db *leveldb.DB, folder, device []byte, fs []protocol.File
|
||||
ldbUpdateGlobal(snap, batch, folder, device, newName, fs[fsi].Version)
|
||||
}
|
||||
}
|
||||
// Iterate both sides.
|
||||
fsi++
|
||||
moreDb = dbi.Next()
|
||||
|
||||
case moreDb && (!moreFs || cmp == 1):
|
||||
if deleteFn != nil {
|
||||
if lv := deleteFn(snap, batch, folder, device, oldName, dbi); lv > maxLocalVer {
|
||||
maxLocalVer = lv
|
||||
}
|
||||
if lv := deleteFn(snap, batch, folder, device, oldName, dbi); lv > maxLocalVer {
|
||||
maxLocalVer = lv
|
||||
}
|
||||
moreDb = dbi.Next()
|
||||
}
|
||||
@@ -251,7 +259,7 @@ func ldbGenericReplace(db *leveldb.DB, folder, device []byte, fs []protocol.File
|
||||
func ldbReplace(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) uint64 {
|
||||
// TODO: Return the remaining maxLocalVer?
|
||||
return ldbGenericReplace(db, folder, device, fs, func(db dbReader, batch dbWriter, folder, device, name []byte, dbi iterator.Iterator) uint64 {
|
||||
// Disk has files that we are missing. Remove it.
|
||||
// Database has a file that we are missing. Remove it.
|
||||
if debug {
|
||||
l.Debugf("delete; folder=%q device=%v name=%q", folder, protocol.DeviceIDFromBytes(device), name)
|
||||
}
|
||||
@@ -304,7 +312,7 @@ func ldbUpdate(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) ui
|
||||
fk := deviceKey(folder, device, name)
|
||||
bs, err := snap.Get(fk, nil)
|
||||
if err == leveldb.ErrNotFound {
|
||||
if lv := ldbInsert(batch, folder, device, name, f); lv > maxLocalVer {
|
||||
if lv := ldbInsert(batch, folder, device, f); lv > maxLocalVer {
|
||||
maxLocalVer = lv
|
||||
}
|
||||
if f.IsInvalid() {
|
||||
@@ -323,7 +331,7 @@ func ldbUpdate(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) ui
|
||||
// Flags might change without the version being bumped when we set the
|
||||
// invalid flag on an existing file.
|
||||
if ef.Version != f.Version || ef.Flags != f.Flags {
|
||||
if lv := ldbInsert(batch, folder, device, name, f); lv > maxLocalVer {
|
||||
if lv := ldbInsert(batch, folder, device, f); lv > maxLocalVer {
|
||||
maxLocalVer = lv
|
||||
}
|
||||
if f.IsInvalid() {
|
||||
@@ -342,7 +350,7 @@ func ldbUpdate(db *leveldb.DB, folder, device []byte, fs []protocol.FileInfo) ui
|
||||
return maxLocalVer
|
||||
}
|
||||
|
||||
func ldbInsert(batch dbWriter, folder, device, name []byte, file protocol.FileInfo) uint64 {
|
||||
func ldbInsert(batch dbWriter, folder, device []byte, file protocol.FileInfo) uint64 {
|
||||
if debug {
|
||||
l.Debugf("insert; folder=%q device=%v %v", folder, protocol.DeviceIDFromBytes(device), file)
|
||||
}
|
||||
@@ -351,6 +359,7 @@ func ldbInsert(batch dbWriter, folder, device, name []byte, file protocol.FileIn
|
||||
file.LocalVersion = clock(0)
|
||||
}
|
||||
|
||||
name := []byte(file.Name)
|
||||
nk := deviceKey(folder, device, name)
|
||||
batch.Put(nk, file.MarshalXDR())
|
||||
|
||||
@@ -576,9 +585,15 @@ func ldbWithGlobal(db *leveldb.DB, folder []byte, truncate bool, fn fileIterator
|
||||
l.Debugln(dbi.Key())
|
||||
panic("no versions?")
|
||||
}
|
||||
fk := deviceKey(folder, vl.versions[0].device, globalKeyName(dbi.Key()))
|
||||
name := globalKeyName(dbi.Key())
|
||||
fk := deviceKey(folder, vl.versions[0].device, name)
|
||||
bs, err := snap.Get(fk, nil)
|
||||
if err != nil {
|
||||
l.Debugf("folder: %q (%x)", folder, folder)
|
||||
l.Debugf("key: %q (%x)", dbi.Key(), dbi.Key())
|
||||
l.Debugf("vl: %v", vl)
|
||||
l.Debugf("name: %q (%x)", name, name)
|
||||
l.Debugf("fk: %q (%x)", fk, fk)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -670,6 +685,15 @@ outer:
|
||||
fk := deviceKey(folder, vl.versions[i].device, name)
|
||||
bs, err := snap.Get(fk, nil)
|
||||
if err != nil {
|
||||
var id protocol.DeviceID
|
||||
copy(id[:], device)
|
||||
l.Debugf("device: %v", id)
|
||||
l.Debugf("need: %v, have: %v", need, have)
|
||||
l.Debugf("key: %q (%x)", dbi.Key(), dbi.Key())
|
||||
l.Debugf("vl: %v", vl)
|
||||
l.Debugf("i: %v", i)
|
||||
l.Debugf("fk: %q (%x)", fk, fk)
|
||||
l.Debugf("name: %q (%x)", name, name)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
|
||||
@@ -167,10 +167,11 @@ func (m *Model) StartFolderRW(folder string) {
|
||||
panic("cannot start already running folder " + folder)
|
||||
}
|
||||
p := &Puller{
|
||||
folder: folder,
|
||||
dir: cfg.Path,
|
||||
scanIntv: time.Duration(cfg.RescanIntervalS) * time.Second,
|
||||
model: m,
|
||||
folder: folder,
|
||||
dir: cfg.Path,
|
||||
scanIntv: time.Duration(cfg.RescanIntervalS) * time.Second,
|
||||
model: m,
|
||||
ignorePerms: cfg.IgnorePerms,
|
||||
}
|
||||
m.folderRunners[folder] = p
|
||||
m.fmut.Unlock()
|
||||
@@ -1215,7 +1216,9 @@ func (m *Model) CurrentLocalVersion(folder string) uint64 {
|
||||
|
||||
fs, ok := m.folderFiles[folder]
|
||||
if !ok {
|
||||
panic("bug: LocalVersion called for nonexistent folder " + folder)
|
||||
// The folder might not exist, since this can be called with a user
|
||||
// specified folder name from the REST interface.
|
||||
return 0
|
||||
}
|
||||
|
||||
return fs.LocalVersion(protocol.LocalDeviceID)
|
||||
|
||||
@@ -62,12 +62,13 @@ var (
|
||||
)
|
||||
|
||||
type Puller struct {
|
||||
folder string
|
||||
dir string
|
||||
scanIntv time.Duration
|
||||
model *Model
|
||||
stop chan struct{}
|
||||
versioner versioner.Versioner
|
||||
folder string
|
||||
dir string
|
||||
scanIntv time.Duration
|
||||
model *Model
|
||||
stop chan struct{}
|
||||
versioner versioner.Versioner
|
||||
ignorePerms bool
|
||||
}
|
||||
|
||||
// Serve will run scans and pulls. It will return when Stop()ed or on a
|
||||
@@ -318,6 +319,9 @@ func (p *Puller) pullerIteration(ncopiers, npullers, nfinishers int) int {
|
||||
func (p *Puller) handleDir(file protocol.FileInfo) {
|
||||
realName := filepath.Join(p.dir, file.Name)
|
||||
mode := os.FileMode(file.Flags & 0777)
|
||||
if p.ignorePerms {
|
||||
mode = 0755
|
||||
}
|
||||
|
||||
if debug {
|
||||
curFile := p.model.CurrentFolderFile(p.folder, file.Name)
|
||||
@@ -339,17 +343,17 @@ func (p *Puller) handleDir(file protocol.FileInfo) {
|
||||
if err = osutil.InWritableDir(mkdir, realName); err == nil {
|
||||
p.model.updateLocal(p.folder, file)
|
||||
} else {
|
||||
l.Infof("Puller (folder %q, file %q): %v", p.folder, file.Name, err)
|
||||
l.Infof("Puller (folder %q, dir %q): %v", p.folder, file.Name, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Weird error when stat()'ing the dir. Probably won't work to do
|
||||
// anything else with it if we can't even stat() it.
|
||||
l.Infof("Puller (folder %q, file %q): %v", p.folder, file.Name, err)
|
||||
l.Infof("Puller (folder %q, dir %q): %v", p.folder, file.Name, err)
|
||||
return
|
||||
} else if !info.IsDir() {
|
||||
l.Infof("Puller (folder %q, file %q): should be dir, but is not", p.folder, file.Name)
|
||||
l.Infof("Puller (folder %q, dir %q): should be dir, but is not", p.folder, file.Name)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -357,10 +361,12 @@ func (p *Puller) handleDir(file protocol.FileInfo) {
|
||||
// don't handle modification times on directories, because that sucks...)
|
||||
// It's OK to change mode bits on stuff within non-writable directories.
|
||||
|
||||
if err := os.Chmod(realName, mode); err == nil {
|
||||
if p.ignorePerms {
|
||||
p.model.updateLocal(p.folder, file)
|
||||
} else if err := os.Chmod(realName, mode); err == nil {
|
||||
p.model.updateLocal(p.folder, file)
|
||||
} else {
|
||||
l.Infof("Puller (folder %q, file %q): %v", p.folder, file.Name, err)
|
||||
l.Infof("Puller (folder %q, dir %q): %v", p.folder, file.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -370,6 +376,8 @@ func (p *Puller) deleteDir(file protocol.FileInfo) {
|
||||
err := osutil.InWritableDir(os.Remove, realName)
|
||||
if err == nil || os.IsNotExist(err) {
|
||||
p.model.updateLocal(p.folder, file)
|
||||
} else {
|
||||
l.Infof("Puller (folder %q, dir %q): delete: %v", p.folder, file.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -509,14 +517,16 @@ func (p *Puller) handleFile(file protocol.FileInfo, copyChan chan<- copyBlocksSt
|
||||
// thing that has changed.
|
||||
func (p *Puller) shortcutFile(file protocol.FileInfo) {
|
||||
realName := filepath.Join(p.dir, file.Name)
|
||||
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
|
||||
if !p.ignorePerms {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
t := time.Unix(file.Modified, 0)
|
||||
err = os.Chtimes(realName, t, t)
|
||||
err := os.Chtimes(realName, t, t)
|
||||
if err != nil {
|
||||
l.Infof("Puller (folder %q, file %q): shortcut: %v", p.folder, file.Name, err)
|
||||
return
|
||||
@@ -643,11 +653,13 @@ func (p *Puller) finisherRoutine(in <-chan *sharedPullerState) {
|
||||
}
|
||||
|
||||
// Set the correct permission bits on the new file
|
||||
err = os.Chmod(state.tempName, os.FileMode(state.file.Flags&0777))
|
||||
if err != nil {
|
||||
os.Remove(state.tempName)
|
||||
l.Warnln("puller: final:", err)
|
||||
continue
|
||||
if !p.ignorePerms {
|
||||
err = os.Chmod(state.tempName, os.FileMode(state.file.Flags&0777))
|
||||
if err != nil {
|
||||
os.Remove(state.tempName)
|
||||
l.Warnln("puller: final:", err)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Set the correct timestamp on the new file
|
||||
|
||||
@@ -127,7 +127,7 @@ func NewStaggered(folderID, folderPath string, params map[string]string) Version
|
||||
{30, 3600}, // first hour -> 30 sec between versions
|
||||
{3600, 86400}, // next day -> 1 h between versions
|
||||
{86400, 592000}, // next 30 days -> 1 day between versions
|
||||
{604800, maxAge * 86400}, // next year -> 1 week between versions
|
||||
{604800, maxAge}, // next year -> 1 week between versions
|
||||
},
|
||||
mutex: &mutex,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user