Compare commits

...

15 Commits

Author SHA1 Message Date
Jakob Borg 5bb74ee61c gui, man: Update docs & translations 2016-11-22 09:32:57 +01:00
Jakob Borg 462fde5e7d cmd/syncthing: Make the default folder default again
The current way is quite confusing for new users - we create a default
folder, but it's not usable with the default folder created somewhere
else. Instead, when setting up for the first time with two devices, the
default folder must be removed and recreated on one of them. This comes
up on IRC and the forum now and then.

I think this matches expectactions better.

Another alternative would be to remove it entirely (not create a default
folder), but then we should also add some guidance in the UI on how to
proceed.

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3751
2016-11-22 08:18:43 +00:00
Unrud f1e83a57cd lib/osutil: Remove unnecessary fsync in Copy()
Fsyncing the file has a small performance penalty and seems unnecessary. The
file will be fsynced anyway, when the changes are commited to the database.

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3749
2016-11-22 07:59:54 +00:00
Jakob Borg cc9a9fb390 lib/model, lib/protocol: Add Folder.Description() for logging (ref #3741) 2016-11-22 08:36:14 +01:00
Jakob Borg 8fbcceb742 authors: Add further Unrud address 2016-11-22 08:14:22 +01:00
Roman Zaynetdinov d3a251e6d9 lib/model: Log folder IDs and labels (fixes #3724)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3741
2016-11-21 20:09:18 +01:00
Jakob Borg be80b26c18 authors: Add zaynetro 2016-11-21 20:08:31 +01:00
Unrud 1574b7d834 lib/model: Add fsync of files and directories, option to disable (fixes #3711) 2016-11-21 18:09:51 +01:00
Jakob Borg 51e10e344d Add Unrud 2016-11-21 17:59:44 +01:00
kwhite17 0d55d8c5b0 gui: Convert URLs in warning messages to HTML links (fixes #3241)
Skip-check: metalint (annoying timeout)

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3747
2016-11-21 08:27:44 +00:00
Jakob Borg 1392589d36 authors: Add kwhite17 2016-11-21 09:12:03 +01:00
Jakob Borg 548a324256 lib/connections: Slow down failing listeners
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3745
2016-11-19 12:37:14 +00:00
Jakob Borg a8a0bc356a lib/model: Minor cleanup to not fondle cfg.Raw things in handleDeintroductions
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3739
2016-11-17 08:56:55 +00:00
Jakob Borg faee1d5a8d lib/model: Fix locking around introduction handling (fixes #3737)
GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3738
2016-11-17 08:50:24 +00:00
Jakob Borg 3088dac33b lib/model: Clean up generateClusterConfig, fix spurious test failure by sorting 2016-11-17 07:45:45 +01:00
42 changed files with 980 additions and 110 deletions
+3
View File
@@ -61,6 +61,7 @@ Karol Różycki (krozycki) <rozycki.karol@gmail.com>
Kelong Cong (kc1212) <kc04bc@gmx.com> <kc1212@users.noreply.github.com>
Ken'ichi Kamada (kamadak) <kamada@nanohz.org>
Kevin Allen (ironmig) <kma1660@gmail.com>
Kevin White, Jr. (kwhite17) <kevinwhite1710@gmail.com>
Lars K.W. Gohlke (lkwg82) <lkwg82@gmx.de>
Laurent Etiemble (letiemble) <laurent.etiemble@gmail.com> <laurent.etiemble@monobjc.net>
Leo Arias (elopio) <yo@elopio.net>
@@ -82,6 +83,7 @@ Peter Hoeg (peterhoeg) <peter@speartail.com>
Philippe Schommers (filoozoom) <philippe@schommers.be>
Phill Luby (pluby) <phill.luby@newredo.com>
Piotr Bejda (piobpl) <piotrb10@gmail.com>
Roman Zaynetdinov (zaynetro) <romanznet@gmail.com>
Ryan Sullivan (KayoticSully) <kayoticsully@gmail.com>
Scott Klupfel (kluppy) <kluppy@going2blue.com>
Sergey Mishin (ralder) <ralder@yandex.ru>
@@ -94,6 +96,7 @@ Tobias Nygren (tnn2) <tnn@nygren.pp.se>
Tomas Cerveny (kozec) <kozec@kozec.com>
Tully Robinson (tojrobinson) <tully@tojr.org>
Tyler Brazier (tylerbrazier) <tyler@tylerbrazier.com>
Unrud (Unrud) <unrud@openaliasbox.org> <Unrud@users.noreply.github.com>
Veeti Paananen (veeti) <veeti.paananen@rojekti.fi>
Victor Buinsky (buinsky) <vix_booja@tut.by>
Vil Brekin (Vilbrekin) <vilbrekin@gmail.com>
+4
View File
@@ -63,6 +63,7 @@ kozec <kozec@kozec.com>
kralo <max.schulze@online.de>
kralo <kralo@users.noreply.github.com>
krozycki <rozycki.karol@gmail.com>
kwhite17 <kevinwhite1710@gmail.com>
letiemble <laurent.etiemble@gmail.com>
letiemble <laurent.etiemble@monobjc.net>
lkwg82 <lkwg82@gmx.de>
@@ -109,6 +110,8 @@ tnn2 <tnn@nygren.pp.se>
tojrobinson <tully@tojr.org>
tpng <benny.tpng@gmail.com>
tylerbrazier <tyler@tylerbrazier.com>
Unrud <unrud@openaliasbox.org>
Unrud <Unrud@users.noreply.github.com>
uok <ueomkail@gmail.com>
uok <uok@users.noreply.github.com>
veeti <veeti.paananen@rojekti.fi>
@@ -118,5 +121,6 @@ WSGCSysadmin <e.meitner@willystreet.coop>
wweich <wweich@users.noreply.github.com>
wweich <wweich@gmx.de>
xduugu <cedric@gmx.ca>
zaynetro <romanznet@gmail.com>
Zillode <zillode@zillode.be>
zukoo <fxgsell@gmail.com>
+2 -3
View File
@@ -955,9 +955,8 @@ func defaultConfig(myName string) config.Configuration {
if !noDefaultFolder {
l.Infoln("Default folder created and/or linked to new config")
folderID := strings.ToLower(rand.String(5) + "-" + rand.String(5))
defaultFolder = config.NewFolderConfiguration(folderID, locations[locDefFolder])
defaultFolder.Label = "Default Folder (" + folderID + ")"
defaultFolder = config.NewFolderConfiguration("default", locations[locDefFolder])
defaultFolder.Label = "Default Folder"
defaultFolder.RescanIntervalS = 60
defaultFolder.MinDiskFreePct = 1
defaultFolder.Devices = []config.FolderDeviceConfiguration{{DeviceID: myID}}
+1 -1
View File
@@ -86,7 +86,7 @@
"Ignore Patterns": "Πρότυπο για αγνόηση",
"Ignore Permissions": "Αγνόησε τα δικαιώματα",
"Incoming Rate Limit (KiB/s)": "Περιορισμός ταχύτητας λήψης (KiB/s)",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Με μια εσφαλμένη ρύθμιση μπορεί προκαληθεί ζημιά στα περιεχόμενα του φακέλου και το Syncthing μπορεί να σταματήσει να λειτουργεί.",
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Με μια εσφαλμένη ρύθμιση μπορεί να προκληθεί ζημιά στο περιεχόμενο των φακέλων και το Syncthing ενδέχεται να σταματήσει να λειτουργεί.",
"Introducer": "Βασικός κόμβος",
"Inversion of the given condition (i.e. do not exclude)": "Αντιστροφή της δοσμένης συνθήκης (π.χ. να μην εξαιρείς) ",
"Keep Versions": "Διατήρηση εκδόσεων",
+3 -3
View File
@@ -9,7 +9,7 @@
"Add Device": "Gaineratu makina",
"Add Folder": "Gaineratu partekatze",
"Add Remote Device": "Gaineratu makinan izan",
"Add new folder?": "Gaineratu berri partekatze ?",
"Add new folder?": "Gaineratu hau partekatze ?",
"Address": "Helbide",
"Addresses": "Helbidek",
"Advanced": "Aditu",
@@ -235,6 +235,6 @@
"files": "fitxategik",
"full documentation": "Dokumentazio osoa",
"items": "Elementuak",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} banaketa \"{{folderLabel}}\" gomitatzen zaitu.",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} \"{{folderLabel}}\" ({{folder}}) gomitatzen zaitu."
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} banaketa \"{{folder}}\" gomitatzen zaitu.",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} \"{{folderlabel}}\" ({{folder}}) gomitatzen zaitu."
}
+3 -3
View File
@@ -9,7 +9,7 @@
"Add Device": "Ajouter l'appareil",
"Add Folder": "Ajouter un partage",
"Add Remote Device": "Ajouter un appareil",
"Add new folder?": "Ajouter un nouveau partage ?",
"Add new folder?": "Ajouter ce partage ?",
"Address": "Adresse",
"Addresses": "Adresses",
"Advanced": "Avancé",
@@ -235,6 +235,6 @@
"files": "fichiers",
"full documentation": "documentation complète",
"items": "éléments",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} vous invite au partage \"{{folderLabel}}\".",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} vous invite au partage \"{{folderLabel}}\" ({{folder}})."
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} vous invite au partage \"{{folder}}\".",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} vous invite au partage \"{{folderlabel}}\" ({{folder}})."
}
+3 -3
View File
@@ -9,7 +9,7 @@
"Add Device": "Ajouter l'appareil",
"Add Folder": "Ajouter un partage",
"Add Remote Device": "Ajouter un appareil",
"Add new folder?": "Ajouter un nouveau partage ?",
"Add new folder?": "Ajouter ce partage ?",
"Address": "Adresse",
"Addresses": "Adresses",
"Advanced": "Avancé",
@@ -235,6 +235,6 @@
"files": "Fichiers",
"full documentation": "documentation complète",
"items": "éléments",
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} vous invite au partage \"{{folderLabel}}\".",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} vous invite au partage \"{{folderLabel}}\" ({{folder}})."
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} vous invite au partage \"{{folder}}\".",
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} vous invite au partage \"{{folderlabel}}\" ({{folder}})."
}
+5 -1
View File
@@ -236,7 +236,10 @@
</h3>
</div>
<div class="panel-body">
<p ng-repeat="err in errorList()"><small>{{err.when | date:"yyyy-MM-dd HH:mm:ss"}}:</small> {{friendlyDevices(err.message)}}</p>
<p ng-repeat="err in errorList()">
<small>{{err.when | date:"yyyy-MM-dd HH:mm:ss"}}:</small>
<span ng-bind-html="friendlyDevices(err.message) | linky: '_blank'"></span>
</p>
</div>
<div class="panel-footer">
<button type="button" class="btn btn-sm btn-default pull-right" ng-click="clearErrors()">
@@ -658,6 +661,7 @@
<!-- vendor scripts -->
<script src="vendor/jquery/jquery-2.2.2.js"></script>
<script src="vendor/angular/angular.js"></script>
<script src="vendor/angular/angular-sanitize.js"></script>
<script src="vendor/angular/angular-translate.js"></script>
<script src="vendor/angular/angular-translate-loader-static-files.js"></script>
<script src="vendor/angular/angular-dirPagination.js"></script>
+1 -1
View File
@@ -10,7 +10,7 @@
var syncthing = angular.module('syncthing', [
'angularUtils.directives.dirPagination',
'pascalprecht.translate',
'pascalprecht.translate', 'ngSanitize',
'syncthing.core'
]);
@@ -12,7 +12,7 @@
<p translate>Copyright &copy; 2014-2016 the following Contributors:</p>
<div class="row">
<div class="col-md-12" id="contributor-list">
Jakob Borg, Audrius Butkevicius, Alexander Graf, Anderson Mesquita, Antony Male, Ben Schulz, Caleb Callaway, Daniel Harte, Lars K.W. Gohlke, Lode Hoste, Michael Ploujnikov, Philippe Schommers, Ryan Sullivan, Sergey Mishin, Stefan Tatschner, Aaron Bieber, Adam Piggott, Alessandro G., Alexandre Viau, Andrew Dunham, Andrey D, Antoine Lamielle, Arthur Axel fREW Schmidt, Bart De Vries, Ben Curthoys, Ben Sidhom, Benny Ng, Brandon Philips, Brendan Long, Brian R. Becker, Carsten Hagemann, Cathryne Linenweaver, Cedric Staniewski, Chris Howie, Chris Joel, Colin Kennedy, Daniel Bergmann, Daniel Martí, David Rimmer, Denis A., Dennis Wilson, Dominik Heidler, Elias Jarlebring, Emil Hessman, Erik Meitner, Federico Castagnini, Felix Ableitner, Felix Unterpaintner, Francois-Xavier Gsell, Frank Isemann, Gilli Sigurdsson, Jaakko Hannikainen, Jacek Szafarkiewicz, Jake Peterson, James Patterson, Jaroslav Malec, Jens Diemer, Jochen Voss, Johan Vromans, Karol Różycki, Kelong Cong, Ken'ichi Kamada, Kevin Allen, Laurent Etiemble, Leo Arias, Lord Landon Agahnim, Majed Abdulaziz, Marc Laporte, Marc Pujol, Marcin Dziadus, Mateusz Naściszewski, Matt Burke, Max Schulze, Michael Jephcote, Michael Tilli, Nate Morrison, Pascal Jungblut, Peter Hoeg, Phill Luby, Piotr Bejda, Scott Klupfel, Simon Frei, Stefan Kuntz, Tim Abell, Tim Howes, Tobias Nygren, Tomas Cerveny, Tully Robinson, Tyler Brazier, Veeti Paananen, Victor Buinsky, Vil Brekin, William A. Kennington III, Wulf Weich, Xavier O., Yannic A.
Jakob Borg, Audrius Butkevicius, Alexander Graf, Anderson Mesquita, Antony Male, Ben Schulz, Caleb Callaway, Daniel Harte, Lars K.W. Gohlke, Lode Hoste, Michael Ploujnikov, Philippe Schommers, Ryan Sullivan, Sergey Mishin, Stefan Tatschner, Aaron Bieber, Adam Piggott, Alessandro G., Alexandre Viau, Andrew Dunham, Andrey D, Antoine Lamielle, Arthur Axel fREW Schmidt, Bart De Vries, Ben Curthoys, Ben Sidhom, Benny Ng, Brandon Philips, Brendan Long, Brian R. Becker, Carsten Hagemann, Cathryne Linenweaver, Cedric Staniewski, Chris Howie, Chris Joel, Colin Kennedy, Daniel Bergmann, Daniel Martí, David Rimmer, Denis A., Dennis Wilson, Dominik Heidler, Elias Jarlebring, Emil Hessman, Erik Meitner, Federico Castagnini, Felix Ableitner, Felix Unterpaintner, Francois-Xavier Gsell, Frank Isemann, Gilli Sigurdsson, Jaakko Hannikainen, Jacek Szafarkiewicz, Jake Peterson, James Patterson, Jaroslav Malec, Jens Diemer, Jochen Voss, Johan Vromans, Karol Różycki, Kelong Cong, Ken'ichi Kamada, Kevin Allen, Kevin White, Jr., Laurent Etiemble, Leo Arias, Lord Landon Agahnim, Majed Abdulaziz, Marc Laporte, Marc Pujol, Marcin Dziadus, Mateusz Naściszewski, Matt Burke, Max Schulze, Michael Jephcote, Michael Tilli, Nate Morrison, Pascal Jungblut, Peter Hoeg, Phill Luby, Piotr Bejda, Roman Zaynetdinov, Scott Klupfel, Simon Frei, Stefan Kuntz, Tim Abell, Tim Howes, Tobias Nygren, Tomas Cerveny, Tully Robinson, Tyler Brazier, Unrud, Veeti Paananen, Victor Buinsky, Vil Brekin, William A. Kennington III, Wulf Weich, Xavier O., Yannic A.
</div>
</div>
<hr/>
@@ -1318,6 +1318,7 @@ angular.module('syncthing.core')
rescanIntervalS: 60,
minDiskFreePct: 1,
maxConflicts: 10,
fsync: true,
order: "random",
fileVersioningSelector: "none",
trashcanClean: 0,
@@ -1345,6 +1346,7 @@ angular.module('syncthing.core')
rescanIntervalS: 60,
minDiskFreePct: 1,
maxConflicts: 10,
fsync: true,
order: "random",
fileVersioningSelector: "none",
trashcanClean: 0,
+647
View File
@@ -0,0 +1,647 @@
/**
* @license AngularJS v1.2.27
* (c) 2010-2014 Google, Inc. http://angularjs.org
* License: MIT
*/
(function(window, angular, undefined) {'use strict';
var $sanitizeMinErr = angular.$$minErr('$sanitize');
/**
* @ngdoc module
* @name ngSanitize
* @description
*
* # ngSanitize
*
* The `ngSanitize` module provides functionality to sanitize HTML.
*
*
* <div doc-module-components="ngSanitize"></div>
*
* See {@link ngSanitize.$sanitize `$sanitize`} for usage.
*/
/*
* HTML Parser By Misko Hevery (misko@hevery.com)
* based on: HTML Parser By John Resig (ejohn.org)
* Original code by Erik Arvidsson, Mozilla Public License
* http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
*
* // Use like so:
* htmlParser(htmlString, {
* start: function(tag, attrs, unary) {},
* end: function(tag) {},
* chars: function(text) {},
* comment: function(text) {}
* });
*
*/
/**
* @ngdoc service
* @name $sanitize
* @kind function
*
* @description
* The input is sanitized by parsing the html into tokens. All safe tokens (from a whitelist) are
* then serialized back to properly escaped html string. This means that no unsafe input can make
* it into the returned string, however, since our parser is more strict than a typical browser
* parser, it's possible that some obscure input, which would be recognized as valid HTML by a
* browser, won't make it through the sanitizer.
* The whitelist is configured using the functions `aHrefSanitizationWhitelist` and
* `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider `$compileProvider`}.
*
* @param {string} html Html input.
* @returns {string} Sanitized html.
*
* @example
<example module="sanitizeExample" deps="angular-sanitize.js">
<file name="index.html">
<script>
angular.module('sanitizeExample', ['ngSanitize'])
.controller('ExampleController', ['$scope', '$sce', function($scope, $sce) {
$scope.snippet =
'<p style="color:blue">an html\n' +
'<em onmouseover="this.textContent=\'PWN3D!\'">click here</em>\n' +
'snippet</p>';
$scope.deliberatelyTrustDangerousSnippet = function() {
return $sce.trustAsHtml($scope.snippet);
};
}]);
</script>
<div ng-controller="ExampleController">
Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
<table>
<tr>
<td>Directive</td>
<td>How</td>
<td>Source</td>
<td>Rendered</td>
</tr>
<tr id="bind-html-with-sanitize">
<td>ng-bind-html</td>
<td>Automatically uses $sanitize</td>
<td><pre>&lt;div ng-bind-html="snippet"&gt;<br/>&lt;/div&gt;</pre></td>
<td><div ng-bind-html="snippet"></div></td>
</tr>
<tr id="bind-html-with-trust">
<td>ng-bind-html</td>
<td>Bypass $sanitize by explicitly trusting the dangerous value</td>
<td>
<pre>&lt;div ng-bind-html="deliberatelyTrustDangerousSnippet()"&gt;
&lt;/div&gt;</pre>
</td>
<td><div ng-bind-html="deliberatelyTrustDangerousSnippet()"></div></td>
</tr>
<tr id="bind-default">
<td>ng-bind</td>
<td>Automatically escapes</td>
<td><pre>&lt;div ng-bind="snippet"&gt;<br/>&lt;/div&gt;</pre></td>
<td><div ng-bind="snippet"></div></td>
</tr>
</table>
</div>
</file>
<file name="protractor.js" type="protractor">
it('should sanitize the html snippet by default', function() {
expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()).
toBe('<p>an html\n<em>click here</em>\nsnippet</p>');
});
it('should inline raw snippet if bound to a trusted value', function() {
expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()).
toBe("<p style=\"color:blue\">an html\n" +
"<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" +
"snippet</p>");
});
it('should escape snippet without any filter', function() {
expect(element(by.css('#bind-default div')).getInnerHtml()).
toBe("&lt;p style=\"color:blue\"&gt;an html\n" +
"&lt;em onmouseover=\"this.textContent='PWN3D!'\"&gt;click here&lt;/em&gt;\n" +
"snippet&lt;/p&gt;");
});
it('should update', function() {
element(by.model('snippet')).clear();
element(by.model('snippet')).sendKeys('new <b onclick="alert(1)">text</b>');
expect(element(by.css('#bind-html-with-sanitize div')).getInnerHtml()).
toBe('new <b>text</b>');
expect(element(by.css('#bind-html-with-trust div')).getInnerHtml()).toBe(
'new <b onclick="alert(1)">text</b>');
expect(element(by.css('#bind-default div')).getInnerHtml()).toBe(
"new &lt;b onclick=\"alert(1)\"&gt;text&lt;/b&gt;");
});
</file>
</example>
*/
function $SanitizeProvider() {
this.$get = ['$$sanitizeUri', function($$sanitizeUri) {
return function(html) {
var buf = [];
htmlParser(html, htmlSanitizeWriter(buf, function(uri, isImage) {
return !/^unsafe/.test($$sanitizeUri(uri, isImage));
}));
return buf.join('');
};
}];
}
function sanitizeText(chars) {
var buf = [];
var writer = htmlSanitizeWriter(buf, angular.noop);
writer.chars(chars);
return buf.join('');
}
// Regular Expressions for parsing tags and attributes
var START_TAG_REGEXP =
/^<((?:[a-zA-Z])[\w:-]*)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*(>?)/,
END_TAG_REGEXP = /^<\/\s*([\w:-]+)[^>]*>/,
ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,
BEGIN_TAG_REGEXP = /^</,
BEGING_END_TAGE_REGEXP = /^<\//,
COMMENT_REGEXP = /<!--(.*?)-->/g,
DOCTYPE_REGEXP = /<!DOCTYPE([^>]*?)>/i,
CDATA_REGEXP = /<!\[CDATA\[(.*?)]]>/g,
SURROGATE_PAIR_REGEXP = /[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
// Match everything outside of normal chars and " (quote character)
NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g;
// Good source of info about elements and attributes
// http://dev.w3.org/html5/spec/Overview.html#semantics
// http://simon.html5.org/html-elements
// Safe Void Elements - HTML5
// http://dev.w3.org/html5/spec/Overview.html#void-elements
var voidElements = makeMap("area,br,col,hr,img,wbr");
// Elements that you can, intentionally, leave open (and which close themselves)
// http://dev.w3.org/html5/spec/Overview.html#optional-tags
var optionalEndTagBlockElements = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"),
optionalEndTagInlineElements = makeMap("rp,rt"),
optionalEndTagElements = angular.extend({},
optionalEndTagInlineElements,
optionalEndTagBlockElements);
// Safe Block Elements - HTML5
var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap("address,article," +
"aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5," +
"h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul"));
// Inline Elements - HTML5
var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b," +
"bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s," +
"samp,small,span,strike,strong,sub,sup,time,tt,u,var"));
// Special Elements (can contain anything)
var specialElements = makeMap("script,style");
var validElements = angular.extend({},
voidElements,
blockElements,
inlineElements,
optionalEndTagElements);
//Attributes that have href and hence need to be sanitized
var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap");
var validAttrs = angular.extend({}, uriAttrs, makeMap(
'abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,'+
'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,'+
'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,'+
'scope,scrolling,shape,size,span,start,summary,target,title,type,'+
'valign,value,vspace,width'));
function makeMap(str) {
var obj = {}, items = str.split(','), i;
for (i = 0; i < items.length; i++) obj[items[i]] = true;
return obj;
}
/**
* @example
* htmlParser(htmlString, {
* start: function(tag, attrs, unary) {},
* end: function(tag) {},
* chars: function(text) {},
* comment: function(text) {}
* });
*
* @param {string} html string
* @param {object} handler
*/
function htmlParser( html, handler ) {
if (typeof html !== 'string') {
if (html === null || typeof html === 'undefined') {
html = '';
} else {
html = '' + html;
}
}
var index, chars, match, stack = [], last = html, text;
stack.last = function() { return stack[ stack.length - 1 ]; };
while ( html ) {
text = '';
chars = true;
// Make sure we're not in a script or style element
if ( !stack.last() || !specialElements[ stack.last() ] ) {
// Comment
if ( html.indexOf("<!--") === 0 ) {
// comments containing -- are not allowed unless they terminate the comment
index = html.indexOf("--", 4);
if ( index >= 0 && html.lastIndexOf("-->", index) === index) {
if (handler.comment) handler.comment( html.substring( 4, index ) );
html = html.substring( index + 3 );
chars = false;
}
// DOCTYPE
} else if ( DOCTYPE_REGEXP.test(html) ) {
match = html.match( DOCTYPE_REGEXP );
if ( match ) {
html = html.replace( match[0], '');
chars = false;
}
// end tag
} else if ( BEGING_END_TAGE_REGEXP.test(html) ) {
match = html.match( END_TAG_REGEXP );
if ( match ) {
html = html.substring( match[0].length );
match[0].replace( END_TAG_REGEXP, parseEndTag );
chars = false;
}
// start tag
} else if ( BEGIN_TAG_REGEXP.test(html) ) {
match = html.match( START_TAG_REGEXP );
if ( match ) {
// We only have a valid start-tag if there is a '>'.
if ( match[4] ) {
html = html.substring( match[0].length );
match[0].replace( START_TAG_REGEXP, parseStartTag );
}
chars = false;
} else {
// no ending tag found --- this piece should be encoded as an entity.
text += '<';
html = html.substring(1);
}
}
if ( chars ) {
index = html.indexOf("<");
text += index < 0 ? html : html.substring( 0, index );
html = index < 0 ? "" : html.substring( index );
if (handler.chars) handler.chars( decodeEntities(text) );
}
} else {
html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'),
function(all, text){
text = text.replace(COMMENT_REGEXP, "$1").replace(CDATA_REGEXP, "$1");
if (handler.chars) handler.chars( decodeEntities(text) );
return "";
});
parseEndTag( "", stack.last() );
}
if ( html == last ) {
throw $sanitizeMinErr('badparse', "The sanitizer was unable to parse the following block " +
"of html: {0}", html);
}
last = html;
}
// Clean up any remaining tags
parseEndTag();
function parseStartTag( tag, tagName, rest, unary ) {
tagName = angular.lowercase(tagName);
if ( blockElements[ tagName ] ) {
while ( stack.last() && inlineElements[ stack.last() ] ) {
parseEndTag( "", stack.last() );
}
}
if ( optionalEndTagElements[ tagName ] && stack.last() == tagName ) {
parseEndTag( "", tagName );
}
unary = voidElements[ tagName ] || !!unary;
if ( !unary )
stack.push( tagName );
var attrs = {};
rest.replace(ATTR_REGEXP,
function(match, name, doubleQuotedValue, singleQuotedValue, unquotedValue) {
var value = doubleQuotedValue
|| singleQuotedValue
|| unquotedValue
|| '';
attrs[name] = decodeEntities(value);
});
if (handler.start) handler.start( tagName, attrs, unary );
}
function parseEndTag( tag, tagName ) {
var pos = 0, i;
tagName = angular.lowercase(tagName);
if ( tagName )
// Find the closest opened tag of the same type
for ( pos = stack.length - 1; pos >= 0; pos-- )
if ( stack[ pos ] == tagName )
break;
if ( pos >= 0 ) {
// Close all the open elements, up the stack
for ( i = stack.length - 1; i >= pos; i-- )
if (handler.end) handler.end( stack[ i ] );
// Remove the open elements from the stack
stack.length = pos;
}
}
}
var hiddenPre=document.createElement("pre");
var spaceRe = /^(\s*)([\s\S]*?)(\s*)$/;
/**
* decodes all entities into regular string
* @param value
* @returns {string} A string with decoded entities.
*/
function decodeEntities(value) {
if (!value) { return ''; }
// Note: IE8 does not preserve spaces at the start/end of innerHTML
// so we must capture them and reattach them afterward
var parts = spaceRe.exec(value);
var spaceBefore = parts[1];
var spaceAfter = parts[3];
var content = parts[2];
if (content) {
hiddenPre.innerHTML=content.replace(/</g,"&lt;");
// innerText depends on styling as it doesn't display hidden elements.
// Therefore, it's better to use textContent not to cause unnecessary
// reflows. However, IE<9 don't support textContent so the innerText
// fallback is necessary.
content = 'textContent' in hiddenPre ?
hiddenPre.textContent : hiddenPre.innerText;
}
return spaceBefore + content + spaceAfter;
}
/**
* Escapes all potentially dangerous characters, so that the
* resulting string can be safely inserted into attribute or
* element text.
* @param value
* @returns {string} escaped text
*/
function encodeEntities(value) {
return value.
replace(/&/g, '&amp;').
replace(SURROGATE_PAIR_REGEXP, function (value) {
var hi = value.charCodeAt(0);
var low = value.charCodeAt(1);
return '&#' + (((hi - 0xD800) * 0x400) + (low - 0xDC00) + 0x10000) + ';';
}).
replace(NON_ALPHANUMERIC_REGEXP, function(value){
return '&#' + value.charCodeAt(0) + ';';
}).
replace(/</g, '&lt;').
replace(/>/g, '&gt;');
}
/**
* create an HTML/XML writer which writes to buffer
* @param {Array} buf use buf.jain('') to get out sanitized html string
* @returns {object} in the form of {
* start: function(tag, attrs, unary) {},
* end: function(tag) {},
* chars: function(text) {},
* comment: function(text) {}
* }
*/
function htmlSanitizeWriter(buf, uriValidator){
var ignore = false;
var out = angular.bind(buf, buf.push);
return {
start: function(tag, attrs, unary){
tag = angular.lowercase(tag);
if (!ignore && specialElements[tag]) {
ignore = tag;
}
if (!ignore && validElements[tag] === true) {
out('<');
out(tag);
angular.forEach(attrs, function(value, key){
var lkey=angular.lowercase(key);
var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background');
if (validAttrs[lkey] === true &&
(uriAttrs[lkey] !== true || uriValidator(value, isImage))) {
out(' ');
out(key);
out('="');
out(encodeEntities(value));
out('"');
}
});
out(unary ? '/>' : '>');
}
},
end: function(tag){
tag = angular.lowercase(tag);
if (!ignore && validElements[tag] === true) {
out('</');
out(tag);
out('>');
}
if (tag == ignore) {
ignore = false;
}
},
chars: function(chars){
if (!ignore) {
out(encodeEntities(chars));
}
}
};
}
// define ngSanitize module and register $sanitize service
angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider);
/* global sanitizeText: false */
/**
* @ngdoc filter
* @name linky
* @kind function
*
* @description
* Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and
* plain email address links.
*
* Requires the {@link ngSanitize `ngSanitize`} module to be installed.
*
* @param {string} text Input text.
* @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in.
* @returns {string} Html-linkified text.
*
* @usage
<span ng-bind-html="linky_expression | linky"></span>
*
* @example
<example module="linkyExample" deps="angular-sanitize.js">
<file name="index.html">
<script>
angular.module('linkyExample', ['ngSanitize'])
.controller('ExampleController', ['$scope', function($scope) {
$scope.snippet =
'Pretty text with some links:\n'+
'http://angularjs.org/,\n'+
'mailto:us@somewhere.org,\n'+
'another@somewhere.org,\n'+
'and one more: ftp://127.0.0.1/.';
$scope.snippetWithTarget = 'http://angularjs.org/';
}]);
</script>
<div ng-controller="ExampleController">
Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea>
<table>
<tr>
<td>Filter</td>
<td>Source</td>
<td>Rendered</td>
</tr>
<tr id="linky-filter">
<td>linky filter</td>
<td>
<pre>&lt;div ng-bind-html="snippet | linky"&gt;<br>&lt;/div&gt;</pre>
</td>
<td>
<div ng-bind-html="snippet | linky"></div>
</td>
</tr>
<tr id="linky-target">
<td>linky target</td>
<td>
<pre>&lt;div ng-bind-html="snippetWithTarget | linky:'_blank'"&gt;<br>&lt;/div&gt;</pre>
</td>
<td>
<div ng-bind-html="snippetWithTarget | linky:'_blank'"></div>
</td>
</tr>
<tr id="escaped-html">
<td>no filter</td>
<td><pre>&lt;div ng-bind="snippet"&gt;<br>&lt;/div&gt;</pre></td>
<td><div ng-bind="snippet"></div></td>
</tr>
</table>
</file>
<file name="protractor.js" type="protractor">
it('should linkify the snippet with urls', function() {
expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()).
toBe('Pretty text with some links: http://angularjs.org/, us@somewhere.org, ' +
'another@somewhere.org, and one more: ftp://127.0.0.1/.');
expect(element.all(by.css('#linky-filter a')).count()).toEqual(4);
});
it('should not linkify snippet without the linky filter', function() {
expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText()).
toBe('Pretty text with some links: http://angularjs.org/, mailto:us@somewhere.org, ' +
'another@somewhere.org, and one more: ftp://127.0.0.1/.');
expect(element.all(by.css('#escaped-html a')).count()).toEqual(0);
});
it('should update', function() {
element(by.model('snippet')).clear();
element(by.model('snippet')).sendKeys('new http://link.');
expect(element(by.id('linky-filter')).element(by.binding('snippet | linky')).getText()).
toBe('new http://link.');
expect(element.all(by.css('#linky-filter a')).count()).toEqual(1);
expect(element(by.id('escaped-html')).element(by.binding('snippet')).getText())
.toBe('new http://link.');
});
it('should work with the target property', function() {
expect(element(by.id('linky-target')).
element(by.binding("snippetWithTarget | linky:'_blank'")).getText()).
toBe('http://angularjs.org/');
expect(element(by.css('#linky-target a')).getAttribute('target')).toEqual('_blank');
});
</file>
</example>
*/
angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) {
var LINKY_URL_REGEXP =
/((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>"]/,
MAILTO_REGEXP = /^mailto:/;
return function(text, target) {
if (!text) return text;
var match;
var raw = text;
var html = [];
var url;
var i;
while ((match = raw.match(LINKY_URL_REGEXP))) {
// We can not end in these as they are sometimes found at the end of the sentence
url = match[0];
// if we did not match ftp/http/mailto then assume mailto
if (match[2] == match[3]) url = 'mailto:' + url;
i = match.index;
addText(raw.substr(0, i));
addLink(url, match[0].replace(MAILTO_REGEXP, ''));
raw = raw.substring(i + match[0].length);
}
addText(raw);
return $sanitize(html.join(''));
function addText(text) {
if (!text) {
return;
}
html.push(sanitizeText(text));
}
function addLink(url, text) {
html.push('<a ');
if (angular.isDefined(target)) {
html.push('target="');
html.push(target);
html.push('" ');
}
html.push('href="');
html.push(url);
html.push('">');
addText(text);
html.push('</a>');
}
};
}]);
})(window, window.angular);
+12 -1
View File
@@ -26,7 +26,7 @@ import (
const (
OldestHandledVersion = 10
CurrentVersion = 16
CurrentVersion = 17
MaxRescanIntervalS = 365 * 24 * 60 * 60
)
@@ -254,6 +254,9 @@ func (cfg *Configuration) clean() error {
if cfg.Version == 15 {
convertV15V16(cfg)
}
if cfg.Version == 16 {
convertV16V17(cfg)
}
// Build a list of available devices
existingDevices := make(map[protocol.DeviceID]bool)
@@ -327,6 +330,14 @@ func convertV15V16(cfg *Configuration) {
cfg.Version = 16
}
func convertV16V17(cfg *Configuration) {
for i := range cfg.Folders {
cfg.Folders[i].Fsync = true
}
cfg.Version = 17
}
func convertV13V14(cfg *Configuration) {
// Not using the ignore cache is the new default. Disable it on existing
// configurations.
+1
View File
@@ -104,6 +104,7 @@ func TestDeviceConfig(t *testing.T) {
AutoNormalize: true,
MinDiskFreePct: 1,
MaxConflicts: -1,
Fsync: true,
Versioning: VersioningConfiguration{
Params: map[string]string{},
},
+9
View File
@@ -7,6 +7,7 @@
package config
import (
"fmt"
"os"
"path/filepath"
"runtime"
@@ -38,6 +39,7 @@ type FolderConfiguration struct {
MaxConflicts int `xml:"maxConflicts" json:"maxConflicts"`
DisableSparseFiles bool `xml:"disableSparseFiles" json:"disableSparseFiles"`
DisableTempIndexes bool `xml:"disableTempIndexes" json:"disableTempIndexes"`
Fsync bool `xml:"fsync" json:"fsync"`
cachedPath string
@@ -85,6 +87,9 @@ func (f *FolderConfiguration) CreateMarker() error {
return err
}
fd.Close()
if err := osutil.SyncDir(filepath.Dir(marker)); err != nil {
l.Infof("fsync %q failed: %v", filepath.Dir(marker), err)
}
osutil.HideFile(marker)
}
@@ -99,6 +104,10 @@ func (f *FolderConfiguration) HasMarker() bool {
return true
}
func (f FolderConfiguration) Description() string {
return fmt.Sprintf("%q (%s)", f.Label, f.ID)
}
func (f *FolderConfiguration) DeviceIDs() []protocol.DeviceID {
deviceIDs := make([]protocol.DeviceID, len(f.Devices))
for i, n := range f.Devices {
+15
View File
@@ -0,0 +1,15 @@
<configuration version="17">
<folder id="test" path="testdata" type="readonly" ignorePerms="false" rescanIntervalS="600" autoNormalize="true">
<device id="AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR"></device>
<device id="P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2"></device>
<minDiskFreePct>1</minDiskFreePct>
<maxConflicts>-1</maxConflicts>
<fsync>true</fsync>
</folder>
<device id="AIR6LPZ-7K4PTTV-UXQSMUU-CPQ5YWH-OEDFIIQ-JUG777G-2YQXXR5-YD6AWQR" name="node one" compression="metadata">
<address>tcp://a</address>
</device>
<device id="P56IOI7-MZJNU2Y-IQGDREY-DM2MGTI-MGL3BXN-PQ6W5BM-TBBZ4TJ-XZWICQ2" name="node two" compression="metadata">
<address>tcp://b</address>
</device>
</configuration>
+21
View File
@@ -209,6 +209,27 @@ func (w *Wrapper) SetDevice(dev DeviceConfiguration) error {
return w.replaceLocked(newCfg)
}
// RemoveDevice removes the device from the configuration
func (w *Wrapper) RemoveDevice(id protocol.DeviceID) error {
w.mut.Lock()
defer w.mut.Unlock()
newCfg := w.cfg.Copy()
removed := false
for i := range newCfg.Devices {
if newCfg.Devices[i].DeviceID == id {
newCfg.Devices = append(newCfg.Devices[:i], newCfg.Devices[i+1:]...)
removed = true
break
}
}
if !removed {
return nil
}
return w.replaceLocked(newCfg)
}
// Folders returns a map of folders. Folder structures should not be changed,
// other than for the purpose of updating via SetFolder().
func (w *Wrapper) Folders() map[string]FolderConfiguration {
+24 -6
View File
@@ -59,9 +59,10 @@ type Service struct {
natService *nat.Service
natServiceToken *suture.ServiceToken
listenersMut sync.RWMutex
listeners map[string]genericListener
listenerTokens map[string]suture.ServiceToken
listenersMut sync.RWMutex
listeners map[string]genericListener
listenerTokens map[string]suture.ServiceToken
listenerSupervisor *suture.Supervisor
curConMut sync.Mutex
currentConnection map[protocol.DeviceID]Connection
@@ -71,7 +72,11 @@ func NewService(cfg *config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *
bepProtocolName string, tlsDefaultCommonName string, lans []*net.IPNet) *Service {
service := &Service{
Supervisor: suture.NewSimple("connections.Service"),
Supervisor: suture.New("connections.Service", suture.Spec{
Log: func(line string) {
l.Infoln(line)
},
}),
cfg: cfg,
myID: myID,
model: mdl,
@@ -87,6 +92,18 @@ func NewService(cfg *config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *
listeners: make(map[string]genericListener),
listenerTokens: make(map[string]suture.ServiceToken),
// A listener can fail twice, rapidly. Any more than that and it
// will be put on suspension for ten minutes. Restarts and changes
// due to config are done by removing and adding services, so are
// not subject to these limitations.
listenerSupervisor: suture.New("c.S.listenerSupervisor", suture.Spec{
Log: func(line string) {
l.Infoln(line)
},
FailureThreshold: 2,
FailureBackoff: 600 * time.Second,
}),
curConMut: sync.NewMutex(),
currentConnection: make(map[protocol.DeviceID]Connection),
}
@@ -111,6 +128,7 @@ func NewService(cfg *config.Wrapper, myID protocol.DeviceID, mdl Model, tlsCfg *
service.Add(serviceFunc(service.connect))
service.Add(serviceFunc(service.handle))
service.Add(service.listenerSupervisor)
raw := cfg.RawCopy()
// Actually starts the listeners and NAT service
@@ -417,7 +435,7 @@ func (s *Service) createListener(factory listenerFactory, uri *url.URL) bool {
listener := factory.New(uri, s.cfg, s.tlsCfg, s.conns, s.natService)
listener.OnAddressesChanged(s.logListenAddressesChangedEvent)
s.listeners[uri.String()] = listener
s.listenerTokens[uri.String()] = s.Add(listener)
s.listenerTokens[uri.String()] = s.listenerSupervisor.Add(listener)
return true
}
@@ -481,7 +499,7 @@ func (s *Service) CommitConfiguration(from, to config.Configuration) bool {
for addr, listener := range s.listeners {
if _, ok := seen[addr]; !ok || !listener.Factory().Enabled(to) {
l.Debugln("Stopping listener", addr)
s.Remove(s.listenerTokens[addr])
s.listenerSupervisor.Remove(s.listenerTokens[addr])
delete(s.listenerTokens, addr)
delete(s.listeners, addr)
}
+51 -49
View File
@@ -189,12 +189,12 @@ func (m *Model) StartFolder(folder string) {
func (m *Model) startFolderLocked(folder string) config.FolderType {
cfg, ok := m.folderCfgs[folder]
if !ok {
panic("cannot start nonexistent folder " + folder)
panic("cannot start nonexistent folder " + cfg.Description())
}
_, ok = m.folderRunners[folder]
if ok {
panic("cannot start already running folder " + folder)
panic("cannot start already running folder " + cfg.Description())
}
folderFactory, ok := folderFactories[cfg.Type]
@@ -777,7 +777,7 @@ func (m *Model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
"folderLabel": folder.Label,
"device": deviceID.String(),
})
l.Infof("Unexpected folder ID %q sent from device %q; ensure that the folder exists and that this device is selected under \"Share With\" in the folder configuration.", folder.ID, deviceID)
l.Infof("Unexpected folder %s sent from device %q; ensure that the folder exists and that this device is selected under \"Share With\" in the folder configuration.", folder.Description(), deviceID)
continue
}
if !folder.DisableTempIndexes {
@@ -806,19 +806,19 @@ func (m *Model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
// the IndexID, or something else weird has
// happened. We send a full index to reset the
// situation.
l.Infof("Device %v folder %q is delta index compatible, but seems out of sync with reality", deviceID, folder.ID)
l.Infof("Device %v folder %s is delta index compatible, but seems out of sync with reality", deviceID, folder.Description())
startSequence = 0
continue
}
l.Debugf("Device %v folder %q is delta index compatible (mlv=%d)", deviceID, folder.ID, dev.MaxSequence)
l.Debugf("Device %v folder %s is delta index compatible (mlv=%d)", deviceID, folder.Description(), dev.MaxSequence)
startSequence = dev.MaxSequence
} else if dev.IndexID != 0 {
// They say they've seen an index ID from us, but it's
// not the right one. Either they are confused or we
// must have reset our database since last talking to
// them. We'll start with a full index transfer.
l.Infof("Device %v folder %q has mismatching index ID for us (%v != %v)", deviceID, folder.ID, dev.IndexID, myIndexID)
l.Infof("Device %v folder %s has mismatching index ID for us (%v != %v)", deviceID, folder.Description(), dev.IndexID, myIndexID)
startSequence = 0
}
} else if dev.ID == deviceID && dev.IndexID != 0 {
@@ -840,7 +840,7 @@ func (m *Model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
// will probably send us a full index. We drop any
// information we have and remember this new index ID
// instead.
l.Infof("Device %v folder %q has a new index ID (%v)", deviceID, folder.ID, dev.IndexID)
l.Infof("Device %v folder %s has a new index ID (%v)", deviceID, folder.Description(), dev.IndexID)
fs.Replace(deviceID, nil)
fs.SetIndexID(deviceID, dev.IndexID)
} else {
@@ -857,7 +857,6 @@ func (m *Model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
go sendIndexes(conn, folder.ID, fs, m.folderIgnores[folder.ID], startSequence, dbLocation)
}
m.fmut.Unlock()
// This breaks if we send multiple CM messages during the same connection.
if len(tempIndexFolders) > 0 {
@@ -883,6 +882,7 @@ func (m *Model) ClusterConfig(deviceID protocol.DeviceID, cm protocol.ClusterCon
changed = true
}
}
m.fmut.Unlock()
if changed {
if err := m.cfg.Save(); err != nil {
@@ -949,9 +949,10 @@ func (m *Model) handleDeintroductions(introducerCfg config.DeviceConfiguration,
for i := 0; i < len(folderCfg.Devices); i++ {
if folderCfg.Devices[i].IntroducedBy == introducerCfg.DeviceID {
if !foldersDevices.has(folderCfg.Devices[i].DeviceID, folderCfg.ID) {
// We could not find that folder shared on the introducer with the device that was introduced to us.
// We could not find that folder shared on the
// introducer with the device that was introduced to us.
// We should follow and unshare aswell.
l.Infof("Unsharing folder %q with %v as introducer %v no longer shares the folder with that device", folderCfg.ID, folderCfg.Devices[i].DeviceID, folderCfg.Devices[i].IntroducedBy)
l.Infof("Unsharing folder %s with %v as introducer %v no longer shares the folder with that device", folderCfg.Description(), folderCfg.Devices[i].DeviceID, folderCfg.Devices[i].IntroducedBy)
folderCfg.Devices = append(folderCfg.Devices[:i], folderCfg.Devices[i+1:]...)
i--
folderChanged = true
@@ -968,36 +969,26 @@ func (m *Model) handleDeintroductions(introducerCfg config.DeviceConfiguration,
}
}
// Check if we should remove some devices, if the introducer no longer shares any folder with them.
// Yet do not remove if we share other folders that haven't been introduced by the introducer.
raw := m.cfg.RawCopy()
deviceChanged := false
for i := 0; i < len(raw.Devices); i++ {
if raw.Devices[i].IntroducedBy == introducerCfg.DeviceID {
if !foldersDevices.hasDevice(raw.Devices[i].DeviceID) {
if foldersIntroducedByOthers.hasDevice(raw.Devices[i].DeviceID) {
l.Infof("Would have removed %v as %v no longer shares any folders, yet there are other folders that are shared with this device that haven't been introduced by this introducer.", raw.Devices[i].DeviceID, raw.Devices[i].IntroducedBy)
// Check if we should remove some devices, if the introducer no longer
// shares any folder with them. Yet do not remove if we share other
// folders that haven't been introduced by the introducer.
for _, device := range m.cfg.Devices() {
if device.IntroducedBy == introducerCfg.DeviceID {
if !foldersDevices.hasDevice(device.DeviceID) {
if foldersIntroducedByOthers.hasDevice(device.DeviceID) {
l.Infof("Would have removed %v as %v no longer shares any folders, yet there are other folders that are shared with this device that haven't been introduced by this introducer.", device.DeviceID, device.IntroducedBy)
continue
}
// The introducer no longer shares any folder with the device, remove the device.
l.Infof("Removing device %v as introducer %v no longer shares any folders with that device", raw.Devices[i].DeviceID, raw.Devices[i].IntroducedBy)
raw.Devices = append(raw.Devices[:i], raw.Devices[i+1:]...)
i--
deviceChanged = true
// The introducer no longer shares any folder with the
// device, remove the device.
l.Infof("Removing device %v as introducer %v no longer shares any folders with that device", device.DeviceID, device.IntroducedBy)
m.cfg.RemoveDevice(device.DeviceID)
changed = true
}
}
}
// We've removed a device, replace the config.
if deviceChanged {
if err := m.cfg.Replace(raw); err != nil {
l.Warnln("Failed to save config", err)
}
changed = true
}
return changed
}
func (m *Model) introduceDevice(device protocol.Device, introducerCfg config.DeviceConfiguration) {
@@ -1029,7 +1020,7 @@ func (m *Model) introduceDevice(device protocol.Device, introducerCfg config.Dev
}
func (m *Model) introduceDeviceToFolder(device protocol.Device, folder protocol.Folder, introducerCfg config.DeviceConfiguration) {
l.Infof("Sharing folder %q with %v (vouched for by introducer %v)", folder.ID, device.ID, introducerCfg.DeviceID)
l.Infof("Sharing folder %s with %v (vouched for by introducer %v)", folder.Description(), device.ID, introducerCfg.DeviceID)
m.deviceFolders[device.ID] = append(m.deviceFolders[device.ID], folder.ID)
m.folderDevices.set(device.ID, folder.ID)
@@ -1744,14 +1735,14 @@ func (m *Model) internalScanFolderSubdirs(folder string, subDirs []string) error
if err := m.CheckFolderHealth(folder); err != nil {
runner.setError(err)
l.Infof("Stopping folder %s due to error: %s", folder, err)
l.Infof("Stopping folder %s due to error: %s", folderCfg.Description(), err)
return err
}
if err := ignores.Load(filepath.Join(folderCfg.Path(), ".stignore")); err != nil && !os.IsNotExist(err) {
err = fmt.Errorf("loading ignores: %v", err)
runner.setError(err)
l.Infof("Stopping folder %s due to error: %s", folder, err)
l.Infof("Stopping folder %s due to error: %s", folderCfg.Description(), err)
return err
}
@@ -1808,7 +1799,7 @@ func (m *Model) internalScanFolderSubdirs(folder string, subDirs []string) error
for f := range fchan {
if len(batch) == batchSizeFiles || blocksHandled > batchSizeBlocks {
if err := m.CheckFolderHealth(folder); err != nil {
l.Infof("Stopping folder %s mid-scan due to folder error: %s", folder, err)
l.Infof("Stopping folder %s mid-scan due to folder error: %s", folderCfg.Description(), err)
return err
}
m.updateLocalsFromScanning(folder, batch)
@@ -1820,7 +1811,7 @@ func (m *Model) internalScanFolderSubdirs(folder string, subDirs []string) error
}
if err := m.CheckFolderHealth(folder); err != nil {
l.Infof("Stopping folder %s mid-scan due to folder error: %s", folder, err)
l.Infof("Stopping folder %s mid-scan due to folder error: %s", folderCfg.Description(), err)
return err
} else if len(batch) > 0 {
m.updateLocalsFromScanning(folder, batch)
@@ -1896,13 +1887,13 @@ func (m *Model) internalScanFolderSubdirs(folder string, subDirs []string) error
})
if iterError != nil {
l.Infof("Stopping folder %s mid-scan due to folder error: %s", folder, iterError)
l.Infof("Stopping folder %s mid-scan due to folder error: %s", folderCfg.Description(), iterError)
return iterError
}
}
if err := m.CheckFolderHealth(folder); err != nil {
l.Infof("Stopping folder %s mid-scan due to folder error: %s", folder, err)
l.Infof("Stopping folder %s mid-scan due to folder error: %s", folderCfg.Description(), err)
return err
} else if len(batch) > 0 {
m.updateLocalsFromScanning(folder, batch)
@@ -1958,7 +1949,12 @@ func (m *Model) generateClusterConfig(device protocol.DeviceID) protocol.Cluster
var message protocol.ClusterConfig
m.fmut.RLock()
for _, folder := range m.deviceFolders[device] {
// The list of folders in the message is sorted, so we always get the
// same order.
folders := m.deviceFolders[device]
sort.Strings(folders)
for _, folder := range folders {
folderCfg := m.cfg.Folders()[folder]
fs := m.folderFiles[folder]
@@ -1971,12 +1967,8 @@ func (m *Model) generateClusterConfig(device protocol.DeviceID) protocol.Cluster
DisableTempIndexes: folderCfg.DisableTempIndexes,
}
for device := range m.folderDevices[folder] {
// DeviceID is a value type, but with an underlying array. Copy it
// so we don't grab aliases to the same array later on in device[:]
device := device
// TODO: Set read only bit when relevant, and when we have per device
// access controls.
// Devices are sorted, so we always get the same order.
for _, device := range m.folderDevices.sortedDevices(folder) {
deviceCfg := m.cfg.Devices()[device]
var indexID protocol.IndexID
@@ -2299,9 +2291,9 @@ func (m *Model) runnerExchangeError(folder config.FolderConfiguration, err error
if err != nil {
if oldErr != nil && oldErr.Error() != err.Error() {
l.Infof("Folder %q error changed: %q -> %q", folder.ID, oldErr, err)
l.Infof("Folder %s error changed: %q -> %q", folder.Description(), oldErr, err)
} else if oldErr == nil {
l.Warnf("Stopping folder %q - %v", folder.ID, err)
l.Warnf("Stopping folder %s - %v", folder.Description(), err)
}
if runnerExists {
runner.setError(err)
@@ -2604,3 +2596,13 @@ func (s folderDeviceSet) hasDevice(dev protocol.DeviceID) bool {
}
return false
}
// sortedDevices returns the list of devices for a given folder, sorted
func (s folderDeviceSet) sortedDevices(folder string) []protocol.DeviceID {
devs := make([]protocol.DeviceID, 0, len(s[folder]))
for dev := range s[folder] {
devs = append(devs, dev)
}
sort.Sort(protocol.DeviceIDs(devs))
return devs
}
+4 -4
View File
@@ -445,13 +445,13 @@ func TestClusterConfig(t *testing.T) {
t.Errorf("Incorrect number of devices %d != 2", l)
}
if id := r.Devices[0].ID; id != device1 {
t.Errorf("Incorrect device ID %x != %x", id, device1)
t.Errorf("Incorrect device ID %s != %s", id, device1)
}
if !r.Devices[0].Introducer {
t.Error("Device1 should be flagged as Introducer")
}
if id := r.Devices[1].ID; id != device2 {
t.Errorf("Incorrect device ID %x != %x", id, device2)
t.Errorf("Incorrect device ID %s != %s", id, device2)
}
if r.Devices[1].Introducer {
t.Error("Device2 should not be flagged as Introducer")
@@ -465,13 +465,13 @@ func TestClusterConfig(t *testing.T) {
t.Errorf("Incorrect number of devices %d != 2", l)
}
if id := r.Devices[0].ID; id != device1 {
t.Errorf("Incorrect device ID %x != %x", id, device1)
t.Errorf("Incorrect device ID %s != %s", id, device1)
}
if !r.Devices[0].Introducer {
t.Error("Device1 should be flagged as Introducer")
}
if id := r.Devices[1].ID; id != device2 {
t.Errorf("Incorrect device ID %x != %x", id, device2)
t.Errorf("Incorrect device ID %s != %s", id, device2)
}
if r.Devices[1].Introducer {
t.Error("Device2 should not be flagged as Introducer")
+48
View File
@@ -91,6 +91,7 @@ type rwFolder struct {
allowSparse bool
checkFreeSpace bool
ignoreDelete bool
fsync bool
copiers int
pullers int
@@ -126,6 +127,7 @@ func newRWFolder(model *Model, cfg config.FolderConfiguration, ver versioner.Ver
allowSparse: !cfg.DisableSparseFiles,
checkFreeSpace: cfg.MinDiskFreePct != 0,
ignoreDelete: cfg.IgnoreDelete,
fsync: cfg.Fsync,
queue: newJobQueue(),
pullTimer: time.NewTimer(time.Second),
@@ -1372,12 +1374,50 @@ func (f *rwFolder) dbUpdaterRoutine() {
tick := time.NewTicker(maxBatchTime)
defer tick.Stop()
var changedFiles []string
var changedDirs []string
if f.fsync {
changedFiles = make([]string, 0, maxBatchSize)
changedDirs = make([]string, 0, maxBatchSize)
}
syncFilesOnce := func(files []string, syncFn func(string) error) {
sort.Strings(files)
var lastFile string
for _, file := range files {
if lastFile == file {
continue
}
lastFile = file
if err := syncFn(file); err != nil {
l.Infof("fsync %q failed: %v", file, err)
}
}
}
handleBatch := func() {
found := false
var lastFile protocol.FileInfo
for _, job := range batch {
files = append(files, job.file)
if f.fsync {
// collect changed files and dirs
switch job.jobType {
case dbUpdateHandleFile, dbUpdateShortcutFile:
// fsyncing symlinks is only supported by MacOS
if !job.file.IsSymlink() {
changedFiles = append(changedFiles,
filepath.Join(f.dir, job.file.Name))
}
case dbUpdateHandleDir:
changedDirs = append(changedDirs, filepath.Join(f.dir, job.file.Name))
}
if job.jobType != dbUpdateShortcutFile {
changedDirs = append(changedDirs,
filepath.Dir(filepath.Join(f.dir, job.file.Name)))
}
}
if job.file.IsInvalid() || (job.file.IsDirectory() && !job.file.IsSymlink()) {
continue
}
@@ -1390,6 +1430,14 @@ func (f *rwFolder) dbUpdaterRoutine() {
lastFile = job.file
}
if f.fsync {
// sync files and dirs to disk
syncFilesOnce(changedFiles, osutil.SyncFile)
changedFiles = changedFiles[:0]
syncFilesOnce(changedDirs, osutil.SyncDir)
changedDirs = changedDirs[:0]
}
// All updates to file/folder objects that originated remotely
// (across the network) use this call to updateLocals
f.model.updateLocalsFromPulling(f.folderID, files)
+7
View File
@@ -77,6 +77,11 @@ func (w *AtomicWriter) Close() error {
// Try to not leave temp file around, but ignore error.
defer os.Remove(w.next.Name())
if err := w.next.Sync(); err != nil {
w.err = err
return err
}
if err := w.next.Close(); err != nil {
w.err = err
return err
@@ -97,6 +102,8 @@ func (w *AtomicWriter) Close() error {
return err
}
SyncDir(filepath.Dir(w.next.Name()))
// Set w.err to return appropriately for any future operations.
w.err = ErrClosed
+1 -4
View File
@@ -172,10 +172,7 @@ func copyFileContents(src, dst string) (err error) {
err = cerr
}
}()
if _, err = io.Copy(out, in); err != nil {
return
}
err = out.Sync()
_, err = io.Copy(out, in)
return
}
+37
View File
@@ -0,0 +1,37 @@
// Copyright (C) 2016 The Syncthing Authors.
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// You can obtain one at http://mozilla.org/MPL/2.0/.
package osutil
import (
"os"
"runtime"
)
func SyncFile(path string) error {
flag := 0
if runtime.GOOS == "windows" {
flag = os.O_WRONLY
}
fd, err := os.OpenFile(path, flag, 0)
if err != nil {
return err
}
defer fd.Close()
// MacOS and Windows do not flush the disk cache
if err := fd.Sync(); err != nil {
return err
}
return nil
}
func SyncDir(path string) error {
if runtime.GOOS == "windows" {
// not supported by Windows
return nil
}
return SyncFile(path)
}
+5
View File
@@ -134,3 +134,8 @@ func (i *IndexID) Unmarshal(bs []byte) error {
func NewIndexID() IndexID {
return IndexID(rand.Int64())
}
func (f Folder) Description() string {
// used by logging stuff
return fmt.Sprintf("%q (%s)", f.Label, f.ID)
}
+15
View File
@@ -203,3 +203,18 @@ func untypeoify(s string) string {
s = strings.Replace(s, "8", "B", -1)
return s
}
// DeviceIDs is a sortable slice of DeviceID
type DeviceIDs []DeviceID
func (l DeviceIDs) Len() int {
return len(l)
}
func (l DeviceIDs) Less(a, b int) bool {
return l[a].Compare(l[b]) == -1
}
func (l DeviceIDs) Swap(a, b int) {
l[a], l[b] = l[b], l[a]
}
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "STDISCOSRV" "1" "November 12, 2016" "v0.14" "Syncthing"
.TH "STDISCOSRV" "1" "November 21, 2016" "v0.14" "Syncthing"
.SH NAME
stdiscosrv \- Syncthing Discovery Server
.
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "STRELAYSRV" "1" "November 12, 2016" "v0.14" "Syncthing"
.TH "STRELAYSRV" "1" "November 21, 2016" "v0.14" "Syncthing"
.SH NAME
strelaysrv \- Syncthing Relay Server
.
+14 -9
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-BEP" "7" "November 12, 2016" "v0.14" "Syncthing"
.TH "SYNCTHING-BEP" "7" "November 21, 2016" "v0.14" "Syncthing"
.SH NAME
syncthing-bep \- Block Exchange Protocol v1
.
@@ -290,14 +290,15 @@ message Folder {
}
message Device {
bytes id = 1;
string name = 2;
repeated string addresses = 3;
Compression compression = 4;
string cert_name = 5;
int64 max_sequence = 6;
bool introducer = 7;
uint64 index_id = 8;
bytes id = 1;
string name = 2;
repeated string addresses = 3;
Compression compression = 4;
string cert_name = 5;
int64 max_sequence = 6;
bool introducer = 7;
uint64 index_id = 8;
bool skip_introduction_removals = 9;
}
enum Compression {
@@ -373,6 +374,10 @@ introducers.
.sp
The \fBindex id\fP field contains the unique identifier for the current set of
index data. See \fI\%Delta Index Exchange\fP for the usage of this field.
.sp
The \fBskip introduction removals\fP field signifies if the remote device has
opted to ignore introduction removals for the given device. This setting is
copied across as we are being introduced to a new device.
.SS Index and Index Update
.sp
The Index and Index Update messages define the contents of the senders
+26 -7
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-CONFIG" "5" "November 12, 2016" "v0.14" "Syncthing"
.TH "SYNCTHING-CONFIG" "5" "November 21, 2016" "v0.14" "Syncthing"
.SH NAME
syncthing-config \- Syncthing Configuration
.
@@ -97,6 +97,7 @@ The following shows an example of the default configuration file (IDs will diffe
<maxConflicts>\-1</maxConflicts>
<disableSparseFiles>false</disableSparseFiles>
<disableTempIndexes>false</disableTempIndexes>
<fsync>false</fsync>
</folder>
<device id="3LT2GA5\-CQI4XJM\-WTZ264P\-MLOGMHL\-MCRLDNT\-MZV4RD3\-KA745CL\-OGAERQZ" name="syno" compression="metadata" introducer="false">
<address>dynamic</address>
@@ -161,7 +162,7 @@ migration from previous formats.
.nf
.ft C
<folder id="zj2AA\-q55a7" label="Default Folder (zj2AA\-q55a7)" path="/Users/jb/Sync/" type="readwrite" rescanIntervalS="60" ignorePerms="false" autoNormalize="true" ro="false">
<device id="3LT2GA5\-CQI4XJM\-WTZ264P\-MLOGMHL\-MCRLDNT\-MZV4RD3\-KA745CL\-OGAERQZ"></device>
<device id="3LT2GA5\-CQI4XJM\-WTZ264P\-MLOGMHL\-MCRLDNT\-MZV4RD3\-KA745CL\-OGAERQZ" introducedBy="2CYF2WQ\-AKZO2QZ\-JAKWLYD\-AGHMQUM\-BGXUOIS\-GYILW34\-HJG3DUK\-LRRYQAR"></device>
<minDiskFreePct>1</minDiskFreePct>
<versioning></versioning>
<copiers>0</copiers>
@@ -175,6 +176,7 @@ migration from previous formats.
<maxConflicts>\-1</maxConflicts>
<disableSparseFiles>false</disableSparseFiles>
<disableTempIndexes>false</disableTempIndexes>
<fsync>false</fsync>
</folder>
.ft P
.fi
@@ -225,10 +227,13 @@ The following child elements may exist:
.INDENT 0.0
.TP
.B device
These must have the \fBid\fP attribute and nothing else. Mentioned devices
are those that will be sharing the folder in question. Each mentioned
device must have a separate \fBdevice\fP element later in the file. It is
customary that the local device ID is included in all repositories.
These must have the \fBid\fP attribute and can have an \fBintroducedBy\fP attribute,
identifying the device that introduced us to share this folder with the given device.
If the original introducer unshares this folder with this device, our device will follow
and unshare the folder (subject to skipIntroductionRemovals being false on the introducer device).
All mentioned devices are those that will be sharing the folder in question.
Each mentioned device must have a separate \fBdevice\fP element later in the file.
It is customary that the local device ID is included in all repositories.
Syncthing will currently add this automatically if it is not present in
the configuration file.
.TP
@@ -301,6 +306,12 @@ sparse files will not be created.
By default, devices exchange information about blocks available in
transfers that are still in progress. When set to true, such information
is not exchanged for this folder.
.INDENT 7.0
.TP
.B fsync
Transfer updated (from other devices) files to permanent storage before
committing the changes to the internal database.
.UNINDENT
.UNINDENT
.SH DEVICE ELEMENT
.INDENT 0.0
@@ -308,7 +319,7 @@ is not exchanged for this folder.
.sp
.nf
.ft C
<device id="5SYI2FS\-LW6YAXI\-JJDYETS\-NDBBPIO\-256MWBO\-XDPXWVG\-24QPUM4\-PDW4UQU" name="syno" compression="metadata" introducer="false">
<device id="5SYI2FS\-LW6YAXI\-JJDYETS\-NDBBPIO\-256MWBO\-XDPXWVG\-24QPUM4\-PDW4UQU" name="syno" compression="metadata" introducer="false" introducedBy="2CYF2WQ\-AKZO2QZ\-JAKWLYD\-AGHMQUM\-BGXUOIS\-GYILW34\-HJG3DUK\-LRRYQAR">
<address>dynamic</address>
</device>
<device id="2CYF2WQ\-AKZO2QZ\-JAKWLYD\-AGHMQUM\-BGXUOIS\-GYILW34\-HJG3DUK\-LRRYQAR" name="syno local" compression="metadata" introducer="false">
@@ -354,6 +365,14 @@ Disable all compression.
.B introducer
Set to true if this device should be trusted as an introducer, i.e. we
should copy their list of devices per folder when connecting.
.TP
.B skipIntroductionRemovals
Set to true if you wish to follow only introductions and not de\-introductions.
For example, if this is set, we would not remove a device that we were introduced
to even if the original introducer is no longer listing the remote device as known.
.TP
.B introducedBy
Defines which device has introduced us to this device. Used only for following de\-introductions.
.UNINDENT
.sp
In addition, one or more \fBaddress\fP child elements must be present. Each
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-DEVICE-IDS" "7" "November 12, 2016" "v0.14" "Syncthing"
.TH "SYNCTHING-DEVICE-IDS" "7" "November 21, 2016" "v0.14" "Syncthing"
.SH NAME
syncthing-device-ids \- Understanding Device IDs
.
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-EVENT-API" "7" "November 12, 2016" "v0.14" "Syncthing"
.TH "SYNCTHING-EVENT-API" "7" "November 21, 2016" "v0.14" "Syncthing"
.SH NAME
syncthing-event-api \- Event API
.
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-FAQ" "7" "November 12, 2016" "v0.14" "Syncthing"
.TH "SYNCTHING-FAQ" "7" "November 21, 2016" "v0.14" "Syncthing"
.SH NAME
syncthing-faq \- Frequently Asked Questions
.
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-GLOBALDISCO" "7" "November 12, 2016" "v0.14" "Syncthing"
.TH "SYNCTHING-GLOBALDISCO" "7" "November 21, 2016" "v0.14" "Syncthing"
.SH NAME
syncthing-globaldisco \- Global Discovery Protocol v3
.
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-LOCALDISCO" "7" "November 12, 2016" "v0.14" "Syncthing"
.TH "SYNCTHING-LOCALDISCO" "7" "November 21, 2016" "v0.14" "Syncthing"
.SH NAME
syncthing-localdisco \- Local Discovery Protocol v4
.
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-NETWORKING" "7" "November 12, 2016" "v0.14" "Syncthing"
.TH "SYNCTHING-NETWORKING" "7" "November 21, 2016" "v0.14" "Syncthing"
.SH NAME
syncthing-networking \- Firewall Setup
.
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-RELAY" "7" "November 12, 2016" "v0.14" "Syncthing"
.TH "SYNCTHING-RELAY" "7" "November 21, 2016" "v0.14" "Syncthing"
.SH NAME
syncthing-relay \- Relay Protocol v1
.
+2 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-REST-API" "7" "November 12, 2016" "v0.14" "Syncthing"
.TH "SYNCTHING-REST-API" "7" "November 21, 2016" "v0.14" "Syncthing"
.SH NAME
syncthing-rest-api \- REST API
.
@@ -86,6 +86,7 @@ Returns the current configuration.
"maxConflicts": 10,
"disableSparseFiles": false,
"disableTempIndexes": false,
"fsync": false,
"invalid": ""
}
],
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-SECURITY" "7" "November 12, 2016" "v0.14" "Syncthing"
.TH "SYNCTHING-SECURITY" "7" "November 21, 2016" "v0.14" "Syncthing"
.SH NAME
syncthing-security \- Security Principles
.
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-STIGNORE" "5" "November 12, 2016" "v0.14" "Syncthing"
.TH "SYNCTHING-STIGNORE" "5" "November 21, 2016" "v0.14" "Syncthing"
.SH NAME
syncthing-stignore \- Prevent files from being synchronized to other nodes
.
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING-VERSIONING" "7" "November 12, 2016" "v0.14" "Syncthing"
.TH "SYNCTHING-VERSIONING" "7" "November 21, 2016" "v0.14" "Syncthing"
.SH NAME
syncthing-versioning \- Keep automatic backups of deleted files by other nodes
.
+1 -1
View File
@@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SYNCTHING" "1" "November 12, 2016" "v0.14" "Syncthing"
.TH "SYNCTHING" "1" "November 21, 2016" "v0.14" "Syncthing"
.SH NAME
syncthing \- Syncthing
.