2303 lines
209 KiB
HTML
2303 lines
209 KiB
HTML
|
<!DOCTYPE html><html lang="fr">
|
|||
|
<head><meta charset="utf-8">
|
|||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|||
|
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"><title>Synchronisation fichiers avec Unison sous Linux - YannStatic</title>
|
|||
|
|
|||
|
<meta name="description" content="Unison est un outil de synchronisation de fichiers gratuit, open source, multiplateforme et bidirectionnel . Il est utilisé pour stocker deux répliques de fi...">
|
|||
|
<link rel="canonical" href="https://static.rnmkcy.eu/2022/09/20/unison_alternative_rsync.html"><link rel="alternate" type="application/rss+xml" title="YannStatic" href="/feed.xml">
|
|||
|
|
|||
|
<!-- - include head/favicon.html - -->
|
|||
|
<link rel="shortcut icon" type="image/png" href="/assets/favicon/favicon.png"><link rel="stylesheet" href="/assets/css/main.css"><link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.13/css/all.css" ><!-- start custom head snippets --><link rel="stylesheet" href="/assets/css/expand.css">
|
|||
|
<!-- end custom head snippets --><script>(function() {
|
|||
|
window.isArray = function(val) {
|
|||
|
return Object.prototype.toString.call(val) === '[object Array]';
|
|||
|
};
|
|||
|
window.isString = function(val) {
|
|||
|
return typeof val === 'string';
|
|||
|
};
|
|||
|
|
|||
|
window.hasEvent = function(event) {
|
|||
|
return 'on'.concat(event) in window.document;
|
|||
|
};
|
|||
|
|
|||
|
window.isOverallScroller = function(node) {
|
|||
|
return node === document.documentElement || node === document.body || node === window;
|
|||
|
};
|
|||
|
|
|||
|
window.isFormElement = function(node) {
|
|||
|
var tagName = node.tagName;
|
|||
|
return tagName === 'INPUT' || tagName === 'SELECT' || tagName === 'TEXTAREA';
|
|||
|
};
|
|||
|
|
|||
|
window.pageLoad = (function () {
|
|||
|
var loaded = false, cbs = [];
|
|||
|
window.addEventListener('load', function () {
|
|||
|
var i;
|
|||
|
loaded = true;
|
|||
|
if (cbs.length > 0) {
|
|||
|
for (i = 0; i < cbs.length; i++) {
|
|||
|
cbs[i]();
|
|||
|
}
|
|||
|
}
|
|||
|
});
|
|||
|
return {
|
|||
|
then: function(cb) {
|
|||
|
cb && (loaded ? cb() : (cbs.push(cb)));
|
|||
|
}
|
|||
|
};
|
|||
|
})();
|
|||
|
})();
|
|||
|
(function() {
|
|||
|
window.throttle = function(func, wait) {
|
|||
|
var args, result, thisArg, timeoutId, lastCalled = 0;
|
|||
|
|
|||
|
function trailingCall() {
|
|||
|
lastCalled = new Date;
|
|||
|
timeoutId = null;
|
|||
|
result = func.apply(thisArg, args);
|
|||
|
}
|
|||
|
return function() {
|
|||
|
var now = new Date,
|
|||
|
remaining = wait - (now - lastCalled);
|
|||
|
|
|||
|
args = arguments;
|
|||
|
thisArg = this;
|
|||
|
|
|||
|
if (remaining <= 0) {
|
|||
|
clearTimeout(timeoutId);
|
|||
|
timeoutId = null;
|
|||
|
lastCalled = now;
|
|||
|
result = func.apply(thisArg, args);
|
|||
|
} else if (!timeoutId) {
|
|||
|
timeoutId = setTimeout(trailingCall, remaining);
|
|||
|
}
|
|||
|
return result;
|
|||
|
};
|
|||
|
};
|
|||
|
})();
|
|||
|
(function() {
|
|||
|
var Set = (function() {
|
|||
|
var add = function(item) {
|
|||
|
var i, data = this._data;
|
|||
|
for (i = 0; i < data.length; i++) {
|
|||
|
if (data[i] === item) {
|
|||
|
return;
|
|||
|
}
|
|||
|
}
|
|||
|
this.size ++;
|
|||
|
data.push(item);
|
|||
|
return data;
|
|||
|
};
|
|||
|
|
|||
|
var Set = function(data) {
|
|||
|
this.size = 0;
|
|||
|
this._data = [];
|
|||
|
var i;
|
|||
|
if (data.length > 0) {
|
|||
|
for (i = 0; i < data.length; i++) {
|
|||
|
add.call(this, data[i]);
|
|||
|
}
|
|||
|
}
|
|||
|
};
|
|||
|
Set.prototype.add = add;
|
|||
|
Set.prototype.get = function(index) { return this._data[index]; };
|
|||
|
Set.prototype.has = function(item) {
|
|||
|
var i, data = this._data;
|
|||
|
for (i = 0; i < data.length; i++) {
|
|||
|
if (this.get(i) === item) {
|
|||
|
return true;
|
|||
|
}
|
|||
|
}
|
|||
|
return false;
|
|||
|
};
|
|||
|
Set.prototype.is = function(map) {
|
|||
|
if (map._data.length !== this._data.length) { return false; }
|
|||
|
var i, j, flag, tData = this._data, mData = map._data;
|
|||
|
for (i = 0; i < tData.length; i++) {
|
|||
|
for (flag = false, j = 0; j < mData.length; j++) {
|
|||
|
if (tData[i] === mData[j]) {
|
|||
|
flag = true;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
if (!flag) { return false; }
|
|||
|
}
|
|||
|
return true;
|
|||
|
};
|
|||
|
Set.prototype.values = function() {
|
|||
|
return this._data;
|
|||
|
};
|
|||
|
return Set;
|
|||
|
})();
|
|||
|
|
|||
|
window.Lazyload = (function(doc) {
|
|||
|
var queue = {js: [], css: []}, sources = {js: {}, css: {}}, context = this;
|
|||
|
var createNode = function(name, attrs) {
|
|||
|
var node = doc.createElement(name), attr;
|
|||
|
for (attr in attrs) {
|
|||
|
if (attrs.hasOwnProperty(attr)) {
|
|||
|
node.setAttribute(attr, attrs[attr]);
|
|||
|
}
|
|||
|
}
|
|||
|
return node;
|
|||
|
};
|
|||
|
var end = function(type, url) {
|
|||
|
var s, q, qi, cbs, i, j, cur, val, flag;
|
|||
|
if (type === 'js' || type ==='css') {
|
|||
|
s = sources[type], q = queue[type];
|
|||
|
s[url] = true;
|
|||
|
for (i = 0; i < q.length; i++) {
|
|||
|
cur = q[i];
|
|||
|
if (cur.urls.has(url)) {
|
|||
|
qi = cur, val = qi.urls.values();
|
|||
|
qi && (cbs = qi.callbacks);
|
|||
|
for (flag = true, j = 0; j < val.length; j++) {
|
|||
|
cur = val[j];
|
|||
|
if (!s[cur]) {
|
|||
|
flag = false;
|
|||
|
}
|
|||
|
}
|
|||
|
if (flag && cbs && cbs.length > 0) {
|
|||
|
for (j = 0; j < cbs.length; j++) {
|
|||
|
cbs[j].call(context);
|
|||
|
}
|
|||
|
qi.load = true;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
};
|
|||
|
var load = function(type, urls, callback) {
|
|||
|
var s, q, qi, node, i, cur,
|
|||
|
_urls = typeof urls === 'string' ? new Set([urls]) : new Set(urls), val, url;
|
|||
|
if (type === 'js' || type ==='css') {
|
|||
|
s = sources[type], q = queue[type];
|
|||
|
for (i = 0; i < q.length; i++) {
|
|||
|
cur = q[i];
|
|||
|
if (_urls.is(cur.urls)) {
|
|||
|
qi = cur;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
val = _urls.values();
|
|||
|
if (qi) {
|
|||
|
callback && (qi.load || qi.callbacks.push(callback));
|
|||
|
callback && (qi.load && callback());
|
|||
|
} else {
|
|||
|
q.push({
|
|||
|
urls: _urls,
|
|||
|
callbacks: callback ? [callback] : [],
|
|||
|
load: false
|
|||
|
});
|
|||
|
for (i = 0; i < val.length; i++) {
|
|||
|
node = null, url = val[i];
|
|||
|
if (s[url] === undefined) {
|
|||
|
(type === 'js' ) && (node = createNode('script', { src: url }));
|
|||
|
(type === 'css') && (node = createNode('link', { rel: 'stylesheet', href: url }));
|
|||
|
if (node) {
|
|||
|
node.onload = (function(type, url) {
|
|||
|
return function() {
|
|||
|
end(type, url);
|
|||
|
};
|
|||
|
})(type, url);
|
|||
|
(doc.head || doc.body).appendChild(node);
|
|||
|
s[url] = false;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
};
|
|||
|
return {
|
|||
|
js: function(url, callback) {
|
|||
|
load('js', url, callback);
|
|||
|
},
|
|||
|
css: function(url, callback) {
|
|||
|
load('css', url, callback);
|
|||
|
}
|
|||
|
};
|
|||
|
})(this.document);
|
|||
|
})();
|
|||
|
</script><script>
|
|||
|
(function() {
|
|||
|
var TEXT_VARIABLES = {
|
|||
|
version: '2.2.6',
|
|||
|
sources: {
|
|||
|
font_awesome: 'https://use.fontawesome.com/releases/v5.0.13/css/all.css',
|
|||
|
jquery: '/assets/js/jquery.min.js',
|
|||
|
leancloud_js_sdk: '//cdn.jsdelivr.net/npm/leancloud-storage@3.13.2/dist/av-min.js',
|
|||
|
chart: 'https://cdn.bootcss.com/Chart.js/2.7.2/Chart.bundle.min.js',
|
|||
|
gitalk: {
|
|||
|
js: 'https://cdn.bootcss.com/gitalk/1.2.2/gitalk.min.js',
|
|||
|
css: 'https://cdn.bootcss.com/gitalk/1.2.2/gitalk.min.css'
|
|||
|
},
|
|||
|
valine: 'https://unpkg.com/valine/dist/Valine.min.js'
|
|||
|
},
|
|||
|
site: {
|
|||
|
toc: {
|
|||
|
selectors: 'h1,h2,h3'
|
|||
|
}
|
|||
|
},
|
|||
|
paths: {
|
|||
|
search_js: '/assets/search.js'
|
|||
|
}
|
|||
|
};
|
|||
|
window.TEXT_VARIABLES = TEXT_VARIABLES;
|
|||
|
})();
|
|||
|
</script>
|
|||
|
</head>
|
|||
|
<body>
|
|||
|
<div class="root" data-is-touch="false">
|
|||
|
<div class="layout--page js-page-root"><!----><div class="page__main js-page-main page__viewport hide-footer has-aside has-aside cell cell--auto">
|
|||
|
|
|||
|
<div class="page__main-inner"><div class="page__header d-print-none"><header class="header"><div class="main">
|
|||
|
<div class="header__title">
|
|||
|
<div class="header__brand"><svg id="svg" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="400" height="478.9473684210526" viewBox="0, 0, 400,478.9473684210526"><g id="svgg"><path id="path0" d="M308.400 56.805 C 306.970 56.966,303.280 57.385,300.200 57.738 C 290.906 58.803,278.299 59.676,269.200 59.887 L 260.600 60.085 259.400 61.171 C 258.010 62.428,256.198 63.600,255.645 63.600 C 255.070 63.600,252.887 65.897,252.598 66.806 C 252.460 67.243,252.206 67.600,252.034 67.600 C 251.397 67.600,247.206 71.509,247.202 72.107 C 247.201 72.275,246.390 73.190,245.400 74.138 C 243.961 75.517,243.598 76.137,243.592 77.231 C 243.579 79.293,241.785 83.966,240.470 85.364 C 239.176 86.740,238.522 88.365,237.991 91.521 C 237.631 93.665,236.114 97.200,235.554 97.200 C 234.938 97.200,232.737 102.354,232.450 104.472 C 232.158 106.625,230.879 109.226,229.535 110.400 C 228.933 110.926,228.171 113.162,226.434 119.500 C 226.178 120.435,225.795 121.200,225.584 121.200 C 225.373 121.200,225.200 121.476,225.200 121.813 C 225.200 122.149,224.885 122.541,224.500 122.683 C 223.606 123.013,223.214 123.593,223.204 124.600 C 223.183 126.555,220.763 132.911,219.410 134.562 C 218.443 135.742,217.876 136.956,217.599 138.440 C 217.041 141.424,215.177 146.434,214.532 146.681 C 214.240 146.794,214.000 147.055,214.000 147.261 C 214.000 147.467,213.550 148.086,213.000 148.636 C 212.450 149.186,212.000 149.893,212.000 150.208 C 212.000 151.386,208.441 154.450,207.597 153.998 C 206.319 153.315,204.913 150.379,204.633 147.811 C 204.365 145.357,202.848 142.147,201.759 141.729 C 200.967 141.425,199.200 137.451,199.200 135.974 C 199.200 134.629,198.435 133.224,196.660 131.311 C 195.363 129.913,194.572 128.123,193.870 125.000 C 193.623 123.900,193.236 122.793,193.010 122.540 C 190.863 120.133,190.147 118.880,188.978 115.481 C 188.100 112.928,187.151 111.003,186.254 109.955 C 185.358 108.908,184.518 107.204,183.847 105.073 C 183.280 103.273,182.497 101.329,182.108 100.753 C 181.719 100.177,180.904 98.997,180.298 98.131 C 179.693 97.265,178.939 95.576,178.624 94.378 C 178.041 92.159,177.125 90.326,175.023 87.168 C 174.375 86.196,173.619 84.539,173.342 83.486 C 172.800 81.429,171.529 79.567,170.131 78.785 C 169.654 78.517,168.697 77.511,168.006 76.549 C 167.316 75.587,166.594 74.800,166.402 74.800 C 166.210 74.800,164.869 73.633,163.421 72.206 C 160.103 68.936,161.107 69.109,146.550 69.301 C 133.437 69.474,128.581 70.162,126.618 72.124 C 126.248 72.495,125.462 72.904,124.872 73.033 C 124.282 73.163,123.088 73.536,122.219 73.863 C 121.349 74.191,119.028 74.638,117.061 74.858 C 113.514 75.254,109.970 76.350,108.782 77.419 C 107.652 78.436,100.146 80.400,97.388 80.400 C 95.775 80.400,93.167 81.360,91.200 82.679 C 90.430 83.195,89.113 83.804,88.274 84.031 C 85.875 84.681,78.799 90.910,74.400 96.243 L 73.400 97.456 73.455 106.028 C 73.526 117.055,74.527 121.238,77.820 124.263 C 78.919 125.273,80.400 127.902,80.400 128.842 C 80.400 129.202,81.075 130.256,81.900 131.186 C 83.563 133.059,85.497 136.346,86.039 138.216 C 86.233 138.886,87.203 140.207,88.196 141.153 C 89.188 142.098,90.000 143.104,90.000 143.388 C 90.000 144.337,92.129 148.594,92.869 149.123 C 93.271 149.410,93.600 149.831,93.600 150.059 C 93.600 150.286,93.932 150.771,94.337 151.136 C 94.743 151.501,95.598 153.004,96.237 154.475 C 96.877 155.947,97.760 157.351,98.200 157.596 C 98.640 157.841,99.900 159.943,101.000 162.267 C 102.207 164.817,103.327 166.644,103.825 166.876 C 104.278 167.087,105.065 168.101,105.573 169.130 C 107.658 173.348,108.097 174.093,110.006 176.647 C 111.103 178.114,112.000 179.725,112.000 180.227 C 112.000 181.048,113.425 183.163,114.678 184.200 C 115.295 184.711,117.396 188.733,117.720 190.022 C 117.855 190.562,118.603 191.633,119.381 192.402 C 120.160 193.171,121.496 195.258,122.351 197.039 C 123.206 198.820,124.167 200.378,124.487 200.501 C 124.807 200.624,125.953 202.496,127.034 204.662 C 128.114 206.828,129.676 209.299,130.505 210.153 C 131.333 211.007,132.124 212.177,132.262 212.753 C 132.618 214.239,134.291 217.048,136.288 219.5
|
|||
|
" href="/">YannStatic</a></div><!--<button class="button button--secondary button--circle search-button js-search-toggle"><i class="fas fa-search"></i></button>--><!-- <li><button class="button button--secondary button--circle search-button js-search-toggle"><i class="fas fa-search"></i></button></li> -->
|
|||
|
<!-- Champ de recherche -->
|
|||
|
<div id="searchbox" class="search search--dark" style="visibility: visible">
|
|||
|
<div class="main">
|
|||
|
<div class="search__header"></div>
|
|||
|
<div class="search-bar">
|
|||
|
<div class="search-box js-search-box">
|
|||
|
<div class="search-box__icon-search"><i class="fas fa-search"></i></div>
|
|||
|
<input id="search-input" type="text" />
|
|||
|
<!-- <div class="search-box__icon-clear js-icon-clear">
|
|||
|
<a><i class="fas fa-times"></i></a>
|
|||
|
</div> -->
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
<!-- Script pointing to search-script.js -->
|
|||
|
<script>/*!
|
|||
|
* Simple-Jekyll-Search
|
|||
|
* Copyright 2015-2020, Christian Fei
|
|||
|
* Licensed under the MIT License.
|
|||
|
*/
|
|||
|
|
|||
|
(function(){
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$Templater_7 = {
|
|||
|
compile: compile,
|
|||
|
setOptions: setOptions
|
|||
|
}
|
|||
|
|
|||
|
const options = {}
|
|||
|
options.pattern = /\{(.*?)\}/g
|
|||
|
options.template = ''
|
|||
|
options.middleware = function () {}
|
|||
|
|
|||
|
function setOptions (_options) {
|
|||
|
options.pattern = _options.pattern || options.pattern
|
|||
|
options.template = _options.template || options.template
|
|||
|
if (typeof _options.middleware === 'function') {
|
|||
|
options.middleware = _options.middleware
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function compile (data) {
|
|||
|
return options.template.replace(options.pattern, function (match, prop) {
|
|||
|
const value = options.middleware(prop, data[prop], options.template)
|
|||
|
if (typeof value !== 'undefined') {
|
|||
|
return value
|
|||
|
}
|
|||
|
return data[prop] || match
|
|||
|
})
|
|||
|
}
|
|||
|
|
|||
|
'use strict';
|
|||
|
|
|||
|
function fuzzysearch (needle, haystack) {
|
|||
|
var tlen = haystack.length;
|
|||
|
var qlen = needle.length;
|
|||
|
if (qlen > tlen) {
|
|||
|
return false;
|
|||
|
}
|
|||
|
if (qlen === tlen) {
|
|||
|
return needle === haystack;
|
|||
|
}
|
|||
|
outer: for (var i = 0, j = 0; i < qlen; i++) {
|
|||
|
var nch = needle.charCodeAt(i);
|
|||
|
while (j < tlen) {
|
|||
|
if (haystack.charCodeAt(j++) === nch) {
|
|||
|
continue outer;
|
|||
|
}
|
|||
|
}
|
|||
|
return false;
|
|||
|
}
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
var _$fuzzysearch_1 = fuzzysearch;
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
/* removed: const _$fuzzysearch_1 = require('fuzzysearch') */;
|
|||
|
|
|||
|
var _$FuzzySearchStrategy_5 = new FuzzySearchStrategy()
|
|||
|
|
|||
|
function FuzzySearchStrategy () {
|
|||
|
this.matches = function (string, crit) {
|
|||
|
return _$fuzzysearch_1(crit.toLowerCase(), string.toLowerCase())
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$LiteralSearchStrategy_6 = new LiteralSearchStrategy()
|
|||
|
|
|||
|
function LiteralSearchStrategy () {
|
|||
|
this.matches = function (str, crit) {
|
|||
|
if (!str) return false
|
|||
|
|
|||
|
str = str.trim().toLowerCase()
|
|||
|
crit = crit.trim().toLowerCase()
|
|||
|
|
|||
|
return crit.split(' ').filter(function (word) {
|
|||
|
return str.indexOf(word) >= 0
|
|||
|
}).length === crit.split(' ').length
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$Repository_4 = {
|
|||
|
put: put,
|
|||
|
clear: clear,
|
|||
|
search: search,
|
|||
|
setOptions: __setOptions_4
|
|||
|
}
|
|||
|
|
|||
|
/* removed: const _$FuzzySearchStrategy_5 = require('./SearchStrategies/FuzzySearchStrategy') */;
|
|||
|
/* removed: const _$LiteralSearchStrategy_6 = require('./SearchStrategies/LiteralSearchStrategy') */;
|
|||
|
|
|||
|
function NoSort () {
|
|||
|
return 0
|
|||
|
}
|
|||
|
|
|||
|
const data = []
|
|||
|
let opt = {}
|
|||
|
|
|||
|
opt.fuzzy = false
|
|||
|
opt.limit = 10
|
|||
|
opt.searchStrategy = opt.fuzzy ? _$FuzzySearchStrategy_5 : _$LiteralSearchStrategy_6
|
|||
|
opt.sort = NoSort
|
|||
|
opt.exclude = []
|
|||
|
|
|||
|
function put (data) {
|
|||
|
if (isObject(data)) {
|
|||
|
return addObject(data)
|
|||
|
}
|
|||
|
if (isArray(data)) {
|
|||
|
return addArray(data)
|
|||
|
}
|
|||
|
return undefined
|
|||
|
}
|
|||
|
function clear () {
|
|||
|
data.length = 0
|
|||
|
return data
|
|||
|
}
|
|||
|
|
|||
|
function isObject (obj) {
|
|||
|
return Boolean(obj) && Object.prototype.toString.call(obj) === '[object Object]'
|
|||
|
}
|
|||
|
|
|||
|
function isArray (obj) {
|
|||
|
return Boolean(obj) && Object.prototype.toString.call(obj) === '[object Array]'
|
|||
|
}
|
|||
|
|
|||
|
function addObject (_data) {
|
|||
|
data.push(_data)
|
|||
|
return data
|
|||
|
}
|
|||
|
|
|||
|
function addArray (_data) {
|
|||
|
const added = []
|
|||
|
clear()
|
|||
|
for (let i = 0, len = _data.length; i < len; i++) {
|
|||
|
if (isObject(_data[i])) {
|
|||
|
added.push(addObject(_data[i]))
|
|||
|
}
|
|||
|
}
|
|||
|
return added
|
|||
|
}
|
|||
|
|
|||
|
function search (crit) {
|
|||
|
if (!crit) {
|
|||
|
return []
|
|||
|
}
|
|||
|
return findMatches(data, crit, opt.searchStrategy, opt).sort(opt.sort)
|
|||
|
}
|
|||
|
|
|||
|
function __setOptions_4 (_opt) {
|
|||
|
opt = _opt || {}
|
|||
|
|
|||
|
opt.fuzzy = _opt.fuzzy || false
|
|||
|
opt.limit = _opt.limit || 10
|
|||
|
opt.searchStrategy = _opt.fuzzy ? _$FuzzySearchStrategy_5 : _$LiteralSearchStrategy_6
|
|||
|
opt.sort = _opt.sort || NoSort
|
|||
|
opt.exclude = _opt.exclude || []
|
|||
|
}
|
|||
|
|
|||
|
function findMatches (data, crit, strategy, opt) {
|
|||
|
const matches = []
|
|||
|
for (let i = 0; i < data.length && matches.length < opt.limit; i++) {
|
|||
|
const match = findMatchesInObject(data[i], crit, strategy, opt)
|
|||
|
if (match) {
|
|||
|
matches.push(match)
|
|||
|
}
|
|||
|
}
|
|||
|
return matches
|
|||
|
}
|
|||
|
|
|||
|
function findMatchesInObject (obj, crit, strategy, opt) {
|
|||
|
for (const key in obj) {
|
|||
|
if (!isExcluded(obj[key], opt.exclude) && strategy.matches(obj[key], crit)) {
|
|||
|
return obj
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function isExcluded (term, excludedTerms) {
|
|||
|
for (let i = 0, len = excludedTerms.length; i < len; i++) {
|
|||
|
const excludedTerm = excludedTerms[i]
|
|||
|
if (new RegExp(excludedTerm).test(term)) {
|
|||
|
return true
|
|||
|
}
|
|||
|
}
|
|||
|
return false
|
|||
|
}
|
|||
|
|
|||
|
/* globals ActiveXObject:false */
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$JSONLoader_2 = {
|
|||
|
load: load
|
|||
|
}
|
|||
|
|
|||
|
function load (location, callback) {
|
|||
|
const xhr = getXHR()
|
|||
|
xhr.open('GET', location, true)
|
|||
|
xhr.onreadystatechange = createStateChangeListener(xhr, callback)
|
|||
|
xhr.send()
|
|||
|
}
|
|||
|
|
|||
|
function createStateChangeListener (xhr, callback) {
|
|||
|
return function () {
|
|||
|
if (xhr.readyState === 4 && xhr.status === 200) {
|
|||
|
try {
|
|||
|
callback(null, JSON.parse(xhr.responseText))
|
|||
|
} catch (err) {
|
|||
|
callback(err, null)
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function getXHR () {
|
|||
|
return window.XMLHttpRequest ? new window.XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP')
|
|||
|
}
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$OptionsValidator_3 = function OptionsValidator (params) {
|
|||
|
if (!validateParams(params)) {
|
|||
|
throw new Error('-- OptionsValidator: required options missing')
|
|||
|
}
|
|||
|
|
|||
|
if (!(this instanceof OptionsValidator)) {
|
|||
|
return new OptionsValidator(params)
|
|||
|
}
|
|||
|
|
|||
|
const requiredOptions = params.required
|
|||
|
|
|||
|
this.getRequiredOptions = function () {
|
|||
|
return requiredOptions
|
|||
|
}
|
|||
|
|
|||
|
this.validate = function (parameters) {
|
|||
|
const errors = []
|
|||
|
requiredOptions.forEach(function (requiredOptionName) {
|
|||
|
if (typeof parameters[requiredOptionName] === 'undefined') {
|
|||
|
errors.push(requiredOptionName)
|
|||
|
}
|
|||
|
})
|
|||
|
return errors
|
|||
|
}
|
|||
|
|
|||
|
function validateParams (params) {
|
|||
|
if (!params) {
|
|||
|
return false
|
|||
|
}
|
|||
|
return typeof params.required !== 'undefined' && params.required instanceof Array
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$utils_9 = {
|
|||
|
merge: merge,
|
|||
|
isJSON: isJSON
|
|||
|
}
|
|||
|
|
|||
|
function merge (defaultParams, mergeParams) {
|
|||
|
const mergedOptions = {}
|
|||
|
for (const option in defaultParams) {
|
|||
|
mergedOptions[option] = defaultParams[option]
|
|||
|
if (typeof mergeParams[option] !== 'undefined') {
|
|||
|
mergedOptions[option] = mergeParams[option]
|
|||
|
}
|
|||
|
}
|
|||
|
return mergedOptions
|
|||
|
}
|
|||
|
|
|||
|
function isJSON (json) {
|
|||
|
try {
|
|||
|
if (json instanceof Object && JSON.parse(JSON.stringify(json))) {
|
|||
|
return true
|
|||
|
}
|
|||
|
return false
|
|||
|
} catch (err) {
|
|||
|
return false
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
var _$src_8 = {};
|
|||
|
(function (window) {
|
|||
|
'use strict'
|
|||
|
|
|||
|
let options = {
|
|||
|
searchInput: null,
|
|||
|
resultsContainer: null,
|
|||
|
json: [],
|
|||
|
success: Function.prototype,
|
|||
|
searchResultTemplate: '<li><a href="{url}" title="{desc}">{title}</a></li>',
|
|||
|
templateMiddleware: Function.prototype,
|
|||
|
sortMiddleware: function () {
|
|||
|
return 0
|
|||
|
},
|
|||
|
noResultsText: 'No results found',
|
|||
|
limit: 10,
|
|||
|
fuzzy: false,
|
|||
|
debounceTime: null,
|
|||
|
exclude: []
|
|||
|
}
|
|||
|
|
|||
|
let debounceTimerHandle
|
|||
|
const debounce = function (func, delayMillis) {
|
|||
|
if (delayMillis) {
|
|||
|
clearTimeout(debounceTimerHandle)
|
|||
|
debounceTimerHandle = setTimeout(func, delayMillis)
|
|||
|
} else {
|
|||
|
func.call()
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
const requiredOptions = ['searchInput', 'resultsContainer', 'json']
|
|||
|
|
|||
|
/* removed: const _$Templater_7 = require('./Templater') */;
|
|||
|
/* removed: const _$Repository_4 = require('./Repository') */;
|
|||
|
/* removed: const _$JSONLoader_2 = require('./JSONLoader') */;
|
|||
|
const optionsValidator = _$OptionsValidator_3({
|
|||
|
required: requiredOptions
|
|||
|
})
|
|||
|
/* removed: const _$utils_9 = require('./utils') */;
|
|||
|
|
|||
|
window.SimpleJekyllSearch = function (_options) {
|
|||
|
const errors = optionsValidator.validate(_options)
|
|||
|
if (errors.length > 0) {
|
|||
|
throwError('You must specify the following required options: ' + requiredOptions)
|
|||
|
}
|
|||
|
|
|||
|
options = _$utils_9.merge(options, _options)
|
|||
|
|
|||
|
_$Templater_7.setOptions({
|
|||
|
template: options.searchResultTemplate,
|
|||
|
middleware: options.templateMiddleware
|
|||
|
})
|
|||
|
|
|||
|
_$Repository_4.setOptions({
|
|||
|
fuzzy: options.fuzzy,
|
|||
|
limit: options.limit,
|
|||
|
sort: options.sortMiddleware,
|
|||
|
exclude: options.exclude
|
|||
|
})
|
|||
|
|
|||
|
if (_$utils_9.isJSON(options.json)) {
|
|||
|
initWithJSON(options.json)
|
|||
|
} else {
|
|||
|
initWithURL(options.json)
|
|||
|
}
|
|||
|
|
|||
|
const rv = {
|
|||
|
search: search
|
|||
|
}
|
|||
|
|
|||
|
typeof options.success === 'function' && options.success.call(rv)
|
|||
|
return rv
|
|||
|
}
|
|||
|
|
|||
|
function initWithJSON (json) {
|
|||
|
_$Repository_4.put(json)
|
|||
|
registerInput()
|
|||
|
}
|
|||
|
|
|||
|
function initWithURL (url) {
|
|||
|
_$JSONLoader_2.load(url, function (err, json) {
|
|||
|
if (err) {
|
|||
|
throwError('failed to get JSON (' + url + ')')
|
|||
|
}
|
|||
|
initWithJSON(json)
|
|||
|
})
|
|||
|
}
|
|||
|
|
|||
|
function emptyResultsContainer () {
|
|||
|
options.resultsContainer.innerHTML = ''
|
|||
|
}
|
|||
|
|
|||
|
function appendToResultsContainer (text) {
|
|||
|
options.resultsContainer.innerHTML += text
|
|||
|
}
|
|||
|
|
|||
|
function registerInput () {
|
|||
|
options.searchInput.addEventListener('input', function (e) {
|
|||
|
if (isWhitelistedKey(e.which)) {
|
|||
|
emptyResultsContainer()
|
|||
|
debounce(function () { search(e.target.value) }, options.debounceTime)
|
|||
|
}
|
|||
|
})
|
|||
|
}
|
|||
|
|
|||
|
function search (query) {
|
|||
|
if (isValidQuery(query)) {
|
|||
|
emptyResultsContainer()
|
|||
|
render(_$Repository_4.search(query), query)
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function render (results, query) {
|
|||
|
const len = results.length
|
|||
|
if (len === 0) {
|
|||
|
return appendToResultsContainer(options.noResultsText)
|
|||
|
}
|
|||
|
for (let i = 0; i < len; i++) {
|
|||
|
results[i].query = query
|
|||
|
appendToResultsContainer(_$Templater_7.compile(results[i]))
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function isValidQuery (query) {
|
|||
|
return query && query.length > 0
|
|||
|
}
|
|||
|
|
|||
|
function isWhitelistedKey (key) {
|
|||
|
return [13, 16, 20, 37, 38, 39, 40, 91].indexOf(key) === -1
|
|||
|
}
|
|||
|
|
|||
|
function throwError (message) {
|
|||
|
throw new Error('SimpleJekyllSearch --- ' + message)
|
|||
|
}
|
|||
|
})(window)
|
|||
|
|
|||
|
}());
|
|||
|
</script>
|
|||
|
|
|||
|
<!-- Configuration -->
|
|||
|
<script>
|
|||
|
SimpleJekyllSearch({
|
|||
|
searchInput: document.getElementById('search-input'),
|
|||
|
resultsContainer: document.getElementById('results-container'),
|
|||
|
json: '/search.json',
|
|||
|
//searchResultTemplate: '<li><a href="https://static.rnmkcy.eu{url}">{date} {title}</a></li>'
|
|||
|
searchResultTemplate: '<li><a href="{url}">{date} {title}</a></li>'
|
|||
|
})
|
|||
|
</script>
|
|||
|
<!-- Fin déclaration champ de recherche --></div><nav class="navigation">
|
|||
|
<ul><li class="navigation__item"><a href="/archive.html">Etiquettes</a></li><li class="navigation__item"><a href="/htmldoc.html">Documents</a></li><li class="navigation__item"><a href="/liens_ttrss.html">Liens</a></li><li class="navigation__item"><a href="/aide-jekyll-text-theme.html">Aide</a></li></ul>
|
|||
|
</nav></div>
|
|||
|
</header>
|
|||
|
|
|||
|
</div><div class="page__content"><div class ="main"><div class="grid grid--reverse">
|
|||
|
<div class="col-main cell cell--auto"><!-- start custom main top snippet --><div id="results-container" class="search-result js-search-result"></div><!-- end custom main top snippet -->
|
|||
|
<article itemscope itemtype="http://schema.org/Article"><div class="article__header"><header><h1 style="color:Tomato;">Synchronisation fichiers avec Unison sous Linux</h1></header></div><meta itemprop="headline" content="Synchronisation fichiers avec Unison sous Linux"><div class="article__info clearfix"><ul class="left-col menu"><li>
|
|||
|
<a class="button button--secondary button--pill button--sm"
|
|||
|
href="/archive.html?tag=rsync">rsync</a>
|
|||
|
</li></ul><ul class="right-col menu"><li>
|
|||
|
<i class="far fa-calendar-alt"></i> <span title="Création" style="color:#FF00FF">20 sept. 2022</span></li></ul></div><meta itemprop="datePublished" content="2022-09-20T00:00:00+02:00">
|
|||
|
<meta itemprop="keywords" content="rsync"><div class="js-article-content">
|
|||
|
<div class="layout--article"><!-- start custom article top snippet -->
|
|||
|
<style>
|
|||
|
#myBtn {
|
|||
|
display: none;
|
|||
|
position: fixed;
|
|||
|
bottom: 10px;
|
|||
|
right: 10px;
|
|||
|
z-index: 99;
|
|||
|
font-size: 12px;
|
|||
|
font-weight: bold;
|
|||
|
border: none;
|
|||
|
outline: none;
|
|||
|
background-color: white;
|
|||
|
color: black;
|
|||
|
cursor: pointer;
|
|||
|
padding: 5px;
|
|||
|
border-radius: 4px;
|
|||
|
}
|
|||
|
|
|||
|
#myBtn:hover {
|
|||
|
background-color: #555;
|
|||
|
}
|
|||
|
</style>
|
|||
|
|
|||
|
<button onclick="topFunction()" id="myBtn" title="Haut de page">⇧</button>
|
|||
|
|
|||
|
<script>
|
|||
|
//Get the button
|
|||
|
var mybutton = document.getElementById("myBtn");
|
|||
|
|
|||
|
// When the user scrolls down 20px from the top of the document, show the button
|
|||
|
window.onscroll = function() {scrollFunction()};
|
|||
|
|
|||
|
function scrollFunction() {
|
|||
|
if (document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) {
|
|||
|
mybutton.style.display = "block";
|
|||
|
} else {
|
|||
|
mybutton.style.display = "none";
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// When the user clicks on the button, scroll to the top of the document
|
|||
|
function topFunction() {
|
|||
|
document.body.scrollTop = 0;
|
|||
|
document.documentElement.scrollTop = 0;
|
|||
|
}
|
|||
|
</script>
|
|||
|
|
|||
|
|
|||
|
<!-- end custom article top snippet -->
|
|||
|
<div class="article__content" itemprop="articleBody"><details>
|
|||
|
<summary><b>Afficher/cacher Sommaire</b></summary>
|
|||
|
<!-- affichage sommaire -->
|
|||
|
<div class="toc-aside js-toc-root"></div>
|
|||
|
</details><p><strong>Unison</strong> est un outil de synchronisation de fichiers gratuit, open source, multiplateforme et bidirectionnel . Il est utilisé pour stocker deux répliques de fichiers et de répertoires sur deux systèmes différents ou sur des disques différents sur le même système.</p>
|
|||
|
|
|||
|
<p>Les répliques de fichiers et de répertoires sont modifiées séparément puis mises à jour en propageant les modifications de chaque réplique à l’autre.</p>
|
|||
|
|
|||
|
<p>Pour le dire en termes simples, toute modification apportée à un emplacement sera répliquée à un autre emplacement et vice versa.</p>
|
|||
|
|
|||
|
<p>Comme il s’agit d’une application multiplateforme, vous pouvez synchroniser des fichiers et des répertoires entre des ordinateurs fonctionnant avec différents systèmes d’exploitation.</p>
|
|||
|
|
|||
|
<p>Par exemple, vous pouvez synchroniser des fichiers stockés sur une machine Linux avec une machine Windows ou Unix et vice versa. Non seulement le contenu du fichier, même la propriété du fichier et les autorisations seront également synchronisées.</p>
|
|||
|
|
|||
|
<p class="warning">Si le contenu est différent sur les deux répliques, cela provoque un conflit. Unison détectera et affichera de tels conflits. Dans ce cas, vous devez indiquer à Unison quelle réplique doit remplacer l’autre. Les mises à jour qui n’entrent pas en conflit seront propagées automatiquement.</p>
|
|||
|
|
|||
|
<p>Unison fonctionne bien avec n’importe quelle paire d’ordinateurs connectés à un réseau local ou à Internet. Les fichiers sont synchronisés avec un système distant via une connexion ssh cryptée .</p>
|
|||
|
|
|||
|
<p>Une autre caractéristique notable d’Unison est qu’il fonctionne bien sur des liaisons lentes telles que les connexions PPP. Semblable à rsync , Unison ne transférera que les parties d’un fichier qui ont été modifiées.</p>
|
|||
|
|
|||
|
<p>L’implémentation intégrée d’Unison de l’algorithme rsync accélère le transfert des mises à jour vers les fichiers existants.</p>
|
|||
|
|
|||
|
<p>Unison est un programme de niveau utilisateur, il n’est donc pas nécessaire de modifier le noyau ou d’avoir des privilèges de superutilisateur sur l’un ou l’autre hôte.</p>
|
|||
|
|
|||
|
<p><strong>Le programme Unison est créé par Benjamin C. Pierce et est publié sous licence GPL</strong>. Il fonctionne sous les plateformes GNU/Linux, Windows et Unix.</p>
|
|||
|
|
|||
|
<p>Traduction de l’article <a href="https://ostechnix.com/how-to-synchronize-files-with-unison-on-linux/">How To Synchronize Files With Unison On Linux</a></p>
|
|||
|
|
|||
|
<h2 id="sommaire">Sommaire</h2>
|
|||
|
|
|||
|
<ul>
|
|||
|
<li><a href="#installer-unison-sous-linux">Installer Unison sous Linux</a></li>
|
|||
|
<li><a href="#synchroniser-des-fichiers-avec-unison-sous-linux">Synchroniser des fichiers avec Unison sous Linux</a>
|
|||
|
<ul>
|
|||
|
<li><a href="#synchroniser-les-fichiers-stockés-dans-différents-répertoires-sur-la-même-machine">Synchroniser les fichiers stockés dans différents répertoires sur la même machine</a>
|
|||
|
<ul>
|
|||
|
<li><a href="#profils-unison">Profils unison</a></li>
|
|||
|
<li><a href="#ignorer-les-fichiers-de-la-synchronisation">Ignorer les fichiers de la synchronisation</a></li>
|
|||
|
<li><a href="#synchroniser-les-fichiers-et-répertoires-locaux-avec-unison-gtk">Synchroniser les fichiers et répertoires locaux avec unison-gtk</a></li>
|
|||
|
</ul>
|
|||
|
</li>
|
|||
|
<li><a href="#synchroniser-les-fichiers-et-répertoires-stockés-sur-différents-disques-sur-la-même-machine">Synchroniser les fichiers et répertoires stockés sur différents disques sur la même machine</a></li>
|
|||
|
<li><a href="#synchroniser-les-fichiers-et-répertoires-entre-les-machines-locales-et-distantes">Synchroniser les fichiers et répertoires entre les machines locales et distantes</a>
|
|||
|
<ul>
|
|||
|
<li><a href="#synchronisez-les-fichiers-et-les-répertoires-entre-les-machines-locales-et-distantes-à-partir-de-la-ligne-de-commande">Synchronisez les fichiers et les répertoires entre les machines locales et distantes à partir de la ligne de commande</a></li>
|
|||
|
<li><a href="#configurer-lauthentification-basée-sur-la-clé-ssh-facultatif">Configurer l’authentification basée sur la clé SSH (facultatif)</a></li>
|
|||
|
<li><a href="#configurer-la-tâche-cron-facultatif">Configurer la tâche cron (facultatif)</a></li>
|
|||
|
</ul>
|
|||
|
</li>
|
|||
|
</ul>
|
|||
|
</li>
|
|||
|
</ul>
|
|||
|
|
|||
|
<h2 id="installer-unison-sous-linux">Installer Unison sous Linux</h2>
|
|||
|
|
|||
|
<p>Unison est disponible dans les référentiels par défaut des référentiels officiels de nombreux systèmes d’exploitation Linux.</p>
|
|||
|
|
|||
|
<p>Pour installer Unison sur Alpine Linux, exécutez :</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ sudo apk add unison
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Installez Unison sur Arch Linux et ses variantes comme EndeavourOS et Manjaro Linux :</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ sudo pacman -S unison
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Debian, Ubuntu, Linux Mint :</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ sudo apt install unison
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Fedora:</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ sudo dnf install unison
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>openSUSE :</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ sudo zypper install unison
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Une interface graphique simple nommée <strong>unison-gtk</strong> est également disponible pour ceux qui préfèrent l’interface graphique à la CLI. Il est disponible dans les référentiels par défaut de quelques distributions Linux.</p>
|
|||
|
|
|||
|
<p>Sur Alpine Linux, vous pouvez installer Unison-gtk à l’aide de la commande :</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ sudo apk add unison-gui
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Sur Arch Linux et ses variantes, installez Unison-gtk à l’aide de la commande :</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ sudo pacman -S unison-gtk2
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Si vous êtes sur un système basé sur Debian, installez-le à l’aide de la commande suivante :</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ sudo apt install unison-gtk
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p class="info">Veuillez noter que si vous souhaitez synchroniser des fichiers entre deux systèmes différents, vous devez installer <strong>Unison</strong> sur tous les systèmes que vous souhaitez conserver synchronisés. De plus, <u>la même version sur les deux systèmes est recommandée</u>.</p>
|
|||
|
|
|||
|
<h2 id="synchroniser-des-fichiers-avec-unison-sous-linux">Synchroniser des fichiers avec Unison sous Linux</h2>
|
|||
|
|
|||
|
<p>Nous pouvons utiliser Unison dans les trois modes différents suivants :</p>
|
|||
|
|
|||
|
<ul>
|
|||
|
<li>Synchroniser les fichiers stockés dans différents répertoires sur la même machine,</li>
|
|||
|
<li>Synchroniser les fichiers et répertoires stockés sur différents disques sur la même machine,</li>
|
|||
|
<li>Synchronisez les fichiers et les répertoires entre les machines locales et distantes :
|
|||
|
<ul>
|
|||
|
<li>Synchroniser des fichiers avec une machine distante via une connexion socket directe,</li>
|
|||
|
<li>Synchronisez les fichiers avec la machine distante à l’aide de SSH.</li>
|
|||
|
</ul>
|
|||
|
</li>
|
|||
|
</ul>
|
|||
|
|
|||
|
<h3 id="synchroniser-les-fichiers-stockés-dans-différents-répertoires-sur-la-même-machine">Synchroniser les fichiers stockés dans différents répertoires sur la même machine</h3>
|
|||
|
|
|||
|
<p>Pour les besoins de ce guide, je vais créer deux répertoires racine (c’est-à-dire des répliques) à savoir directory1 et directory2.</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ mkdir ~/répertoire1
|
|||
|
$ mkdir ~/répertoire2
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Placez du contenu dans l’un des répertoires ci-dessus. J’ai stocké des fichiers et des répertoires à l’intérieur directory1.</p>
|
|||
|
|
|||
|
<p>Voyons le contenu de directory1:</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ls -ltRF répertoire1/
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Exemple de sortie :</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>directory1/:
|
|||
|
total 7780
|
|||
|
drwxrwxr-x 2 sk sk 4096 Oct 2 15:35 dir1/
|
|||
|
-rw-rw-r-- 1 sk sk 7779007 Sep 25 16:51 file2
|
|||
|
-rw-rw-r-- 1 sk sk 179765 Aug 17 12:51 file1
|
|||
|
|
|||
|
directory1/dir1:
|
|||
|
total 140
|
|||
|
-rw-rw-r-- 1 sk sk 141729 Aug 6 22:34 file3
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Comme vous pouvez le voir, j’ai un dossier appelé <strong>dir1</strong> et deux autres fichiers à savoir <strong>file1</strong> et <strong>file2</strong> à la racine de <strong>directory1</strong>.<br />
|
|||
|
A l’intérieur du <strong>dir1</strong>, il y a un autre fichier appelé <strong>file3</strong>.</p>
|
|||
|
|
|||
|
<p>Et la réplique <strong>directory2</strong> n’a aucun contenu.</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ls -ltRF directory2/
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>directory2/:
|
|||
|
total 0
|
|||
|
</code></pre></div></div>
|
|||
|
<p>Synchronisons ces deux répertoires avec la commande <code class="language-plaintext highlighter-rouge">unison</code> :</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ unison directory1/ directory2/
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Vous serez invité à indiquer si vous souhaitez continuer à synchroniser le contenu de <strong>directory1</strong> avec <strong>directory2</strong>. Appuyez simplement sur la touche <strong>ENTER</strong>. <br />
|
|||
|
Ensuite, Unison vous demandera de confirmer les fichiers et répertoires à synchroniser.<br />
|
|||
|
Appuyez sur <strong>ENTER</strong> ou appuyez sur la touche <strong>f</strong> pour confirmer les fichiers et dossiers que vous souhaitez synchroniser.<br />
|
|||
|
Enfin, appuyez sur <strong>y</strong> et appuyez sur la touche <strong>ENTER</strong> pour procéder à la propagation des mises à jour.</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Contacting server...
|
|||
|
Looking for changes
|
|||
|
Warning: No archive files were found for these roots, whose canonical names are:
|
|||
|
/home/sk/directory1
|
|||
|
/home/sk/directory2
|
|||
|
This can happen either
|
|||
|
because this is the first time you have synchronized these roots,
|
|||
|
or because you have upgraded Unison to a new version with a different
|
|||
|
archive format.
|
|||
|
|
|||
|
Update detection may take a while on this run if the replicas are
|
|||
|
large.
|
|||
|
|
|||
|
Unison will assume that the 'last synchronized state' of both replicas
|
|||
|
was completely empty. This means that any files that are different
|
|||
|
will be reported as conflicts, and any files that exist only on one
|
|||
|
replica will be judged as new and propagated to the other replica.
|
|||
|
If the two replicas are identical, then no changes will be reported.
|
|||
|
|
|||
|
If you see this message repeatedly, it may be because one of your machines
|
|||
|
is getting its address from DHCP, which is causing its host name to change
|
|||
|
between synchronizations. See the documentation for the UNISONLOCALHOSTNAME
|
|||
|
environment variable for advice on how to correct this.
|
|||
|
|
|||
|
Donations to the Unison project are gratefully accepted:
|
|||
|
http://www.cis.upenn.edu/~bcpierce/unison
|
|||
|
|
|||
|
Reconciling changestinue.[<spc>] / file1
|
|||
|
|
|||
|
directory1 directory2
|
|||
|
dir ----> dir1 [f] f
|
|||
|
file ----> file1 [f] f
|
|||
|
file ----> file2 [f] f
|
|||
|
|
|||
|
Proceed with propagating updates? [] y
|
|||
|
Propagating updates
|
|||
|
|
|||
|
|
|||
|
UNISON 2.48.4 started propagating changes at 15:43:29.75 on 02 Oct 2020
|
|||
|
[BGN] Copying dir1 from /home/sk/directory1 to /home/sk/directory2
|
|||
|
[END] Copying dir1
|
|||
|
[BGN] Copying file1 from /home/sk/directory1 to /home/sk/directory2
|
|||
|
[END] Copying file1
|
|||
|
[BGN] Copying file2 from /home/sk/directory1 to /home/sk/directory2
|
|||
|
[END] Copying file2
|
|||
|
UNISON 2.48.4 finished propagating changes at 15:43:29.81 on 02 Oct 2020
|
|||
|
|
|||
|
|
|||
|
Saving synchronizer state
|
|||
|
Synchronization complete at 15:43:29 (3 items transferred, 0 skipped, 0 failed)
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p><img src="/images/unison001.png" alt="" /></p>
|
|||
|
|
|||
|
<p>Désormais, tout le contenu de <strong>directory1</strong> sera répliqué dans <strong>directory2</strong>. Vous pouvez le vérifier en utilisant la commande <code class="language-plaintext highlighter-rouge">ls</code> :<br />
|
|||
|
<img src="/images/unison002.png" alt="" /></p>
|
|||
|
|
|||
|
<p>Voir? Le contenu des deux répliques est identique. Comme je l’ai déjà mentionné, les autorisations de fichiers et les numéros d’inode des fichiers et des dossiers sur les deux emplacements sont également synchronisés. Vous pouvez voir que les autorisations et les inodes sont également identiques dans la sortie ci-dessus.</p>
|
|||
|
|
|||
|
<p>Avez-vous remarqué les flèches dans la sortie lors de la synchronisation des répertoires ? Par exemple, vous devriez avoir remarqué les lignes suivantes au milieu de la sortie :</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>directory1 directory2
|
|||
|
dir ----> dir1 [f] f
|
|||
|
file ----> file1 [f] f
|
|||
|
file ----> file2 [f] f
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Les flèches indiquent la direction dans laquelle la modification doit être propagée. Dans notre cas, les flèches pointent de gauche à droite. Signification - les dir1, file1 et file2 sont propagés à la deuxième réplique, c’est-à-dire directory2.</p>
|
|||
|
|
|||
|
<p>Créez maintenant un nouveau fichier dans la réplique directory2 et voyez ce qui se passe.</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ touch directory2/file4
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Synchronisez les deux instances dupliquées à l’aide de la commande :</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ unison directory1/ directory2/
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Vous verrez maintenant qu’une flèche pointe de droite à gauche. Cela signifie que les modifications apportées au deuxième réplica sont propagées au premier réplica. En d’autres termes, le contenu de directory2 est répliqué dans directory1.</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Contacting server...
|
|||
|
Looking for changes
|
|||
|
Reconciling changes
|
|||
|
|
|||
|
directory1 directory2
|
|||
|
<---- new file file4 [f] f
|
|||
|
|
|||
|
Proceed with propagating updates? [] y
|
|||
|
Propagating updates
|
|||
|
|
|||
|
|
|||
|
UNISON 2.48.4 started propagating changes at 16:04:08.78 on 02 Oct 2020
|
|||
|
[BGN] Copying file4 from /home/sk/directory2 to /home/sk/directory1
|
|||
|
[END] Copying file4
|
|||
|
UNISON 2.48.4 finished propagating changes at 16:04:08.78 on 02 Oct 2020
|
|||
|
|
|||
|
|
|||
|
Saving synchronizer state
|
|||
|
Synchronization complete at 16:04:08 (1 item transferred, 0 skipped, 0 failed)
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Unison reconnaîtra automatiquement quels fichiers doivent être mis à jour dans les deux répliques. Lorsque vous exécutez Unison pour la première fois, il prend note de l’horodatage de modification, des autorisations, de la propriété et du numéro d’inode de tous les fichiers et dossiers dans les deux emplacements. Sur la base de ces informations, Unison décidera quels fichiers doivent être mis à jour lorsque vous exécuterez à nouveau la même commande. Comme indiqué précédemment, toutes les informations seront conservées dans un répertoire <strong>~/.unison</strong></p>
|
|||
|
|
|||
|
<p>Pour le vérifier vous-même, modifiez le contenu d’un fichier existant. Je vais modifier le contenu de file4 dans le replica directory1</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ echo "Welcome to OSTechNix" > directory1/file4
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Relancez maintenant la synchronisation :</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ unison directory1/ directory2/
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Cette fois, Unison synchronisera le(s) fichier(s) modifié(s).</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Contacting server...
|
|||
|
Looking for changes
|
|||
|
Reconciling changes
|
|||
|
|
|||
|
directory1 directory2
|
|||
|
changed ----> file4 [f] f
|
|||
|
|
|||
|
Proceed with propagating updates? [] y
|
|||
|
Propagating updates
|
|||
|
|
|||
|
|
|||
|
UNISON 2.48.4 started propagating changes at 16:05:27.13 on 02 Oct 2020
|
|||
|
[BGN] Updating file file4 from /home/sk/directory1 to /home/sk/directory2
|
|||
|
[END] Updating file file4
|
|||
|
UNISON 2.48.4 finished propagating changes at 16:05:27.14 on 02 Oct 2020
|
|||
|
|
|||
|
|
|||
|
Saving synchronizer state
|
|||
|
Synchronization complete at 16:05:27 (1 item transferred, 0 skipped, 0 failed)
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Comme vous pouvez le voir dans la sortie ci-dessus, Unison a détecté qui file4 a été modifié dans le premier réplica et l’a synchronisé avec le deuxième réplica.</p>
|
|||
|
|
|||
|
<p>Si les deux répliques sont modifiées et que leur contenu est différent, les modifications sont en conflit. Dans de tels cas, Unison demandera à l’utilisateur comment propager les mises à jour des deux côtés. Les conflits seront affichés avec des flèches gauche et droite comme ci-dessous.</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>directory1 directory2
|
|||
|
new file <-?-> new file file6 []
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Si une action par défaut est indiquée par une flèche, appuyez simplement sur la touche ENTER pour passer au fichier modifié suivant. Si vous voulez faire quelque chose de différent avec ce fichier, vous pouvez appuyer sur “<code class="language-plaintext highlighter-rouge"><</code>” ou “<code class="language-plaintext highlighter-rouge">></code>” pour forcer la propagation du changement de droite à gauche ou de gauche à droite.</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>directory1 directory2
|
|||
|
new file <==== new file file6 [] <
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Si vous ne souhaitez apporter aucune modification, appuyez simplement sur “<code class="language-plaintext highlighter-rouge">/</code>” pour ignorer ce fichier et laisser les deux répliques seules.</p>
|
|||
|
|
|||
|
<h4 id="profils-unison">Profils unison</h4>
|
|||
|
|
|||
|
<p>Unison utilise des profils pour synchroniser des fichiers et des dossiers entre deux répertoires sur la même machine ou sur deux machines différentes. Un profil n’est rien d’autre qu’un fichier texte dans lequel nous définissons le chemin réel des répertoires racine et les préférences de chemin qui doivent être synchronisées. Tous les profils seront stockés dans le répertoire de <code class="language-plaintext highlighter-rouge">.unison</code> votre répertoire <code class="language-plaintext highlighter-rouge">$HOME</code>.</p>
|
|||
|
|
|||
|
<p>Lorsque vous exécutez Unison pour la première fois, un profil par défaut nommé <code class="language-plaintext highlighter-rouge">default.prf</code> est créé sous le répertoire <code class="language-plaintext highlighter-rouge">~/.unison</code>. Le profil par défaut n’a pas de préférences définies. Ainsi, tout ce qui est stocké dans une réplique sera synchronisé avec une autre réplique.</p>
|
|||
|
|
|||
|
<p>Nous pouvons également créer des profils personnalisés avec la liste des préférences de chemin. Cela vous évitera de synchroniser l’ensemble des deux répliques.</p>
|
|||
|
|
|||
|
<p>En utilisant les profils personnalisés, nous pouvons demander à Unison :</p>
|
|||
|
|
|||
|
<ul>
|
|||
|
<li>pour synchroniser des fichiers et dossiers spécifiques,</li>
|
|||
|
<li>pour ignorer un fichier spécifique ou un type spécifique de fichiers de la synchronisation.</li>
|
|||
|
</ul>
|
|||
|
|
|||
|
<p>Voyons un exemple.</p>
|
|||
|
|
|||
|
<p>Créez deux répliques et enregistrez certains fichiers et dossiers dans une réplique et laissez l’autre vide.</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ mkdir ~/directory1
|
|||
|
$ mkdir ~/directory2
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Ensuite, créez un nouveau profil nommé <strong>dir_profile.prf</strong>:</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ nano .unison/dir_profile.prf
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Définissez les répertoires racine et les chemins des fichiers et répertoires que vous souhaitez synchroniser :</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Roots of the synchronization
|
|||
|
root = /home/sk/directory1
|
|||
|
root = /home/sk/directory2
|
|||
|
|
|||
|
# Paths to synchronize
|
|||
|
path = dir1
|
|||
|
path = file2
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Les deux racines peuvent être données dans n’importe quel ordre. Unison les triera dans un ordre canonique avant de faire quoi que ce soit d’autre. Si vous ne spécifiez aucun chemin, Unison synchronisera simplement les deux répliques entières, en commençant par la paire de racines donnée. Si une ou plusieurs préférences de chemin sont données, Unison synchronisera uniquement ces chemins et leurs enfants.</p>
|
|||
|
|
|||
|
<p>Dans l’exemple ci-dessus, j’ai défini que dir1 et file2 doivent être synchronisés avec un autre répertoire racine. Enregistrez et fermez le fichier.</p>
|
|||
|
|
|||
|
<p>Après avoir créé le profil, démarrez le processus de synchronisation à l’aide de la commande :</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ unison dir_profile.prf
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Vous serez invité à indiquer si vous souhaitez continuer à synchroniser le contenu de directory1 avec directory2. Appuyez simplement sur la touche ENTER puis appuyez sur la touche f pour confirmer les chemins à synchroniser. Enfin, appuyez sur y et appuyez sur la touche ENTER pour procéder à la propagation des mises à jour.</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Contacting server...
|
|||
|
Looking for changes
|
|||
|
Warning: No archive files were found for these roots, whose canonical names are:
|
|||
|
/home/sk/directory1
|
|||
|
/home/sk/directory2
|
|||
|
This can happen either
|
|||
|
because this is the first time you have synchronized these roots,
|
|||
|
or because you have upgraded Unison to a new version with a different
|
|||
|
archive format.
|
|||
|
|
|||
|
Update detection may take a while on this run if the replicas are
|
|||
|
large.
|
|||
|
|
|||
|
Unison will assume that the 'last synchronized state' of both replicas
|
|||
|
was completely empty. This means that any files that are different
|
|||
|
will be reported as conflicts, and any files that exist only on one
|
|||
|
replica will be judged as new and propagated to the other replica.
|
|||
|
If the two replicas are identical, then no changes will be reported.
|
|||
|
|
|||
|
If you see this message repeatedly, it may be because one of your machines
|
|||
|
is getting its address from DHCP, which is causing its host name to change
|
|||
|
between synchronizations. See the documentation for the UNISONLOCALHOSTNAME
|
|||
|
environment variable for advice on how to correct this.
|
|||
|
|
|||
|
Donations to the Unison project are gratefully accepted:
|
|||
|
http://www.cis.upenn.edu/~bcpierce/unison
|
|||
|
|
|||
|
Reconciling changestinue.[<spc>] | dir1
|
|||
|
|
|||
|
directory1 directory2
|
|||
|
dir ----> dir1 [f]
|
|||
|
file ----> file2 [f]
|
|||
|
|
|||
|
Proceed with propagating updates? [] y
|
|||
|
Propagating updates
|
|||
|
|
|||
|
|
|||
|
UNISON 2.48.4 started propagating changes at 15:42:32.99 on 01 Oct 2020
|
|||
|
[BGN] Copying dir1 from /home/sk/directory1 to /home/sk/directory2
|
|||
|
[END] Copying dir1
|
|||
|
[BGN] Copying file2 from /home/sk/directory1 to /home/sk/directory2
|
|||
|
[END] Copying file2
|
|||
|
UNISON 2.48.4 finished propagating changes at 15:42:33.04 on 01 Oct 2020
|
|||
|
|
|||
|
|
|||
|
Saving synchronizer state
|
|||
|
Synchronization complete at 15:42:33 (2 items transferred, 0 skipped, 0 failed)
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p><img src="/images/unison003.png" alt="" /></p>
|
|||
|
|
|||
|
<p>Allez maintenant au deuxième réplica directory2 et voyez si le contenu est synchronisé.</p>
|
|||
|
|
|||
|
<h4 id="ignorer-les-fichiers-de-la-synchronisation">Ignorer les fichiers de la synchronisation</h4>
|
|||
|
|
|||
|
<p>Parfois, vous ne souhaitez peut-être pas synchroniser un fichier spécifique ou un type de fichiers spécifique. Si tel est le cas, vous pouvez définir les fichiers à ignorer dans le profil comme ci-dessous.</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ignore=Name text.*
|
|||
|
ignore=Name .*~
|
|||
|
ignore=Name *.pdf
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Pour plus d’exemples de profils, reportez-vous <a href="https://www.cis.upenn.edu/~bcpierce/unison/download/releases/stable/unison-manual.html#profileegs">ici</a></p>
|
|||
|
|
|||
|
<h4 id="synchroniser-les-fichiers-et-répertoires-locaux-avec-unison-gtk">Synchroniser les fichiers et répertoires locaux avec unison-gtk</h4>
|
|||
|
|
|||
|
<p>Certains d’entre vous ne sont peut-être pas à l’aise avec la ligne de commande. Heureusement, Unison possède une interface graphique simple qui permet aux utilisateurs de créer facilement des profils et de synchroniser des fichiers basés sur les profils via une fenêtre graphique.</p>
|
|||
|
|
|||
|
<p>Assurez-vous d’avoir installé le package <strong>unison-gtk</strong> comme indiqué dans la section d’installation ci-dessus.</p>
|
|||
|
|
|||
|
<h3 id="synchroniser-les-fichiers-et-répertoires-stockés-sur-différents-disques-sur-la-même-machine">Synchroniser les fichiers et répertoires stockés sur différents disques sur la même machine</h3>
|
|||
|
|
|||
|
<p>Les fichiers de synchronisation entre deux disques sont exactement les mêmes que ci-dessus. Assurez-vous simplement que vous avez mentionné le chemin correct du répertoire racine dans le lecteur externe.</p>
|
|||
|
|
|||
|
<p>Vous savez maintenant comment synchroniser des fichiers entre deux répertoires sur le même disque dur et un disque différent sur un système local. La section suivante explique comment synchroniser des fichiers entre deux systèmes différents.</p>
|
|||
|
|
|||
|
<h3 id="synchroniser-les-fichiers-et-répertoires-entre-les-machines-locales-et-distantes">Synchroniser les fichiers et répertoires entre les machines locales et distantes</h3>
|
|||
|
|
|||
|
<p>Comme indiqué précédemment, Unison peut synchroniser des fichiers entre des systèmes locaux et distants via une connexion socket directe et via une connexion SSH cryptée. Comme vous le savez déjà, SSH est bien meilleur et plus sécurisé que la méthode socket. Pour les besoins de ce guide, j’utiliserai la méthode SSH pour synchroniser les fichiers.</p>
|
|||
|
|
|||
|
<p>Assurez-vous d’avoir <strong>installé openSSH et Unison sur les deux systèmes</strong> . Assurez-vous également que les deux systèmes peuvent communiquer via SSH. Veuillez noter que <strong>vos systèmes locaux et distants doivent avoir la même version d’Unison</strong></p>
|
|||
|
|
|||
|
<p>Pour vérifier la version d’Unison, exécutez la commande suivante sur votre système local et distant :</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ unisson -version
|
|||
|
unisson version 2.48.4
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Étant donné que les fichiers seront synchronisés via SSH, peu importe que le système distant ait unison-gtk ou non. L’interface de ligne de commande Unison est juste suffisante sur une machine distante ! Sur votre ordinateur local, vous pouvez utiliser une interface textuelle ou graphique pour unison</p>
|
|||
|
|
|||
|
<h4 id="synchronisez-les-fichiers-et-les-répertoires-entre-les-machines-locales-et-distantes-à-partir-de-la-ligne-de-commande">Synchronisez les fichiers et les répertoires entre les machines locales et distantes à partir de la ligne de commande</h4>
|
|||
|
|
|||
|
<p>Maintenant, synchronisons le directory1 depuis notre système local vers le système directory1 distant à l’aide de l’unisson en exécutant la commande suivante :</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ unison directory1/ ssh://ostechnix@192.168.225.52/directory1/
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p><code class="language-plaintext highlighter-rouge">ostechnix@192.168.225.52</code> avec le nom d’utilisateur et l’adresse IP de mon système distant.</p>
|
|||
|
|
|||
|
<p>Vous serez invité à entrer le mot de passe de votre utilisateur distant. Appuyez ensuite sur ENTER pour accepter de synchroniser le contenu de directory1 de la machine locale avec directory1 de la machine distante. Appuyez ensuite sur la touche f pour confirmer les chemins à synchroniser. Enfin, appuyez sur y et appuyez sur la touche ENTER pour procéder à la propagation des mises à jour.</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Contacting server...
|
|||
|
ostechnix@192.168.225.52's password:
|
|||
|
Connected [//ostechnix//home/sk/directory1 -> //ubuntuserver//home/ostechnix/directory1]
|
|||
|
Looking for changes
|
|||
|
Warning: No archive files were found for these roots, whose canonical names are:
|
|||
|
/home/sk/directory1
|
|||
|
//ubuntuserver//home/ostechnix/directory1
|
|||
|
This can happen either
|
|||
|
because this is the first time you have synchronized these roots,
|
|||
|
or because you have upgraded Unison to a new version with a different
|
|||
|
archive format.
|
|||
|
|
|||
|
Update detection may take a while on this run if the replicas are
|
|||
|
large.
|
|||
|
|
|||
|
Unison will assume that the 'last synchronized state' of both replicas
|
|||
|
was completely empty. This means that any files that are different
|
|||
|
will be reported as conflicts, and any files that exist only on one
|
|||
|
replica will be judged as new and propagated to the other replica.
|
|||
|
If the two replicas are identical, then no changes will be reported.
|
|||
|
|
|||
|
If you see this message repeatedly, it may be because one of your machines
|
|||
|
is getting its address from DHCP, which is causing its host name to change
|
|||
|
between synchronizations. See the documentation for the UNISONLOCALHOSTNAME
|
|||
|
environment variable for advice on how to correct this.
|
|||
|
|
|||
|
Donations to the Unison project are gratefully accepted:
|
|||
|
http://www.cis.upenn.edu/~bcpierce/unison
|
|||
|
|
|||
|
Press return to continue.[<spc>] Waiting for changes from server
|
|||
|
Reconciling changes
|
|||
|
|
|||
|
local ubuntuserver
|
|||
|
file ----> file1 [f] f
|
|||
|
file ----> file2 [f] f
|
|||
|
|
|||
|
Proceed with propagating updates? [] y
|
|||
|
Propagating updates
|
|||
|
|
|||
|
|
|||
|
UNISON 2.48.4 started propagating changes at 14:49:18.44 on 03 Oct 2020
|
|||
|
[BGN] Copying file1 from /home/sk/directory1 to //ubuntuserver//home/ostechnix/directory1
|
|||
|
[BGN] Copying file2 from /home/sk/directory1 to //ubuntuserver//home/ostechnix/directory1
|
|||
|
[END] Copying file1
|
|||
|
[END] Copying file2
|
|||
|
UNISON 2.48.4 finished propagating changes at 14:49:18.45 on 03 Oct 2020
|
|||
|
|
|||
|
|
|||
|
Saving synchronizer state
|
|||
|
Synchronization complete at 14:49:18 (2 items transferred, 0 skipped, 0 failed)
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p><img src="/images/unison004.png" alt="" /></p>
|
|||
|
|
|||
|
<p>La commande ci-dessus répliquera le contenu de directory1 du système local vers le directory1 sur le système distant. Ajoutez maintenant un nouveau fichier ou mettez à jour un fichier existant et essayez de synchroniser à nouveau. Vous devriez voir des résultats similaires des deux côtés.</p>
|
|||
|
|
|||
|
<p>Si vous souhaitez synchroniser le contenu ailleurs que dans le répertoire personnel du système distant, vous devez spécifier le chemin absolu du répertoire distant en <strong>ajoutant une barre oblique supplémentaire entre le nom d’hôte distant et le début du chemin</strong> comme ci-dessous :</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ unison directory1/ ssh://ostechnix@192.168.225.52//sync/the/files/here/
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<h4 id="configurer-lauthentification-basée-sur-la-clé-ssh-facultatif">Configurer l’authentification basée sur la clé SSH (facultatif)</h4>
|
|||
|
|
|||
|
<p>Chaque fois que vous synchronisez des fichiers entre deux systèmes différents via SSH, vous devez entrer le mot de passe de l’utilisateur distant. Si vous ne souhaitez pas saisir le mot de passe à chaque fois, vous pouvez configurer l’authentification SSH sans mot de passe comme décrit dans le lien ci-dessous. Ne générez pas de paire de clés SSH avec la phrase secrète. Sinon, vous devez toujours entrer le mot de passe lors de la synchronisation des fichiers entre les systèmes.<br />
|
|||
|
<a href="https://ostechnix.com/configure-ssh-key-based-authentication-linux/">How To Configure SSH Key-based Authentication In Linux</a></p>
|
|||
|
|
|||
|
<h4 id="configurer-la-tâche-cron-facultatif">Configurer la tâche cron (facultatif)</h4>
|
|||
|
|
|||
|
<p>Nous pouvons synchroniser automatiquement les fichiers à un intervalle de temps spécifique à l’aide de tâches cron.</p>
|
|||
|
|
|||
|
<p>Par exemple, ajoutez l’entrée suivante dans votre crontabfichier pour exécuter la synchronisation tous les jours (elle s’exécutera à 00h00) :</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>0 0 * * * unison directory1/ ssh://ostechnix@192.168.225.52//home/ostechnix/directory1/
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Pour plus de détails sur la configuration des tâches cron, consultez le guide suivant<br />
|
|||
|
<a href="https://ostechnix.com/a-beginners-guide-to-cron-jobs/">A Beginners Guide To Cron Jobs</a></p>
|
|||
|
|
|||
|
</div>
|
|||
|
|
|||
|
|
|||
|
|
|||
|
<div class="d-print-none"><footer class="article__footer"><meta itemprop="dateModified" content="2022-09-20T00:00:00+02:00"><!-- start custom article footer snippet -->
|
|||
|
|
|||
|
<!-- end custom article footer snippet -->
|
|||
|
<!--
|
|||
|
<div align="right"><a type="application/rss+xml" href="/feed.xml" title="S'abonner"><i class="fa fa-rss fa-2x"></i></a>
|
|||
|
|
|||
|
 </div>
|
|||
|
-->
|
|||
|
</footer>
|
|||
|
<div class="article__section-navigator clearfix"><div class="previous"><span>PRÉCÉDENT</span><a href="/2022/09/20/Sauvegarde_Restauration_systeme_Linux_avec_rsync_ou_cya.html">Sauvegarde restauration système complet Linux avec Rsync ou snapshots (CYA)</a></div><div class="next"><span>SUIVANT</span><a href="/2022/09/27/Debian_11_Fail2ban_UFW.html">Installer et configurer Fail2ban + UFW sur Debian 11/12</a></div></div></div>
|
|||
|
|
|||
|
</div>
|
|||
|
|
|||
|
<script>(function() {
|
|||
|
var SOURCES = window.TEXT_VARIABLES.sources;
|
|||
|
window.Lazyload.js(SOURCES.jquery, function() {
|
|||
|
$(function() {
|
|||
|
var $this ,$scroll;
|
|||
|
var $articleContent = $('.js-article-content');
|
|||
|
var hasSidebar = $('.js-page-root').hasClass('layout--page--sidebar');
|
|||
|
var scroll = hasSidebar ? '.js-page-main' : 'html, body';
|
|||
|
$scroll = $(scroll);
|
|||
|
|
|||
|
$articleContent.find('.highlight').each(function() {
|
|||
|
$this = $(this);
|
|||
|
$this.attr('data-lang', $this.find('code').attr('data-lang'));
|
|||
|
});
|
|||
|
$articleContent.find('h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]').each(function() {
|
|||
|
$this = $(this);
|
|||
|
$this.append($('<a class="anchor d-print-none" aria-hidden="true"></a>').html('<i class="fas fa-anchor"></i>'));
|
|||
|
});
|
|||
|
$articleContent.on('click', '.anchor', function() {
|
|||
|
$scroll.scrollToAnchor('#' + $(this).parent().attr('id'), 400);
|
|||
|
});
|
|||
|
});
|
|||
|
});
|
|||
|
})();
|
|||
|
</script>
|
|||
|
|
|||
|
</div><section class="page__comments d-print-none"></section></article><!-- start custom main bottom snippet -->
|
|||
|
<!-- end custom main bottom snippet -->
|
|||
|
</div>
|
|||
|
</div></div></div></div>
|
|||
|
</div><script>(function() {
|
|||
|
var SOURCES = window.TEXT_VARIABLES.sources;
|
|||
|
window.Lazyload.js(SOURCES.jquery, function() {
|
|||
|
var $body = $('body'), $window = $(window);
|
|||
|
var $pageRoot = $('.js-page-root'), $pageMain = $('.js-page-main');
|
|||
|
var activeCount = 0;
|
|||
|
function modal(options) {
|
|||
|
var $root = this, visible, onChange, hideWhenWindowScroll = false;
|
|||
|
var scrollTop;
|
|||
|
function setOptions(options) {
|
|||
|
var _options = options || {};
|
|||
|
visible = _options.initialVisible === undefined ? false : show;
|
|||
|
onChange = _options.onChange;
|
|||
|
hideWhenWindowScroll = _options.hideWhenWindowScroll;
|
|||
|
}
|
|||
|
function init() {
|
|||
|
setState(visible);
|
|||
|
}
|
|||
|
function setState(isShow) {
|
|||
|
if (isShow === visible) {
|
|||
|
return;
|
|||
|
}
|
|||
|
visible = isShow;
|
|||
|
if (visible) {
|
|||
|
activeCount++;
|
|||
|
scrollTop = $(window).scrollTop() || $pageMain.scrollTop();
|
|||
|
$root.addClass('modal--show');
|
|||
|
$pageMain.scrollTop(scrollTop);
|
|||
|
activeCount === 1 && ($pageRoot.addClass('show-modal'), $body.addClass('of-hidden'));
|
|||
|
hideWhenWindowScroll && window.hasEvent('touchstart') && $window.on('scroll', hide);
|
|||
|
$window.on('keyup', handleKeyup);
|
|||
|
} else {
|
|||
|
activeCount > 0 && activeCount--;
|
|||
|
$root.removeClass('modal--show');
|
|||
|
$window.scrollTop(scrollTop);
|
|||
|
activeCount === 0 && ($pageRoot.removeClass('show-modal'), $body.removeClass('of-hidden'));
|
|||
|
hideWhenWindowScroll && window.hasEvent('touchstart') && $window.off('scroll', hide);
|
|||
|
$window.off('keyup', handleKeyup);
|
|||
|
}
|
|||
|
onChange && onChange(visible);
|
|||
|
}
|
|||
|
function show() {
|
|||
|
setState(true);
|
|||
|
}
|
|||
|
function hide() {
|
|||
|
setState(false);
|
|||
|
}
|
|||
|
function handleKeyup(e) {
|
|||
|
// Char Code: 27 ESC
|
|||
|
if (e.which === 27) {
|
|||
|
hide();
|
|||
|
}
|
|||
|
}
|
|||
|
setOptions(options);
|
|||
|
init();
|
|||
|
return {
|
|||
|
show: show,
|
|||
|
hide: hide,
|
|||
|
$el: $root
|
|||
|
};
|
|||
|
}
|
|||
|
$.fn.modal = modal;
|
|||
|
});
|
|||
|
})();
|
|||
|
</script><div class="modal modal--overflow page__search-modal d-print-none js-page-search-modal"><script>
|
|||
|
(function () {
|
|||
|
var SOURCES = window.TEXT_VARIABLES.sources;
|
|||
|
window.Lazyload.js(SOURCES.jquery, function() {
|
|||
|
// search panel
|
|||
|
var search = (window.search || (window.search = {}));
|
|||
|
var useDefaultSearchBox = window.useDefaultSearchBox === undefined ?
|
|||
|
true : window.useDefaultSearchBox ;
|
|||
|
|
|||
|
var $searchModal = $('.js-page-search-modal');
|
|||
|
var $searchToggle = $('.js-search-toggle');
|
|||
|
var searchModal = $searchModal.modal({ onChange: handleModalChange, hideWhenWindowScroll: true });
|
|||
|
var modalVisible = false;
|
|||
|
search.searchModal = searchModal;
|
|||
|
|
|||
|
var $searchBox = null;
|
|||
|
var $searchInput = null;
|
|||
|
var $searchClear = null;
|
|||
|
|
|||
|
function getModalVisible() {
|
|||
|
return modalVisible;
|
|||
|
}
|
|||
|
search.getModalVisible = getModalVisible;
|
|||
|
|
|||
|
function handleModalChange(visible) {
|
|||
|
modalVisible = visible;
|
|||
|
if (visible) {
|
|||
|
search.onShow && search.onShow();
|
|||
|
useDefaultSearchBox && $searchInput[0] && $searchInput[0].focus();
|
|||
|
} else {
|
|||
|
search.onShow && search.onHide();
|
|||
|
useDefaultSearchBox && $searchInput[0] && $searchInput[0].blur();
|
|||
|
setTimeout(function() {
|
|||
|
useDefaultSearchBox && ($searchInput.val(''), $searchBox.removeClass('not-empty'));
|
|||
|
search.clear && search.clear();
|
|||
|
window.pageAsideAffix && window.pageAsideAffix.refresh();
|
|||
|
}, 400);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
$searchToggle.on('click', function() {
|
|||
|
modalVisible ? searchModal.hide() : searchModal.show();
|
|||
|
});
|
|||
|
// Char Code: 83 S, 191 /
|
|||
|
$(window).on('keyup', function(e) {
|
|||
|
if (!modalVisible && !window.isFormElement(e.target || e.srcElement) && (e.which === 83 || e.which === 191)) {
|
|||
|
modalVisible || searchModal.show();
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
if (useDefaultSearchBox) {
|
|||
|
$searchBox = $('.js-search-box');
|
|||
|
$searchInput = $searchBox.children('input');
|
|||
|
$searchClear = $searchBox.children('.js-icon-clear');
|
|||
|
search.getSearchInput = function() {
|
|||
|
return $searchInput.get(0);
|
|||
|
};
|
|||
|
search.getVal = function() {
|
|||
|
return $searchInput.val();
|
|||
|
};
|
|||
|
search.setVal = function(val) {
|
|||
|
$searchInput.val(val);
|
|||
|
};
|
|||
|
|
|||
|
$searchInput.on('focus', function() {
|
|||
|
$(this).addClass('focus');
|
|||
|
});
|
|||
|
$searchInput.on('blur', function() {
|
|||
|
$(this).removeClass('focus');
|
|||
|
});
|
|||
|
$searchInput.on('input', window.throttle(function() {
|
|||
|
var val = $(this).val();
|
|||
|
if (val === '' || typeof val !== 'string') {
|
|||
|
search.clear && search.clear();
|
|||
|
} else {
|
|||
|
$searchBox.addClass('not-empty');
|
|||
|
search.onInputNotEmpty && search.onInputNotEmpty(val);
|
|||
|
}
|
|||
|
}, 400));
|
|||
|
$searchClear.on('click', function() {
|
|||
|
$searchInput.val(''); $searchBox.removeClass('not-empty');
|
|||
|
search.clear && search.clear();
|
|||
|
});
|
|||
|
}
|
|||
|
});
|
|||
|
})();
|
|||
|
</script><div class="search search--dark">
|
|||
|
<div class="main">
|
|||
|
<div class="search__header">Recherche</div>
|
|||
|
<div class="search-bar">
|
|||
|
<div class="search-box js-search-box">
|
|||
|
<div class="search-box__icon-search"><i class="fas fa-search"></i></div>
|
|||
|
<input id="search-input" type="text" />
|
|||
|
<div class="search-box__icon-clear js-icon-clear">
|
|||
|
<a><i class="fas fa-times"></i></a>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
<button class="button button--theme-dark button--pill search__cancel js-search-toggle">
|
|||
|
Annuler</button>
|
|||
|
</div>
|
|||
|
<div id="results-container" class="search-result js-search-result"></div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
<!-- Script pointing to search-script.js -->
|
|||
|
<script>/*!
|
|||
|
* Simple-Jekyll-Search
|
|||
|
* Copyright 2015-2020, Christian Fei
|
|||
|
* Licensed under the MIT License.
|
|||
|
*/
|
|||
|
|
|||
|
(function(){
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$Templater_7 = {
|
|||
|
compile: compile,
|
|||
|
setOptions: setOptions
|
|||
|
}
|
|||
|
|
|||
|
const options = {}
|
|||
|
options.pattern = /\{(.*?)\}/g
|
|||
|
options.template = ''
|
|||
|
options.middleware = function () {}
|
|||
|
|
|||
|
function setOptions (_options) {
|
|||
|
options.pattern = _options.pattern || options.pattern
|
|||
|
options.template = _options.template || options.template
|
|||
|
if (typeof _options.middleware === 'function') {
|
|||
|
options.middleware = _options.middleware
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function compile (data) {
|
|||
|
return options.template.replace(options.pattern, function (match, prop) {
|
|||
|
const value = options.middleware(prop, data[prop], options.template)
|
|||
|
if (typeof value !== 'undefined') {
|
|||
|
return value
|
|||
|
}
|
|||
|
return data[prop] || match
|
|||
|
})
|
|||
|
}
|
|||
|
|
|||
|
'use strict';
|
|||
|
|
|||
|
function fuzzysearch (needle, haystack) {
|
|||
|
var tlen = haystack.length;
|
|||
|
var qlen = needle.length;
|
|||
|
if (qlen > tlen) {
|
|||
|
return false;
|
|||
|
}
|
|||
|
if (qlen === tlen) {
|
|||
|
return needle === haystack;
|
|||
|
}
|
|||
|
outer: for (var i = 0, j = 0; i < qlen; i++) {
|
|||
|
var nch = needle.charCodeAt(i);
|
|||
|
while (j < tlen) {
|
|||
|
if (haystack.charCodeAt(j++) === nch) {
|
|||
|
continue outer;
|
|||
|
}
|
|||
|
}
|
|||
|
return false;
|
|||
|
}
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
var _$fuzzysearch_1 = fuzzysearch;
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
/* removed: const _$fuzzysearch_1 = require('fuzzysearch') */;
|
|||
|
|
|||
|
var _$FuzzySearchStrategy_5 = new FuzzySearchStrategy()
|
|||
|
|
|||
|
function FuzzySearchStrategy () {
|
|||
|
this.matches = function (string, crit) {
|
|||
|
return _$fuzzysearch_1(crit.toLowerCase(), string.toLowerCase())
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$LiteralSearchStrategy_6 = new LiteralSearchStrategy()
|
|||
|
|
|||
|
function LiteralSearchStrategy () {
|
|||
|
this.matches = function (str, crit) {
|
|||
|
if (!str) return false
|
|||
|
|
|||
|
str = str.trim().toLowerCase()
|
|||
|
crit = crit.trim().toLowerCase()
|
|||
|
|
|||
|
return crit.split(' ').filter(function (word) {
|
|||
|
return str.indexOf(word) >= 0
|
|||
|
}).length === crit.split(' ').length
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$Repository_4 = {
|
|||
|
put: put,
|
|||
|
clear: clear,
|
|||
|
search: search,
|
|||
|
setOptions: __setOptions_4
|
|||
|
}
|
|||
|
|
|||
|
/* removed: const _$FuzzySearchStrategy_5 = require('./SearchStrategies/FuzzySearchStrategy') */;
|
|||
|
/* removed: const _$LiteralSearchStrategy_6 = require('./SearchStrategies/LiteralSearchStrategy') */;
|
|||
|
|
|||
|
function NoSort () {
|
|||
|
return 0
|
|||
|
}
|
|||
|
|
|||
|
const data = []
|
|||
|
let opt = {}
|
|||
|
|
|||
|
opt.fuzzy = false
|
|||
|
opt.limit = 10
|
|||
|
opt.searchStrategy = opt.fuzzy ? _$FuzzySearchStrategy_5 : _$LiteralSearchStrategy_6
|
|||
|
opt.sort = NoSort
|
|||
|
opt.exclude = []
|
|||
|
|
|||
|
function put (data) {
|
|||
|
if (isObject(data)) {
|
|||
|
return addObject(data)
|
|||
|
}
|
|||
|
if (isArray(data)) {
|
|||
|
return addArray(data)
|
|||
|
}
|
|||
|
return undefined
|
|||
|
}
|
|||
|
function clear () {
|
|||
|
data.length = 0
|
|||
|
return data
|
|||
|
}
|
|||
|
|
|||
|
function isObject (obj) {
|
|||
|
return Boolean(obj) && Object.prototype.toString.call(obj) === '[object Object]'
|
|||
|
}
|
|||
|
|
|||
|
function isArray (obj) {
|
|||
|
return Boolean(obj) && Object.prototype.toString.call(obj) === '[object Array]'
|
|||
|
}
|
|||
|
|
|||
|
function addObject (_data) {
|
|||
|
data.push(_data)
|
|||
|
return data
|
|||
|
}
|
|||
|
|
|||
|
function addArray (_data) {
|
|||
|
const added = []
|
|||
|
clear()
|
|||
|
for (let i = 0, len = _data.length; i < len; i++) {
|
|||
|
if (isObject(_data[i])) {
|
|||
|
added.push(addObject(_data[i]))
|
|||
|
}
|
|||
|
}
|
|||
|
return added
|
|||
|
}
|
|||
|
|
|||
|
function search (crit) {
|
|||
|
if (!crit) {
|
|||
|
return []
|
|||
|
}
|
|||
|
return findMatches(data, crit, opt.searchStrategy, opt).sort(opt.sort)
|
|||
|
}
|
|||
|
|
|||
|
function __setOptions_4 (_opt) {
|
|||
|
opt = _opt || {}
|
|||
|
|
|||
|
opt.fuzzy = _opt.fuzzy || false
|
|||
|
opt.limit = _opt.limit || 10
|
|||
|
opt.searchStrategy = _opt.fuzzy ? _$FuzzySearchStrategy_5 : _$LiteralSearchStrategy_6
|
|||
|
opt.sort = _opt.sort || NoSort
|
|||
|
opt.exclude = _opt.exclude || []
|
|||
|
}
|
|||
|
|
|||
|
function findMatches (data, crit, strategy, opt) {
|
|||
|
const matches = []
|
|||
|
for (let i = 0; i < data.length && matches.length < opt.limit; i++) {
|
|||
|
const match = findMatchesInObject(data[i], crit, strategy, opt)
|
|||
|
if (match) {
|
|||
|
matches.push(match)
|
|||
|
}
|
|||
|
}
|
|||
|
return matches
|
|||
|
}
|
|||
|
|
|||
|
function findMatchesInObject (obj, crit, strategy, opt) {
|
|||
|
for (const key in obj) {
|
|||
|
if (!isExcluded(obj[key], opt.exclude) && strategy.matches(obj[key], crit)) {
|
|||
|
return obj
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function isExcluded (term, excludedTerms) {
|
|||
|
for (let i = 0, len = excludedTerms.length; i < len; i++) {
|
|||
|
const excludedTerm = excludedTerms[i]
|
|||
|
if (new RegExp(excludedTerm).test(term)) {
|
|||
|
return true
|
|||
|
}
|
|||
|
}
|
|||
|
return false
|
|||
|
}
|
|||
|
|
|||
|
/* globals ActiveXObject:false */
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$JSONLoader_2 = {
|
|||
|
load: load
|
|||
|
}
|
|||
|
|
|||
|
function load (location, callback) {
|
|||
|
const xhr = getXHR()
|
|||
|
xhr.open('GET', location, true)
|
|||
|
xhr.onreadystatechange = createStateChangeListener(xhr, callback)
|
|||
|
xhr.send()
|
|||
|
}
|
|||
|
|
|||
|
function createStateChangeListener (xhr, callback) {
|
|||
|
return function () {
|
|||
|
if (xhr.readyState === 4 && xhr.status === 200) {
|
|||
|
try {
|
|||
|
callback(null, JSON.parse(xhr.responseText))
|
|||
|
} catch (err) {
|
|||
|
callback(err, null)
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function getXHR () {
|
|||
|
return window.XMLHttpRequest ? new window.XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP')
|
|||
|
}
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$OptionsValidator_3 = function OptionsValidator (params) {
|
|||
|
if (!validateParams(params)) {
|
|||
|
throw new Error('-- OptionsValidator: required options missing')
|
|||
|
}
|
|||
|
|
|||
|
if (!(this instanceof OptionsValidator)) {
|
|||
|
return new OptionsValidator(params)
|
|||
|
}
|
|||
|
|
|||
|
const requiredOptions = params.required
|
|||
|
|
|||
|
this.getRequiredOptions = function () {
|
|||
|
return requiredOptions
|
|||
|
}
|
|||
|
|
|||
|
this.validate = function (parameters) {
|
|||
|
const errors = []
|
|||
|
requiredOptions.forEach(function (requiredOptionName) {
|
|||
|
if (typeof parameters[requiredOptionName] === 'undefined') {
|
|||
|
errors.push(requiredOptionName)
|
|||
|
}
|
|||
|
})
|
|||
|
return errors
|
|||
|
}
|
|||
|
|
|||
|
function validateParams (params) {
|
|||
|
if (!params) {
|
|||
|
return false
|
|||
|
}
|
|||
|
return typeof params.required !== 'undefined' && params.required instanceof Array
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$utils_9 = {
|
|||
|
merge: merge,
|
|||
|
isJSON: isJSON
|
|||
|
}
|
|||
|
|
|||
|
function merge (defaultParams, mergeParams) {
|
|||
|
const mergedOptions = {}
|
|||
|
for (const option in defaultParams) {
|
|||
|
mergedOptions[option] = defaultParams[option]
|
|||
|
if (typeof mergeParams[option] !== 'undefined') {
|
|||
|
mergedOptions[option] = mergeParams[option]
|
|||
|
}
|
|||
|
}
|
|||
|
return mergedOptions
|
|||
|
}
|
|||
|
|
|||
|
function isJSON (json) {
|
|||
|
try {
|
|||
|
if (json instanceof Object && JSON.parse(JSON.stringify(json))) {
|
|||
|
return true
|
|||
|
}
|
|||
|
return false
|
|||
|
} catch (err) {
|
|||
|
return false
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
var _$src_8 = {};
|
|||
|
(function (window) {
|
|||
|
'use strict'
|
|||
|
|
|||
|
let options = {
|
|||
|
searchInput: null,
|
|||
|
resultsContainer: null,
|
|||
|
json: [],
|
|||
|
success: Function.prototype,
|
|||
|
searchResultTemplate: '<li><a href="{url}" title="{desc}">{title}</a></li>',
|
|||
|
templateMiddleware: Function.prototype,
|
|||
|
sortMiddleware: function () {
|
|||
|
return 0
|
|||
|
},
|
|||
|
noResultsText: 'No results found',
|
|||
|
limit: 10,
|
|||
|
fuzzy: false,
|
|||
|
debounceTime: null,
|
|||
|
exclude: []
|
|||
|
}
|
|||
|
|
|||
|
let debounceTimerHandle
|
|||
|
const debounce = function (func, delayMillis) {
|
|||
|
if (delayMillis) {
|
|||
|
clearTimeout(debounceTimerHandle)
|
|||
|
debounceTimerHandle = setTimeout(func, delayMillis)
|
|||
|
} else {
|
|||
|
func.call()
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
const requiredOptions = ['searchInput', 'resultsContainer', 'json']
|
|||
|
|
|||
|
/* removed: const _$Templater_7 = require('./Templater') */;
|
|||
|
/* removed: const _$Repository_4 = require('./Repository') */;
|
|||
|
/* removed: const _$JSONLoader_2 = require('./JSONLoader') */;
|
|||
|
const optionsValidator = _$OptionsValidator_3({
|
|||
|
required: requiredOptions
|
|||
|
})
|
|||
|
/* removed: const _$utils_9 = require('./utils') */;
|
|||
|
|
|||
|
window.SimpleJekyllSearch = function (_options) {
|
|||
|
const errors = optionsValidator.validate(_options)
|
|||
|
if (errors.length > 0) {
|
|||
|
throwError('You must specify the following required options: ' + requiredOptions)
|
|||
|
}
|
|||
|
|
|||
|
options = _$utils_9.merge(options, _options)
|
|||
|
|
|||
|
_$Templater_7.setOptions({
|
|||
|
template: options.searchResultTemplate,
|
|||
|
middleware: options.templateMiddleware
|
|||
|
})
|
|||
|
|
|||
|
_$Repository_4.setOptions({
|
|||
|
fuzzy: options.fuzzy,
|
|||
|
limit: options.limit,
|
|||
|
sort: options.sortMiddleware,
|
|||
|
exclude: options.exclude
|
|||
|
})
|
|||
|
|
|||
|
if (_$utils_9.isJSON(options.json)) {
|
|||
|
initWithJSON(options.json)
|
|||
|
} else {
|
|||
|
initWithURL(options.json)
|
|||
|
}
|
|||
|
|
|||
|
const rv = {
|
|||
|
search: search
|
|||
|
}
|
|||
|
|
|||
|
typeof options.success === 'function' && options.success.call(rv)
|
|||
|
return rv
|
|||
|
}
|
|||
|
|
|||
|
function initWithJSON (json) {
|
|||
|
_$Repository_4.put(json)
|
|||
|
registerInput()
|
|||
|
}
|
|||
|
|
|||
|
function initWithURL (url) {
|
|||
|
_$JSONLoader_2.load(url, function (err, json) {
|
|||
|
if (err) {
|
|||
|
throwError('failed to get JSON (' + url + ')')
|
|||
|
}
|
|||
|
initWithJSON(json)
|
|||
|
})
|
|||
|
}
|
|||
|
|
|||
|
function emptyResultsContainer () {
|
|||
|
options.resultsContainer.innerHTML = ''
|
|||
|
}
|
|||
|
|
|||
|
function appendToResultsContainer (text) {
|
|||
|
options.resultsContainer.innerHTML += text
|
|||
|
}
|
|||
|
|
|||
|
function registerInput () {
|
|||
|
options.searchInput.addEventListener('input', function (e) {
|
|||
|
if (isWhitelistedKey(e.which)) {
|
|||
|
emptyResultsContainer()
|
|||
|
debounce(function () { search(e.target.value) }, options.debounceTime)
|
|||
|
}
|
|||
|
})
|
|||
|
}
|
|||
|
|
|||
|
function search (query) {
|
|||
|
if (isValidQuery(query)) {
|
|||
|
emptyResultsContainer()
|
|||
|
render(_$Repository_4.search(query), query)
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function render (results, query) {
|
|||
|
const len = results.length
|
|||
|
if (len === 0) {
|
|||
|
return appendToResultsContainer(options.noResultsText)
|
|||
|
}
|
|||
|
for (let i = 0; i < len; i++) {
|
|||
|
results[i].query = query
|
|||
|
appendToResultsContainer(_$Templater_7.compile(results[i]))
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function isValidQuery (query) {
|
|||
|
return query && query.length > 0
|
|||
|
}
|
|||
|
|
|||
|
function isWhitelistedKey (key) {
|
|||
|
return [13, 16, 20, 37, 38, 39, 40, 91].indexOf(key) === -1
|
|||
|
}
|
|||
|
|
|||
|
function throwError (message) {
|
|||
|
throw new Error('SimpleJekyllSearch --- ' + message)
|
|||
|
}
|
|||
|
})(window)
|
|||
|
|
|||
|
}());
|
|||
|
</script>
|
|||
|
|
|||
|
<!-- Configuration -->
|
|||
|
<script>
|
|||
|
SimpleJekyllSearch({
|
|||
|
searchInput: document.getElementById('search-input'),
|
|||
|
resultsContainer: document.getElementById('results-container'),
|
|||
|
noResultsText: '<p>Aucun résultat!</p>',
|
|||
|
json: '/search.json',
|
|||
|
searchResultTemplate: '<li><a href="{url}">{date} {title}</a> (Création {create})</li>'
|
|||
|
})
|
|||
|
</script>
|
|||
|
|
|||
|
</div></div>
|
|||
|
|
|||
|
|
|||
|
<script>(function() {
|
|||
|
var SOURCES = window.TEXT_VARIABLES.sources;
|
|||
|
window.Lazyload.js(SOURCES.jquery, function() {
|
|||
|
function scrollToAnchor(anchor, duration, callback) {
|
|||
|
var $root = this;
|
|||
|
$root.animate({ scrollTop: $(anchor).position().top }, duration, function() {
|
|||
|
window.history.replaceState(null, '', window.location.href.split('#')[0] + anchor);
|
|||
|
callback && callback();
|
|||
|
});
|
|||
|
}
|
|||
|
$.fn.scrollToAnchor = scrollToAnchor;
|
|||
|
});
|
|||
|
})();
|
|||
|
(function() {
|
|||
|
var SOURCES = window.TEXT_VARIABLES.sources;
|
|||
|
window.Lazyload.js(SOURCES.jquery, function() {
|
|||
|
function affix(options) {
|
|||
|
var $root = this, $window = $(window), $scrollTarget, $scroll,
|
|||
|
offsetBottom = 0, scrollTarget = window, scroll = window.document, disabled = false, isOverallScroller = true,
|
|||
|
rootTop, rootLeft, rootHeight, scrollBottom, rootBottomTop,
|
|||
|
hasInit = false, curState;
|
|||
|
|
|||
|
function setOptions(options) {
|
|||
|
var _options = options || {};
|
|||
|
_options.offsetBottom && (offsetBottom = _options.offsetBottom);
|
|||
|
_options.scrollTarget && (scrollTarget = _options.scrollTarget);
|
|||
|
_options.scroll && (scroll = _options.scroll);
|
|||
|
_options.disabled !== undefined && (disabled = _options.disabled);
|
|||
|
$scrollTarget = $(scrollTarget);
|
|||
|
isOverallScroller = window.isOverallScroller($scrollTarget[0]);
|
|||
|
$scroll = $(scroll);
|
|||
|
}
|
|||
|
function preCalc() {
|
|||
|
top();
|
|||
|
rootHeight = $root.outerHeight();
|
|||
|
rootTop = $root.offset().top + (isOverallScroller ? 0 : $scrollTarget.scrollTop());
|
|||
|
rootLeft = $root.offset().left;
|
|||
|
}
|
|||
|
function calc(needPreCalc) {
|
|||
|
needPreCalc && preCalc();
|
|||
|
scrollBottom = $scroll.outerHeight() - offsetBottom - rootHeight;
|
|||
|
rootBottomTop = scrollBottom - rootTop;
|
|||
|
}
|
|||
|
function top() {
|
|||
|
if (curState !== 'top') {
|
|||
|
$root.removeClass('fixed').css({
|
|||
|
left: 0,
|
|||
|
top: 0
|
|||
|
});
|
|||
|
curState = 'top';
|
|||
|
}
|
|||
|
}
|
|||
|
function fixed() {
|
|||
|
if (curState !== 'fixed') {
|
|||
|
$root.addClass('fixed').css({
|
|||
|
left: rootLeft + 'px',
|
|||
|
top: 0
|
|||
|
});
|
|||
|
curState = 'fixed';
|
|||
|
}
|
|||
|
}
|
|||
|
function bottom() {
|
|||
|
if (curState !== 'bottom') {
|
|||
|
$root.removeClass('fixed').css({
|
|||
|
left: 0,
|
|||
|
top: rootBottomTop + 'px'
|
|||
|
});
|
|||
|
curState = 'bottom';
|
|||
|
}
|
|||
|
}
|
|||
|
function setState() {
|
|||
|
var scrollTop = $scrollTarget.scrollTop();
|
|||
|
if (scrollTop >= rootTop && scrollTop <= scrollBottom) {
|
|||
|
fixed();
|
|||
|
} else if (scrollTop < rootTop) {
|
|||
|
top();
|
|||
|
} else {
|
|||
|
bottom();
|
|||
|
}
|
|||
|
}
|
|||
|
function init() {
|
|||
|
if(!hasInit) {
|
|||
|
var interval, timeout;
|
|||
|
calc(true); setState();
|
|||
|
// run calc every 100 millisecond
|
|||
|
interval = setInterval(function() {
|
|||
|
calc();
|
|||
|
}, 100);
|
|||
|
timeout = setTimeout(function() {
|
|||
|
clearInterval(interval);
|
|||
|
}, 45000);
|
|||
|
window.pageLoad.then(function() {
|
|||
|
setTimeout(function() {
|
|||
|
clearInterval(interval);
|
|||
|
clearTimeout(timeout);
|
|||
|
}, 3000);
|
|||
|
});
|
|||
|
$scrollTarget.on('scroll', function() {
|
|||
|
disabled || setState();
|
|||
|
});
|
|||
|
$window.on('resize', function() {
|
|||
|
disabled || (calc(true), setState());
|
|||
|
});
|
|||
|
hasInit = true;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
setOptions(options);
|
|||
|
if (!disabled) {
|
|||
|
init();
|
|||
|
}
|
|||
|
$window.on('resize', window.throttle(function() {
|
|||
|
init();
|
|||
|
}, 200));
|
|||
|
return {
|
|||
|
setOptions: setOptions,
|
|||
|
refresh: function() {
|
|||
|
calc(true, { animation: false }); setState();
|
|||
|
}
|
|||
|
};
|
|||
|
}
|
|||
|
$.fn.affix = affix;
|
|||
|
});
|
|||
|
})();
|
|||
|
(function() {
|
|||
|
var SOURCES = window.TEXT_VARIABLES.sources;
|
|||
|
window.Lazyload.js(SOURCES.jquery, function() {
|
|||
|
function toc(options) {
|
|||
|
var $root = this, $window = $(window), $scrollTarget, $scroller, $tocUl = $('<ul class="toc toc--ellipsis"></ul>'), $tocLi, $headings, $activeLast, $activeCur,
|
|||
|
selectors = 'h1,h2,h3', container = 'body', scrollTarget = window, scroller = 'html, body', disabled = false,
|
|||
|
headingsPos, scrolling = false, hasRendered = false, hasInit = false;
|
|||
|
|
|||
|
function setOptions(options) {
|
|||
|
var _options = options || {};
|
|||
|
_options.selectors && (selectors = _options.selectors);
|
|||
|
_options.container && (container = _options.container);
|
|||
|
_options.scrollTarget && (scrollTarget = _options.scrollTarget);
|
|||
|
_options.scroller && (scroller = _options.scroller);
|
|||
|
_options.disabled !== undefined && (disabled = _options.disabled);
|
|||
|
$headings = $(container).find(selectors).filter('[id]');
|
|||
|
$scrollTarget = $(scrollTarget);
|
|||
|
$scroller = $(scroller);
|
|||
|
}
|
|||
|
function calc() {
|
|||
|
headingsPos = [];
|
|||
|
$headings.each(function() {
|
|||
|
headingsPos.push(Math.floor($(this).position().top));
|
|||
|
});
|
|||
|
}
|
|||
|
function setState(element, disabled) {
|
|||
|
var scrollTop = $scrollTarget.scrollTop(), i;
|
|||
|
if (disabled || !headingsPos || headingsPos.length < 1) { return; }
|
|||
|
if (element) {
|
|||
|
$activeCur = element;
|
|||
|
} else {
|
|||
|
for (i = 0; i < headingsPos.length; i++) {
|
|||
|
if (scrollTop >= headingsPos[i]) {
|
|||
|
$activeCur = $tocLi.eq(i);
|
|||
|
} else {
|
|||
|
$activeCur || ($activeCur = $tocLi.eq(i));
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
$activeLast && $activeLast.removeClass('active');
|
|||
|
($activeLast = $activeCur).addClass('active');
|
|||
|
}
|
|||
|
function render() {
|
|||
|
if(!hasRendered) {
|
|||
|
$root.append($tocUl);
|
|||
|
$headings.each(function() {
|
|||
|
var $this = $(this);
|
|||
|
$tocUl.append($('<li></li>').addClass('toc-' + $this.prop('tagName').toLowerCase())
|
|||
|
.append($('<a></a>').text($this.text()).attr('href', '#' + $this.prop('id'))));
|
|||
|
});
|
|||
|
$tocLi = $tocUl.children('li');
|
|||
|
$tocUl.on('click', 'a', function(e) {
|
|||
|
e.preventDefault();
|
|||
|
var $this = $(this);
|
|||
|
scrolling = true;
|
|||
|
setState($this.parent());
|
|||
|
$scroller.scrollToAnchor($this.attr('href'), 400, function() {
|
|||
|
scrolling = false;
|
|||
|
});
|
|||
|
});
|
|||
|
}
|
|||
|
hasRendered = true;
|
|||
|
}
|
|||
|
function init() {
|
|||
|
var interval, timeout;
|
|||
|
if(!hasInit) {
|
|||
|
render(); calc(); setState(null, scrolling);
|
|||
|
// run calc every 100 millisecond
|
|||
|
interval = setInterval(function() {
|
|||
|
calc();
|
|||
|
}, 100);
|
|||
|
timeout = setTimeout(function() {
|
|||
|
clearInterval(interval);
|
|||
|
}, 45000);
|
|||
|
window.pageLoad.then(function() {
|
|||
|
setTimeout(function() {
|
|||
|
clearInterval(interval);
|
|||
|
clearTimeout(timeout);
|
|||
|
}, 3000);
|
|||
|
});
|
|||
|
$scrollTarget.on('scroll', function() {
|
|||
|
disabled || setState(null, scrolling);
|
|||
|
});
|
|||
|
$window.on('resize', window.throttle(function() {
|
|||
|
if (!disabled) {
|
|||
|
render(); calc(); setState(null, scrolling);
|
|||
|
}
|
|||
|
}, 100));
|
|||
|
}
|
|||
|
hasInit = true;
|
|||
|
}
|
|||
|
|
|||
|
setOptions(options);
|
|||
|
if (!disabled) {
|
|||
|
init();
|
|||
|
}
|
|||
|
$window.on('resize', window.throttle(function() {
|
|||
|
init();
|
|||
|
}, 200));
|
|||
|
return {
|
|||
|
setOptions: setOptions
|
|||
|
};
|
|||
|
}
|
|||
|
$.fn.toc = toc;
|
|||
|
});
|
|||
|
})();
|
|||
|
/*(function () {
|
|||
|
|
|||
|
})();*/
|
|||
|
</script><script>
|
|||
|
/* toc must before affix, since affix need to konw toc' height. */(function() {
|
|||
|
var SOURCES = window.TEXT_VARIABLES.sources;
|
|||
|
var TOC_SELECTOR = window.TEXT_VARIABLES.site.toc.selectors;
|
|||
|
window.Lazyload.js(SOURCES.jquery, function() {
|
|||
|
var $window = $(window);
|
|||
|
var $articleContent = $('.js-article-content');
|
|||
|
var $tocRoot = $('.js-toc-root'), $col2 = $('.js-col-aside');
|
|||
|
var toc;
|
|||
|
var tocDisabled = false;
|
|||
|
var hasSidebar = $('.js-page-root').hasClass('layout--page--sidebar');
|
|||
|
var hasToc = $articleContent.find(TOC_SELECTOR).length > 0;
|
|||
|
|
|||
|
function disabled() {
|
|||
|
return $col2.css('display') === 'none' || !hasToc;
|
|||
|
}
|
|||
|
|
|||
|
tocDisabled = disabled();
|
|||
|
|
|||
|
toc = $tocRoot.toc({
|
|||
|
selectors: TOC_SELECTOR,
|
|||
|
container: $articleContent,
|
|||
|
scrollTarget: hasSidebar ? '.js-page-main' : null,
|
|||
|
scroller: hasSidebar ? '.js-page-main' : null,
|
|||
|
disabled: tocDisabled
|
|||
|
});
|
|||
|
|
|||
|
$window.on('resize', window.throttle(function() {
|
|||
|
tocDisabled = disabled();
|
|||
|
toc && toc.setOptions({
|
|||
|
disabled: tocDisabled
|
|||
|
});
|
|||
|
}, 100));
|
|||
|
|
|||
|
});
|
|||
|
})();
|
|||
|
(function() {
|
|||
|
var SOURCES = window.TEXT_VARIABLES.sources;
|
|||
|
window.Lazyload.js(SOURCES.jquery, function() {
|
|||
|
var $window = $(window), $pageFooter = $('.js-page-footer');
|
|||
|
var $pageAside = $('.js-page-aside');
|
|||
|
var affix;
|
|||
|
var tocDisabled = false;
|
|||
|
var hasSidebar = $('.js-page-root').hasClass('layout--page--sidebar');
|
|||
|
|
|||
|
affix = $pageAside.affix({
|
|||
|
offsetBottom: $pageFooter.outerHeight(),
|
|||
|
scrollTarget: hasSidebar ? '.js-page-main' : null,
|
|||
|
scroller: hasSidebar ? '.js-page-main' : null,
|
|||
|
scroll: hasSidebar ? $('.js-page-main').children() : null,
|
|||
|
disabled: tocDisabled
|
|||
|
});
|
|||
|
|
|||
|
$window.on('resize', window.throttle(function() {
|
|||
|
affix && affix.setOptions({
|
|||
|
disabled: tocDisabled
|
|||
|
});
|
|||
|
}, 100));
|
|||
|
|
|||
|
window.pageAsideAffix = affix;
|
|||
|
});
|
|||
|
})();
|
|||
|
</script><!---->
|
|||
|
|
|||
|
</div>
|
|||
|
<script>(function () {
|
|||
|
var $root = document.getElementsByClassName('root')[0];
|
|||
|
if (window.hasEvent('touchstart')) {
|
|||
|
$root.dataset.isTouch = true;
|
|||
|
document.addEventListener('touchstart', function(){}, false);
|
|||
|
}
|
|||
|
})();
|
|||
|
</script>
|
|||
|
</body>
|
|||
|
</html>
|
|||
|
|