mirror of
https://github.com/syncthing/syncthing.git
synced 2026-01-03 19:39:20 -05:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
490ec4350c | ||
|
|
dc0dd09e93 | ||
|
|
7ec76095e6 | ||
|
|
cb26552440 | ||
|
|
1ae5ac7d0b | ||
|
|
eeb7091180 | ||
|
|
dc38e6ae88 | ||
|
|
eb6cad7f93 | ||
|
|
ae30b46bfe | ||
|
|
eab268f5f8 | ||
|
|
1e21042138 | ||
|
|
a56f70ab94 | ||
|
|
11c57b9097 | ||
|
|
1921533c4c | ||
|
|
89e762fd6e | ||
|
|
a63d3ee625 | ||
|
|
82741ad207 | ||
|
|
bd363fe0b7 | ||
|
|
7a4c6d262f | ||
|
|
445a82f120 | ||
|
|
69ce121267 | ||
|
|
08e3cd1cce | ||
|
|
da0e5edbec | ||
|
|
c78fa42f31 | ||
|
|
993a3ebe73 | ||
|
|
8040502599 |
4
AUTHORS
4
AUTHORS
@@ -63,6 +63,7 @@ Carsten Hagemann (carstenhag) <moter8@gmail.com> <carsten@chagemann.de>
|
||||
Cathryne Linenweaver (Cathryne) <cathryne.linenweaver@gmail.com> <Cathryne@users.noreply.github.com> <katrinleinweber@MAC.local>
|
||||
Cedric Staniewski (xduugu) <cedric@gmx.ca>
|
||||
chenrui <rui@meetup.com>
|
||||
Chih-Hsuan Yen <yan12125@gmail.com>
|
||||
Choongkyu <choongkyu.kim+gh@gmail.com> <vapidlyrapid+gh@gmail.com>
|
||||
Chris Howie (cdhowie) <me@chrishowie.com>
|
||||
Chris Joel (cdata) <chris@scriptolo.gy>
|
||||
@@ -143,6 +144,7 @@ Jonathan <artback@protonmail.com>
|
||||
Jonathan Cross <jcross@gmail.com>
|
||||
Jonta <359397+Jonta@users.noreply.github.com>
|
||||
Jose Manuel Delicado (jmdaweb) <jmdaweb@hotmail.com> <jmdaweb@users.noreply.github.com>
|
||||
jtagcat <git-514635f7@jtag.cat>
|
||||
Jörg Thalheim <Mic92@users.noreply.github.com>
|
||||
Jędrzej Kula <kula.jedrek@gmail.com>
|
||||
Kalle Laine <pahakalle@protonmail.com>
|
||||
@@ -264,7 +266,7 @@ Unrud (Unrud) <unrud@openaliasbox.org> <Unrud@users.noreply.github.com>
|
||||
Veeti Paananen (veeti) <veeti.paananen@rojekti.fi>
|
||||
Victor Buinsky (buinsky) <vix_booja@tut.by>
|
||||
Vil Brekin (Vilbrekin) <vilbrekin@gmail.com>
|
||||
Vladimir Rusinov <vrusinov@google.com>
|
||||
Vladimir Rusinov <vrusinov@google.com> <vladimir.rusinov@gmail.com>
|
||||
wangguoliang <liangcszzu@163.com>
|
||||
William A. Kennington III (wkennington) <william@wkennington.com>
|
||||
wouter bolsterlee <wouter@bolsterl.ee>
|
||||
|
||||
27
build.go
27
build.go
@@ -14,7 +14,6 @@ import (
|
||||
"bytes"
|
||||
"compress/flate"
|
||||
"compress/gzip"
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"flag"
|
||||
@@ -1382,32 +1381,6 @@ func metalintShort() {
|
||||
runPrint(goCmd, "test", "-short", "-run", "Metalint", "./meta")
|
||||
}
|
||||
|
||||
func temporaryBuildDir() (string, error) {
|
||||
// The base of our temp dir is "syncthing-xxxxxxxx" where the x:es
|
||||
// are eight bytes from the sha256 of our working directory. We do
|
||||
// this because we want a name in the global temp dir that doesn't
|
||||
// conflict with someone else building syncthing on the same
|
||||
// machine, yet is persistent between runs from the same source
|
||||
// directory.
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
hash := sha256.Sum256([]byte(wd))
|
||||
base := fmt.Sprintf("syncthing-%x", hash[:4])
|
||||
|
||||
// The temp dir is taken from $STTMPDIR if set, otherwise the system
|
||||
// default (potentially infrluenced by $TMPDIR on unixes).
|
||||
var tmpDir string
|
||||
if t := os.Getenv("STTMPDIR"); t != "" {
|
||||
tmpDir = t
|
||||
} else {
|
||||
tmpDir = os.TempDir()
|
||||
}
|
||||
|
||||
return filepath.Join(tmpDir, base), nil
|
||||
}
|
||||
|
||||
func (t target) BinaryName() string {
|
||||
if goos == "windows" {
|
||||
return t.binaryName + ".exe"
|
||||
|
||||
@@ -89,7 +89,8 @@ func handleFailureFn(dsn string) func(w http.ResponseWriter, req *http.Request)
|
||||
pkt.Extra = raven.Extra{
|
||||
"count": r.Count,
|
||||
}
|
||||
pkt.Fingerprint = []string{r.Description}
|
||||
message := sanitizeMessageLDB(r.Description)
|
||||
pkt.Fingerprint = []string{message}
|
||||
|
||||
if err := sendReport(dsn, pkt, userIDFor(req)); err != nil {
|
||||
log.Println("Failed to send failure report:", err)
|
||||
|
||||
@@ -143,15 +143,20 @@ var (
|
||||
ldbPathRe = regexp.MustCompile(`(open|write|read) .+[\\/].+[\\/]index[^\\/]+[\\/][^\\/]+: `)
|
||||
)
|
||||
|
||||
func crashReportFingerprint(message string) []string {
|
||||
// Do not fingerprint on the stack in case of db corruption or fatal
|
||||
// db io error - where it occurs doesn't matter.
|
||||
orig := message
|
||||
func sanitizeMessageLDB(message string) string {
|
||||
message = ldbPosRe.ReplaceAllString(message, "${1}x)")
|
||||
message = ldbFileRe.ReplaceAllString(message, "${1}x${3}")
|
||||
message = ldbChecksumRe.ReplaceAllString(message, "${1}X${3}X")
|
||||
message = ldbInternalKeyRe.ReplaceAllString(message, "${1}x${2}x")
|
||||
message = ldbPathRe.ReplaceAllString(message, "$1 x: ")
|
||||
return message
|
||||
}
|
||||
|
||||
func crashReportFingerprint(message string) []string {
|
||||
// Do not fingerprint on the stack in case of db corruption or fatal
|
||||
// db io error - where it occurs doesn't matter.
|
||||
orig := message
|
||||
message = sanitizeMessageLDB(message)
|
||||
if message != orig {
|
||||
return []string{message}
|
||||
}
|
||||
|
||||
@@ -2,29 +2,29 @@
|
||||
"A device with that ID is already added.": "Устройство с този идентификатор е вече добавено.",
|
||||
"A negative number of days doesn't make sense.": "Няма логика в задаването на отрицателен брой дни.",
|
||||
"A new major version may not be compatible with previous versions.": "Нова основна версия, която може да не е съвместима с предишни версии.",
|
||||
"API Key": "API Ключ",
|
||||
"API Key": "Ключ за ППИ",
|
||||
"About": "За програмата",
|
||||
"Action": "Действие",
|
||||
"Actions": "Меню",
|
||||
"Add": "Добави",
|
||||
"Add Device": "Добави устройство",
|
||||
"Add Folder": "Добави папка",
|
||||
"Add Remote Device": "Добави ново устройство",
|
||||
"Add devices from the introducer to our device list, for mutually shared folders.": "Добавяне на нови устройства през устройства предлагащи други устройства, за взаимно споделени папки.",
|
||||
"Add new folder?": "Добави нова папка?",
|
||||
"Add Remote Device": "Ново отдалечено устройство",
|
||||
"Add devices from the introducer to our device list, for mutually shared folders.": "Добавяйте устройства през поръчителите за взаимно споделени папки.",
|
||||
"Add new folder?": "Добавяне на папка?",
|
||||
"Additionally the full rescan interval will be increased (times 60, i.e. new default of 1h). You can also configure it manually for every folder later after choosing No.": "Също така интервалът за повторно сканиране ще бъде увеличен (60 пъти, пр. новият интервал бъде 1ч). Освен това може да го зададете и ръчно за всяка папка след като изберете Не.",
|
||||
"Address": "Адрес",
|
||||
"Addresses": "Адреси",
|
||||
"Advanced": "Допълнителни",
|
||||
"Advanced Configuration": "Допълнителни настройки",
|
||||
"Advanced Configuration": "Разширени настройки",
|
||||
"All Data": "Всички данни",
|
||||
"All folders shared with this device must be protected by a password, such that all sent data is unreadable without the given password.": "All folders shared with this device must be protected by a password, such that all sent data is unreadable without the given password.",
|
||||
"Allow Anonymous Usage Reporting?": "Разрешаване изпращането на анонимни статистически данни?",
|
||||
"All folders shared with this device must be protected by a password, such that all sent data is unreadable without the given password.": "Всички, споделени с това устройство папки трябва да бъдат защитени с парола, за да може данните да са недосстъпни без нея.",
|
||||
"Allow Anonymous Usage Reporting?": "Разрешаване на анонимното отчитане на употребата?",
|
||||
"Allowed Networks": "Разрешени мрежи",
|
||||
"Alphabetic": "Азбучен ред",
|
||||
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "Външна команда се занимава с версиите. Тази команда трябва да премахне файла от синхронизираната папка. Ако пътят до това приложение използва интервали, то той трябва да бъде заграден в кавички.",
|
||||
"Anonymous Usage Reporting": "Анонимен доклад",
|
||||
"Anonymous usage report format has changed. Would you like to move to the new format?": "Форматът на анонимния доклад е променен. Желаете ли да преминете към новия формат?",
|
||||
"An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.": "Външна команда управлява версиите. Тя трябва да премахне файла от синхронизираната папка. Ако в пътя до приложението има интервали, то той трябва да бъде поставен в кавички.",
|
||||
"Anonymous Usage Reporting": "Анонимно отчитане на употреба",
|
||||
"Anonymous usage report format has changed. Would you like to move to the new format?": "Форматът на данните за анонимно отчитане на употреба е променен. Желаете ли да използвате него вместо стария?",
|
||||
"Are you sure you want to continue?": "Are you sure you want to continue?",
|
||||
"Are you sure you want to permanently delete all these files?": "Are you sure you want to permanently delete all these files?",
|
||||
"Are you sure you want to remove device {%name%}?": "Сигурни ли сте, че искате да премахнете устройството {{name}}?",
|
||||
@@ -32,21 +32,21 @@
|
||||
"Are you sure you want to restore {%count%} files?": "Сигурни ли сте, че искате да възстановите файла {{count}}?",
|
||||
"Are you sure you want to upgrade?": "Are you sure you want to upgrade?",
|
||||
"Auto Accept": "Автоматично приемане",
|
||||
"Automatic Crash Reporting": "Automatic Crash Reporting",
|
||||
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Автоматичното обновяване вече предлага избор между стабилни версии и кандидат версии.",
|
||||
"Automatic Crash Reporting": "Автоматично изпращане на доклад за срив",
|
||||
"Automatic upgrade now offers the choice between stable releases and release candidates.": "Автоматичното обновяване вече предлага избор между стабилни и предварителни издания.",
|
||||
"Automatic upgrades": "Автоматично обновяване",
|
||||
"Automatic upgrades are always enabled for candidate releases.": "Automatic upgrades are always enabled for candidate releases.",
|
||||
"Automatic upgrades are always enabled for candidate releases.": "Автоматичното обновяване е винаги включено за предварителните издания.",
|
||||
"Automatically create or share folders that this device advertises at the default path.": "Автоматично създаване или споделяне на папки, които това устройство предлага в пътя по подразбиране.",
|
||||
"Available debug logging facilities:": "Дебъгинг функционалност на разположение:",
|
||||
"Available debug logging facilities:": "Available debug logging facilities:",
|
||||
"Be careful!": "Внимание!",
|
||||
"Bugs": "Бъгове",
|
||||
"Bugs": "Дефекти",
|
||||
"Changelog": "Списък с промени",
|
||||
"Clean out after": "Изчисти след",
|
||||
"Clean out after": "Clean out after",
|
||||
"Cleaning Versions": "Cleaning Versions",
|
||||
"Cleanup Interval": "Cleanup Interval",
|
||||
"Click to see discovery failures": "Натиснете, за да видите грешки при откриването",
|
||||
"Click to see full identification string and QR code.": "Click to see full identification string and QR code.",
|
||||
"Close": "Затвори",
|
||||
"Click to see full identification string and QR code.": "Щракнете, за да видите целия идентификатор и код за QR.",
|
||||
"Close": "Затваряне",
|
||||
"Command": "Команда",
|
||||
"Comment, when used at the start of a line": "Коментар, използван в началото на реда",
|
||||
"Compression": "Компресиране",
|
||||
@@ -55,14 +55,14 @@
|
||||
"Connection Error": "Грешка при свързването",
|
||||
"Connection Type": "Вид връзка",
|
||||
"Connections": "Връзки",
|
||||
"Continuously watching for changes is now available within Syncthing. This will detect changes on disk and issue a scan on only the modified paths. The benefits are that changes are propagated quicker and that less full scans are required.": "Постоянния мониторинг за промени вече е част от Syncthing. При промени по файловете се стартира сканиране само за променените папки. Ползите са, че промените биват синхронизирани по-бързо, без да се изисква цялостно сканирания на папките.",
|
||||
"Continuously watching for changes is now available within Syncthing. This will detect changes on disk and issue a scan on only the modified paths. The benefits are that changes are propagated quicker and that less full scans are required.": "Syncthing вече разполага с постоянно наблюдение за промени. Така се забелязват промените на дисковото устройство и се обхождат само променените папки. Ползите са, че промените се разпространяват по-бързо и с по-малко на брой пълни обхождания.",
|
||||
"Copied from elsewhere": "Копиране от някъде другаде",
|
||||
"Copied from original": "Копиран от оригинала",
|
||||
"Copyright © 2014-2019 the following Contributors:": "Copyright © 2014-2019 the following Contributors:",
|
||||
"Creating ignore patterns, overwriting an existing file at {%path%}.": "Създаване на шаблони за игнориране, презаписване на съществуващ файл в {{path}}.",
|
||||
"Currently Shared With Devices": "Currently Shared With Devices",
|
||||
"Creating ignore patterns, overwriting an existing file at {%path%}.": "При създаване на шаблони за отхвърляне, съществуващият файл „{{path}}“ ще бъде презаписан.",
|
||||
"Currently Shared With Devices": "Устройства, с които е споделена",
|
||||
"Danger!": "Опасност!",
|
||||
"Debugging Facilities": "Дебъг функционалност",
|
||||
"Debugging Facilities": "Debugging Facilities",
|
||||
"Default Configuration": "Default Configuration",
|
||||
"Default Device": "Default Device",
|
||||
"Default Folder": "Default Folder",
|
||||
@@ -71,15 +71,15 @@
|
||||
"Delete Unexpected Items": "Delete Unexpected Items",
|
||||
"Deleted": "Изтрито",
|
||||
"Deselect All": "Никое",
|
||||
"Deselect devices to stop sharing this folder with.": "Deselect devices to stop sharing this folder with.",
|
||||
"Deselect devices to stop sharing this folder with.": "Махнете отметката от устройство, за да спрете споделяне с него.",
|
||||
"Deselect folders to stop sharing with this device.": "Deselect folders to stop sharing with this device.",
|
||||
"Device": "Устройство",
|
||||
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Устройство \"{{name}}\" ({{device}}) с адрес {{address}} желае да се свърже. Да бъде ли добавено?",
|
||||
"Device ID": "Идентификатор на устройство",
|
||||
"Device Identification": "Идентификатор на устройството",
|
||||
"Device Identification": "Идентификатор на устройство",
|
||||
"Device Name": "Име на устройството",
|
||||
"Device is untrusted, enter encryption password": "Device is untrusted, enter encryption password",
|
||||
"Device rate limits": "Device rate limits",
|
||||
"Device rate limits": "Ограничаване на скоростта",
|
||||
"Device that last modified the item": "Устройство, което последно промени обекта",
|
||||
"Devices": "Устройства",
|
||||
"Disable Crash Reporting": "Disable Crash Reporting",
|
||||
@@ -87,7 +87,7 @@
|
||||
"Disabled periodic scanning and disabled watching for changes": "Периодичните сканирания и наблюденията за промяна са деактивирани.",
|
||||
"Disabled periodic scanning and enabled watching for changes": "Периодичните сканирания са деактивирани , а наблюденията за промяна са активирани.",
|
||||
"Disabled periodic scanning and failed setting up watching for changes, retrying every 1m:": "Периодичните сканирания са деактивирани и задаването на наблюдение за промени е неуспешно, ще опита пак след 1мин:",
|
||||
"Disables comparing and syncing file permissions. Useful on systems with nonexistent or custom permissions (e.g. FAT, exFAT, Synology, Android).": "Disables comparing and syncing file permissions. Useful on systems with nonexistent or custom permissions (e.g. FAT, exFAT, Synology, Android).",
|
||||
"Disables comparing and syncing file permissions. Useful on systems with nonexistent or custom permissions (e.g. FAT, exFAT, Synology, Android).": "Изключва сравняването и синхронизацията не правата на файловете. Полезно за системи с липсващи или специфични права (като FAT, exFAT, Synology, Android).",
|
||||
"Discard": "Discard",
|
||||
"Disconnected": "Не е свързано",
|
||||
"Disconnected (Unused)": "Disconnected (Unused)",
|
||||
@@ -96,18 +96,19 @@
|
||||
"Discovery Failures": "Грешка в откриването",
|
||||
"Dismiss": "Dismiss",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Do not add it to the ignore list, so this notification may recur.",
|
||||
"Do not restore": "Не възстановявай",
|
||||
"Do not restore all": "Не възстановявай всички",
|
||||
"Do not add it to the ignore list, so this notification may recurr.": "Do not add it to the ignore list, so this notification may recurr.",
|
||||
"Do not restore": "Без възстановяване",
|
||||
"Do not restore all": "Без възстановяване всички",
|
||||
"Do you want to enable watching for changes for all your folders?": "Желаете ли да активирате наблюдението за промени на всички папки?",
|
||||
"Documentation": "Документация",
|
||||
"Download Rate": "Скорост на сваляне",
|
||||
"Downloaded": "Изтеглен",
|
||||
"Downloading": "Изтегляне",
|
||||
"Edit": "Промени",
|
||||
"Edit": "Редактиране",
|
||||
"Edit Device": "Промяна на устройството",
|
||||
"Edit Device Defaults": "Edit Device Defaults",
|
||||
"Edit Device Defaults": "Подразбирани настройки на устройство",
|
||||
"Edit Folder": "Промяна на папката",
|
||||
"Edit Folder Defaults": "Edit Folder Defaults",
|
||||
"Edit Folder Defaults": "Подразбирани настройки на папка",
|
||||
"Editing {%path%}.": "Променяне на {{path}}.",
|
||||
"Enable Crash Reporting": "Enable Crash Reporting",
|
||||
"Enable NAT traversal": "Разреши NAT traversal",
|
||||
@@ -119,9 +120,9 @@
|
||||
"Enter ignore patterns, one per line.": "Добавете шаблони за игнориране, по един на ред.",
|
||||
"Enter up to three octal digits.": "Enter up to three octal digits.",
|
||||
"Error": "Грешка",
|
||||
"External File Versioning": "Външно управление на версиите",
|
||||
"External File Versioning": "Външно управление на версии",
|
||||
"Failed Items": "Неуспешни",
|
||||
"Failed to setup, retrying": "Неуспешно конфигуриране, правенe на повторен опит",
|
||||
"Failed to setup, retrying": "Грешка при настройване, извършва се повторен опит",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Неуспешна връзка към IPv6 сървъри може да се очаква ако няма IPv6 свързаност.",
|
||||
"File Pull Order": "Ред на сваляне",
|
||||
"File Versioning": "Версии на файловете",
|
||||
@@ -136,17 +137,17 @@
|
||||
"Folder ID": "Идентификатор на папката",
|
||||
"Folder Label": "Име на папката",
|
||||
"Folder Path": "Път до папката",
|
||||
"Folder Type": "Вид папка",
|
||||
"Folder type \"{%receiveEncrypted%}\" can only be set when adding a new folder.": "Folder type \"{{receiveEncrypted}}\" can only be set when adding a new folder.",
|
||||
"Folder Type": "Вид на папката",
|
||||
"Folder type \"{%receiveEncrypted%}\" can only be set when adding a new folder.": "Вида „{{receiveEncrypted}}“ може да бъде избран само при добавяне на папка.",
|
||||
"Folder type \"{%receiveEncrypted%}\" cannot be changed after adding the folder. You need to remove the folder, delete or decrypt the data on disk, and add the folder again.": "Folder type \"{{receiveEncrypted}}\" cannot be changed after adding the folder. You need to remove the folder, delete or decrypt the data on disk, and add the folder again.",
|
||||
"Folders": "Папки",
|
||||
"For the following folders an error occurred while starting to watch for changes. It will be retried every minute, so the errors might go away soon. If they persist, try to fix the underlying issue and ask for help if you can't.": "For the following folders an error occurred while starting to watch for changes. It will be retried every minute, so the errors might go away soon. If they persist, try to fix the underlying issue and ask for help if you can't.",
|
||||
"Full Rescan Interval (s)": "Интервал(и) за периодичното сканиране",
|
||||
"Full Rescan Interval (s)": "Интервал на пълно обхождане (секунди)",
|
||||
"GUI": "Потребителски интерфейс",
|
||||
"GUI Authentication Password": "Парола за интерфейса",
|
||||
"GUI Authentication User": "Потребител за интерфейса",
|
||||
"GUI Authentication: Set User and Password": "GUI Authentication: Set User and Password",
|
||||
"GUI Listen Address": "Адрес на слушане на GUI-то",
|
||||
"GUI Listen Address": "Адрес на слушане",
|
||||
"GUI Theme": "Тема за потребителския интерфейс",
|
||||
"General": "Общи",
|
||||
"Generate": "Генерирай",
|
||||
@@ -154,21 +155,21 @@
|
||||
"Global Discovery Servers": "Сървъри за глобално откриване",
|
||||
"Global State": "Глобално състояние",
|
||||
"Help": "Помощ",
|
||||
"Home page": "Начална страница",
|
||||
"Home page": "Страница",
|
||||
"However, your current settings indicate you might not want it enabled. We have disabled automatic crash reporting for you.": "However, your current settings indicate you might not want it enabled. We have disabled automatic crash reporting for you.",
|
||||
"Identification": "Identification",
|
||||
"If untrusted, enter encryption password": "If untrusted, enter encryption password",
|
||||
"Identification": "Идентификация",
|
||||
"If untrusted, enter encryption password": "Ако е недоверено въведете парола за шифроване",
|
||||
"If you want to prevent other users on this computer from accessing Syncthing and through it your files, consider setting up authentication.": "If you want to prevent other users on this computer from accessing Syncthing and through it your files, consider setting up authentication.",
|
||||
"Ignore": "Игнорирай",
|
||||
"Ignore Patterns": "Шаблони за игнориране",
|
||||
"Ignore Permissions": "Игнорирай правата за достъп",
|
||||
"Ignore Permissions": "Незачитане на права",
|
||||
"Ignored Devices": "Игнорирани устройства",
|
||||
"Ignored Folders": "Игнорирани папки",
|
||||
"Ignored at": "Ignored at",
|
||||
"Incoming Rate Limit (KiB/s)": "Лимит на скоростта за сваляне (KiB/s)",
|
||||
"Incoming Rate Limit (KiB/s)": "При изтегляне (KiB/s)",
|
||||
"Incorrect configuration may damage your folder contents and render Syncthing inoperable.": "Неправилни настройки могат да повредят файлове и да попречат на синхронизирането.",
|
||||
"Introduced By": "Предложено от",
|
||||
"Introducer": "Може да предлага други устройства",
|
||||
"Introducer": "Поръчител",
|
||||
"Inversion of the given condition (i.e. do not exclude)": "Обратното на даденото условие (пр. не изключвай)",
|
||||
"Keep Versions": "Пази версии",
|
||||
"LDAP": "LDAP",
|
||||
@@ -177,18 +178,18 @@
|
||||
"Last seen": "Последно видяно",
|
||||
"Latest Change": "Последна промяна",
|
||||
"Learn more": "Научете повече",
|
||||
"Limit": "Limit",
|
||||
"Listeners": "Синхронизиращи устройства",
|
||||
"Limit": "Ограничение",
|
||||
"Listeners": "Listeners",
|
||||
"Loading data...": "Зареждане на информация...",
|
||||
"Loading...": "Зареждане...",
|
||||
"Local Additions": "Local Additions",
|
||||
"Local Discovery": "Локално откриване",
|
||||
"Local State": "Локално състояние",
|
||||
"Local State (Total)": "Локално състояние (общо)",
|
||||
"Local Discovery": "Local Discovery",
|
||||
"Local State": "Local State",
|
||||
"Local State (Total)": "Local State (Total)",
|
||||
"Locally Changed Items": "Locally Changed Items",
|
||||
"Log": "Доклад",
|
||||
"Log tailing paused. Scroll to the bottom to continue.": "Log tailing paused. Scroll to the bottom to continue.",
|
||||
"Logs": "Доклади",
|
||||
"Log": "Дневник",
|
||||
"Log tailing paused. Scroll to the bottom to continue.": "Добавяне на редове към дневника е спряно. Плъзнете най-долу за да продължи.",
|
||||
"Logs": "Дневници",
|
||||
"Major Upgrade": "Основно Обновяване",
|
||||
"Mass actions": "Действия за всички",
|
||||
"Maximum Age": "Максимална възраст",
|
||||
@@ -215,49 +216,49 @@
|
||||
"Options": "Настройки",
|
||||
"Out of Sync": "Несинхронизирано",
|
||||
"Out of Sync Items": "Несинхронизирани елементи",
|
||||
"Outgoing Rate Limit (KiB/s)": "Лимит на скорост за качване (KiB/s)",
|
||||
"Outgoing Rate Limit (KiB/s)": "При качване (KiB/s)",
|
||||
"Override Changes": "Наложи локалните промени",
|
||||
"Path": "Път",
|
||||
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Път до папката на това устройство. Ако не съществува ще бъде създадена. Символът тилда (~) може да бъде използван като заместител на",
|
||||
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "Къде да бъдат създавани, автоматично приети папки, както и предложението за път, при добавяне на нови папки от потребителският интерфейс. Символът тилда (~) ще бъде заменян с {{tilde}}.",
|
||||
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Пътят, където версиите да бъдат складирани (оставете празно за папката .stversions).",
|
||||
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "Папка, в която да бъдат съхранявани версиите (оставете празно за подразбираната директория .stversions в споделената папка).",
|
||||
"Pause": "Пауза",
|
||||
"Pause All": "Пауза на всички",
|
||||
"Paused": "На пауза",
|
||||
"Paused (Unused)": "Paused (Unused)",
|
||||
"Pending changes": "Pending changes",
|
||||
"Periodic scanning at given interval and disabled watching for changes": "Периодично сканиране, през определен интервал, без мониторинг за промени",
|
||||
"Periodic scanning at given interval and enabled watching for changes": "Периодично сканиране, през определен интервал, и мониторинг за промени",
|
||||
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Периодично сканиране, през определен интервал, мониторинга за промени не може да стартира. Всяка минута минута се прави опит за стартиране:",
|
||||
"Periodic scanning at given interval and disabled watching for changes": "Пълно бхождане през определен интервал, без наблюдение за промени",
|
||||
"Periodic scanning at given interval and enabled watching for changes": "Пълно бхождане през определен интервал и наблюдение за промени",
|
||||
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:",
|
||||
"Permanently add it to the ignore list, suppressing further notifications.": "Permanently add it to the ignore list, suppressing further notifications.",
|
||||
"Permissions": "Права за достъп",
|
||||
"Please consult the release notes before performing a major upgrade.": "Моля прочети бележките по обновяването преди да започнеш.",
|
||||
"Please set a GUI Authentication User and Password in the Settings dialog.": "Моля задайте потребителско име и парола за потребителския интерфейс в секцията Настройки.",
|
||||
"Permissions": "Права",
|
||||
"Please consult the release notes before performing a major upgrade.": "Please consult the release notes before performing a major upgrade.",
|
||||
"Please set a GUI Authentication User and Password in the Settings dialog.": "Задайте потребителско име и парола за потребителския интерфейс в настройките.",
|
||||
"Please wait": "Моля изчакайте",
|
||||
"Prefix indicating that the file can be deleted if preventing directory removal": "Представка, която индикира, че файлът може да бъде изтрит ако пречи на премахването на папка",
|
||||
"Prefix indicating that the pattern should be matched without case sensitivity": "Представка, която индикира, че шаблона няма да прави разлика между главни/малки букви",
|
||||
"Prefix indicating that the file can be deleted if preventing directory removal": "Представка, указваща че файлът може да бъде изтрит ако пречи на премахването на папка",
|
||||
"Prefix indicating that the pattern should be matched without case sensitivity": "Представка, указваща че шаблонът не прави разлика в регистъра на буквите",
|
||||
"Preparing to Sync": "Preparing to Sync",
|
||||
"Preview": "Преглед",
|
||||
"Preview Usage Report": "Преглед на статистиката",
|
||||
"Quick guide to supported patterns": "Бърз наръчник към поддържаните шаблони",
|
||||
"Preview Usage Report": "Преглед на отчета за употребата",
|
||||
"Quick guide to supported patterns": "Кратък наръчник на поддържаните шаблони",
|
||||
"Random": "Произволен",
|
||||
"Receive Encrypted": "Receive Encrypted",
|
||||
"Receive Only": "Само получаване",
|
||||
"Received data is already encrypted": "Received data is already encrypted",
|
||||
"Recent Changes": "Последни промени",
|
||||
"Reduced by ignore patterns": "Намалено посредством шаблон за игнориране",
|
||||
"Release Notes": "Бележки по обновяването",
|
||||
"Reduced by ignore patterns": "Намалено посредством шаблон за отхвърляне",
|
||||
"Release Notes": "Бележки по изданието",
|
||||
"Release candidates contain the latest features and fixes. They are similar to the traditional bi-weekly Syncthing releases.": "Кандидат версиите съдържат най-новата функционалност и поправки. Те са близки до традиционните дву-седмични Synchthing обновления.",
|
||||
"Remote Devices": "Устройства",
|
||||
"Remote GUI": "Remote GUI",
|
||||
"Remove": "Премахни",
|
||||
"Remote Devices": "Отдалачени устройства",
|
||||
"Remote GUI": "Отдалечен ГПИ",
|
||||
"Remove": "Премахване",
|
||||
"Remove Device": "Премахване на устройство",
|
||||
"Remove Folder": "Премахване на папка",
|
||||
"Required identifier for the folder. Must be the same on all cluster devices.": "Задължителен идентификатор за папката. Трябва да бъде един и същ на всяко устройство.",
|
||||
"Required identifier for the folder. Must be the same on all cluster devices.": "Задължителен идентификатор на папката. Трябва е еднакъв на всички устройства.",
|
||||
"Rescan": "Сканирай",
|
||||
"Rescan All": "Сканирай всички",
|
||||
"Rescans": "Повторни сканирания",
|
||||
"Restart": "Рестартирай",
|
||||
"Restart": "Рестартиране",
|
||||
"Restart Needed": "Изисква се рестартиране",
|
||||
"Restarting": "Рестартиране",
|
||||
"Restore": "Възстановяване",
|
||||
@@ -266,35 +267,35 @@
|
||||
"Resume All": "Пусни всички",
|
||||
"Reused": "Повторно използван",
|
||||
"Revert Local Changes": "Revert Local Changes",
|
||||
"Save": "Запази",
|
||||
"Scan Time Remaining": "Оставащо време за сканиране",
|
||||
"Scanning": "Сканиране",
|
||||
"See external versioning help for supported templated command line parameters.": "Прегледайте външната документацията за поддържаните командни параметри. ",
|
||||
"Save": "Запазване",
|
||||
"Scan Time Remaining": "Оставащо време до обхождане",
|
||||
"Scanning": "Обхождане",
|
||||
"See external versioning help for supported templated command line parameters.": "Прочетете ръководството за външно управление на версии, за да се запознаете с шаблонните параметри.",
|
||||
"Select All": "Всички",
|
||||
"Select a version": "Изберете версия",
|
||||
"Select additional devices to share this folder with.": "Select additional devices to share this folder with.",
|
||||
"Select additional folders to share with this device.": "Select additional folders to share with this device.",
|
||||
"Select additional devices to share this folder with.": "Изберете други устройства, с които да споделите с папката.",
|
||||
"Select additional folders to share with this device.": "Изберете други папки, които да споделите с устройството.",
|
||||
"Select latest version": "Избор на най-новата версия",
|
||||
"Select oldest version": "Избор на най-старата версия",
|
||||
"Select the folders to share with this device.": "Изберете папките за споделяне с това устройство.",
|
||||
"Select the folders to share with this device.": "Изберете папките, които да споделите с устройството.",
|
||||
"Send & Receive": "Изпращане и получаване",
|
||||
"Send Only": "Само изпращане",
|
||||
"Settings": "Настройки",
|
||||
"Share": "Сподели",
|
||||
"Share Folder": "Сподели папка",
|
||||
"Share": "Споделяне",
|
||||
"Share Folder": "Споделяне на папка",
|
||||
"Share Folders With Device": "Споделяне на папки с устройството",
|
||||
"Share this folder?": "Сподели тази папка?",
|
||||
"Shared Folders": "Shared Folders",
|
||||
"Shared Folders": "Споделени папки",
|
||||
"Shared With": "Споделена с",
|
||||
"Sharing": "Споделяне",
|
||||
"Show ID": "Покажи идентификатора",
|
||||
"Show QR": "Покажи QR",
|
||||
"Show QR": "Преглед на QR",
|
||||
"Show detailed discovery status": "Show detailed discovery status",
|
||||
"Show detailed listener status": "Show detailed listener status",
|
||||
"Show diff with previous version": "Показване на разликите спрямо предната версия",
|
||||
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Показва се вместо идентификатора на устройството в статуса на клъстъра. Ще се ползва за представяне пред останалите устройства.",
|
||||
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Показва се вместо идентификатора на устройството в статуса на клъстъра. Ще бъде попълнено с името, с което се е представило устройството, ако оставите полето празно.",
|
||||
"Shutdown": "Спри програмата",
|
||||
"Show diff with previous version": "Показване на разликите с предходната версия",
|
||||
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.",
|
||||
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.",
|
||||
"Shutdown": "Изключване",
|
||||
"Shutdown Complete": "Спирането завършено",
|
||||
"Simple File Versioning": "Опростени версии",
|
||||
"Single level wildcard (matches within a directory only)": "Маска на едно ниво (покрива само в папка)",
|
||||
@@ -304,14 +305,14 @@
|
||||
"Some items could not be restored:": "Някои елементи не могат да бъдат възстановени:",
|
||||
"Some listening addresses could not be enabled to accept connections:": "Some listening addresses could not be enabled to accept connections:",
|
||||
"Source Code": "Сорс код",
|
||||
"Stable releases and release candidates": "Стабилни версии и кандидати за стабилни версии",
|
||||
"Stable releases are delayed by about two weeks. During this time they go through testing as release candidates.": "Стабилните версии са забавени с две седмици. През това време те преминават през тестване като бъдат кандидат версии.",
|
||||
"Stable releases and release candidates": "Стабилни и предварителни издания",
|
||||
"Stable releases are delayed by about two weeks. During this time they go through testing as release candidates.": "Стабилните издания биват забавяни с две седмици. През този период те преминават през изпитване като предварителни издания.",
|
||||
"Stable releases only": "Само стабилни версии",
|
||||
"Staggered File Versioning": "Наслагващи се версии",
|
||||
"Start Browser": "Стартирай браузъра",
|
||||
"Statistics": "Статистика",
|
||||
"Stopped": "Не се синхронизира",
|
||||
"Stores and syncs only encrypted data. Folders on all connected devices need to be set up with the same password or be of type \"{%receiveEncrypted%}\" too.": "Stores and syncs only encrypted data. Folders on all connected devices need to be set up with the same password or be of type \"{{receiveEncrypted}}\" too.",
|
||||
"Stores and syncs only encrypted data. Folders on all connected devices need to be set up with the same password or be of type \"{%receiveEncrypted%}\" too.": "Съхранява и синхронизира само шифровани данни. Папките на всички свързани устройства трябва да бъдат настроени със същата парола или също да са от вида „{{receiveEncrypted}}“.",
|
||||
"Support": "Помощ",
|
||||
"Support Bundle": "Support Bundle",
|
||||
"Sync Protocol Listen Addresses": "Адрес за слушане на синхронизиращия протокол",
|
||||
@@ -324,18 +325,18 @@
|
||||
"Syncthing is restarting.": "Syncthing се рестартира",
|
||||
"Syncthing is upgrading.": "Syncthing се обновява.",
|
||||
"Syncthing now supports automatically reporting crashes to the developers. This feature is enabled by default.": "Syncthing now supports automatically reporting crashes to the developers. This feature is enabled by default.",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Изглежда, че Syncthing не е включен, или има проблем с връзката с Интернет. Повторен опит...",
|
||||
"Syncthing seems to be down, or there is a problem with your Internet connection. Retrying…": "Изглежда, че Syncthing не работи или няма достъп до интернет. Извършва се повторен опит…",
|
||||
"Syncthing seems to be experiencing a problem processing your request. Please refresh the page or restart Syncthing if the problem persists.": "Syncthing има проблем при обработването на заявката. Моля, презаредете браузъра или рестартирайте Syncthing ако проблемът продължи.",
|
||||
"Take me back": "Take me back",
|
||||
"The GUI address is overridden by startup options. Changes here will not take effect while the override is in place.": "The GUI address is overridden by startup options. Changes here will not take effect while the override is in place.",
|
||||
"The Syncthing Authors": "The Syncthing Authors",
|
||||
"The Syncthing admin interface is configured to allow remote access without a password.": "Администраторският панел на Syncthing разрешава дистанционен достъп без да изисква парола.",
|
||||
"The aggregated statistics are publicly available at the URL below.": "Обобщение на събраните статистически ще намерите на долния URL адрес.",
|
||||
"The aggregated statistics are publicly available at the URL below.": "Обобщение на събраните статистически данни ще намерите на адреса по-долу.",
|
||||
"The cleanup interval cannot be blank.": "The cleanup interval cannot be blank.",
|
||||
"The configuration has been saved but not activated. Syncthing must restart to activate the new configuration.": "Конфигурацията е запазена, но не е активирана. Syncthing трябва да рестартира, за да се активира новата конфигурация.",
|
||||
"The device ID cannot be blank.": "Полето идентификатор на устройство не може да бъде празно.",
|
||||
"The device ID to enter here can be found in the \"Actions > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "Идентификатор на устройство за въвеждане тук, може да бъде намерен в \"Промени > Покажи идентификатора\" на другото устройство. Интервалите и тиретата са пожелание (биват прескачани).",
|
||||
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Криптиран статистически доклад ще се изпраща ежедневно. Ползва се, за отичане на ползваните платформи, размер на папки и версии на приложението. При промяна в събираните данни, ще бъдете информирани от подобен на този прозорец.",
|
||||
"The device ID to enter here can be found in the \"Actions > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).": "The device ID to enter here can be found in the \"Actions > Show ID\" dialog on the other device. Spaces and dashes are optional (ignored).",
|
||||
"The encrypted usage report is sent daily. It is used to track common platforms, folder sizes and app versions. If the reported data set is changed you will be prompted with this dialog again.": "Шифрованият отчет за употреба се изпраща ежедневно. Използваме го за отчитане на най-често срещаните платформи, размери на папки и издания на приложението. При промяна в събираните данни отново ще бъде поискано вашето съгласие.",
|
||||
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "Въведеният идентификатор на устройство не е валиден. Трябва да бъде 52 или 56 символа и да се състои от букви и цифри, като интервалите и тиретата са пожелание.",
|
||||
"The folder ID cannot be blank.": "Полето идентификатор на папка не може да бъде празно.",
|
||||
"The folder ID must be unique.": "Идентификаторът на папката трябва да бъде уникален.",
|
||||
@@ -357,11 +358,11 @@
|
||||
"The number of versions must be a number and cannot be blank.": "Броят версии трябва да бъде число и не може да бъде празно.",
|
||||
"The path cannot be blank.": "Пътят не може да бъде празен.",
|
||||
"The rate limit must be a non-negative number (0: no limit)": "Ограничението на скоростта трябва да бъде положително число (0: неограничено)",
|
||||
"The rescan interval must be a non-negative number of seconds.": "Интервала на сканиране трябва да бъде не отрицателно число в секунди.",
|
||||
"The rescan interval must be a non-negative number of seconds.": "Интервалът на обхождане трябва да е неотрицателен брой на секунди.",
|
||||
"There are no devices to share this folder with.": "There are no devices to share this folder with.",
|
||||
"There are no folders to share with this device.": "There are no folders to share with this device.",
|
||||
"They are retried automatically and will be synced when the error is resolved.": "Ще бъдат спрени и автоматично синхронизирани, когато грешката бъде оправена.",
|
||||
"This Device": "Вашето устройство",
|
||||
"This Device": "Това устройство",
|
||||
"This can easily give hackers access to read and change any files on your computer.": "Така се предоставя изключително лесен достъп (четене, редактиране и изтриване) до всеки файл, на компютъра Ви.",
|
||||
"This device cannot automatically discover other devices or announce its own address to be found by others. Only devices with statically configured addresses can connect.": "This device cannot automatically discover other devices or announce its own address to be found by others. Only devices with statically configured addresses can connect.",
|
||||
"This is a major version upgrade.": "Това е нова основна версия.",
|
||||
@@ -379,18 +380,18 @@
|
||||
"Unignore": "Unignore",
|
||||
"Unknown": "Неясно",
|
||||
"Unshared": "Несподелена",
|
||||
"Unshared Devices": "Unshared Devices",
|
||||
"Unshared Folders": "Unshared Folders",
|
||||
"Untrusted": "Untrusted",
|
||||
"Unshared Devices": "Устройства, с които не е споделено",
|
||||
"Unshared Folders": "Несподелени папки",
|
||||
"Untrusted": "Недоверено",
|
||||
"Up to Date": "Синхронизирано",
|
||||
"Updated": "Обновено",
|
||||
"Upgrade": "Обнови",
|
||||
"Upgrade To {%version%}": "Обновен до {{version}}",
|
||||
"Upgrade": "Обновяване",
|
||||
"Upgrade To {%version%}": "Обновено до {{version}}",
|
||||
"Upgrading": "Обновяване",
|
||||
"Upload Rate": "Скорост на качване",
|
||||
"Uptime": "Работи от",
|
||||
"Usage reporting is always enabled for candidate releases.": "Докладът за ползването е винаги включен за кандидат нови версии.",
|
||||
"Use HTTPS for GUI": "Използвай HTTPS за потребителския интерфейс",
|
||||
"Usage reporting is always enabled for candidate releases.": "Отчитането на употребата винаги е включено за предварителните издания.",
|
||||
"Use HTTPS for GUI": "Потребителският интерфейс работи под HTTPS",
|
||||
"Username/Password has not been set for the GUI authentication. Please consider setting it up.": "Username/Password has not been set for the GUI authentication. Please consider setting it up.",
|
||||
"Version": "Версия",
|
||||
"Versions": "Версии",
|
||||
@@ -404,12 +405,12 @@
|
||||
"Warning, this path is a parent directory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Предупреждение, този път е по-горна директория на съществуващата папка \"{{otherFolderLabel}}\" ({{otherFolder}}).",
|
||||
"Warning, this path is a subdirectory of an existing folder \"{%otherFolder%}\".": "Внимание, това е вътрешна папка на вече съществуваща папка \"{{otherFolder}}\".",
|
||||
"Warning, this path is a subdirectory of an existing folder \"{%otherFolderLabel%}\" ({%otherFolder%}).": "Предупреждение, този път е под-директория на съществуващата папка \"{{otherFolderLabel}}\" ({{otherFolder}}).",
|
||||
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Предупреждение: Ако използвате външна програма за наблюдение като {{syncthingInotify}}, трябва да я деактивирате.",
|
||||
"Watch for Changes": "Мониторинг за промени",
|
||||
"Watching for Changes": "Мониторинг за промени",
|
||||
"Warning: If you are using an external watcher like {%syncthingInotify%}, you should make sure it is deactivated.": "Предупреждение: Ако използвате външно приложение за наблюдение като {{syncthingInotify}}, трябва да го спрете.",
|
||||
"Watch for Changes": "Watch for Changes",
|
||||
"Watching for Changes": "Watching for Changes",
|
||||
"Watching for changes discovers most changes without periodic scanning.": "Watching for changes discovers most changes without periodic scanning.",
|
||||
"When adding a new device, keep in mind that this device must be added on the other side too.": "Когато добавяте ново устройство имайте предвид, че това устройство също трябва да бъде добавено от другата страна.",
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Когато добавяте нов идентификатор на папка имайте предвид, че той се използва за свързване на папките между отделните устройства. Идентификатора разграничава главни/малки букви.",
|
||||
"When adding a new folder, keep in mind that the Folder ID is used to tie folders together between devices. They are case sensitive and must match exactly between all devices.": "Като добавяте папката имайте предвид, че той се използва за еднозначно указване на папката между устройствата. Има разлика в регистъра на знаците и трябва изцяло да съвпада между всички устройства.",
|
||||
"Yes": "Да",
|
||||
"You can also select one of these nearby devices:": "Също така може да изберете едно от устройствата, които намират се наблизо:",
|
||||
"You can change your choice at any time in the Settings dialog.": "Може да промените решението си по всяко време в прозореца Настройки.",
|
||||
@@ -417,15 +418,15 @@
|
||||
"You have no ignored devices.": "Няма игнорирани устройства.",
|
||||
"You have no ignored folders.": "Няма игнорирани папки.",
|
||||
"You have unsaved changes. Do you really want to discard them?": "Има незапазени промени. Наистина ли желаете да ги отмените?",
|
||||
"You must keep at least one version.": "Трябва да пазиш поне една версия.",
|
||||
"You must keep at least one version.": "You must keep at least one version.",
|
||||
"You should never add or change anything locally in a \"{%receiveEncrypted%}\" folder.": "You should never add or change anything locally in a \"{{receiveEncrypted}}\" folder.",
|
||||
"days": "дни",
|
||||
"directories": "директории",
|
||||
"files": "файла",
|
||||
"full documentation": "пълна документация",
|
||||
"items": "елемента",
|
||||
"seconds": "seconds",
|
||||
"seconds": "секунди",
|
||||
"{%device%} wants to share folder \"{%folder%}\".": "{{device}} желае да сподели папката \"{{folder}}\".",
|
||||
"{%device%} wants to share folder \"{%folderlabel%}\" ({%folder%}).": "{{device}} желае да сподели папката \"{{folderlabel}}\" ({{folder}}).",
|
||||
"{%reintroducer%} might reintroduce this device.": "{{reintroducer}} might reintroduce this device."
|
||||
"{%reintroducer%} might reintroduce this device.": "Поръчителят {{reintroducer}} може отново да предложи това устройство."
|
||||
}
|
||||
@@ -96,6 +96,7 @@
|
||||
"Discovery Failures": "Fallades al Descobriment",
|
||||
"Dismiss": "Dismiss",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Do not add it to the ignore list, so this notification may recur.",
|
||||
"Do not add it to the ignore list, so this notification may recurr.": "Do not add it to the ignore list, so this notification may recurr.",
|
||||
"Do not restore": "No restaurar",
|
||||
"Do not restore all": "No restaurar en absolut",
|
||||
"Do you want to enable watching for changes for all your folders?": "Vols activar el rastreig continu de canvis per a totes les carpetes?",
|
||||
|
||||
@@ -95,7 +95,8 @@
|
||||
"Discovery": "Objevování",
|
||||
"Discovery Failures": "Nezdary při objevování",
|
||||
"Dismiss": "OK",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Do not add it to the ignore list, so this notification may recur.",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Nepřidávat k ignorování, takže oznámení se může opakovat.",
|
||||
"Do not add it to the ignore list, so this notification may recurr.": "Nepřidávat k ignorování, takže oznámení se může opakovat.",
|
||||
"Do not restore": "Neobnovit",
|
||||
"Do not restore all": "Neobnovit nic",
|
||||
"Do you want to enable watching for changes for all your folders?": "Chcete zapnout sledování změn pro všechny složky?",
|
||||
|
||||
@@ -95,7 +95,8 @@
|
||||
"Discovery": "Opslag",
|
||||
"Discovery Failures": "Fejl ved opdagelse",
|
||||
"Dismiss": "Forlade",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Do not add it to the ignore list, so this notification may recur.",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Tilføj ikke til ignore liste, så påmindelsen kan gentage.",
|
||||
"Do not add it to the ignore list, so this notification may recurr.": "Tilføj ikke til ignore liste, så påmindelse kan gentage.",
|
||||
"Do not restore": "Genskab ikke",
|
||||
"Do not restore all": "Genskab ikke alle",
|
||||
"Do you want to enable watching for changes for all your folders?": "Vil du aktivere løbende overvågning af ændringer for alle dine mapper?",
|
||||
@@ -229,7 +230,7 @@
|
||||
"Periodic scanning at given interval and disabled watching for changes": "Periodisk skanning med et givent interval og deaktiveret overvågning af ændringer",
|
||||
"Periodic scanning at given interval and enabled watching for changes": "Periodisk skanning med et givent interval og aktiveret overvågning af ændringer",
|
||||
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Periodisk skanning med et givent interval og lykkedes ikke med at opsætte overvågning af ændringer; prøver igen hvert minut:",
|
||||
"Permanently add it to the ignore list, suppressing further notifications.": "Permanently add it to the ignore list, suppressing further notifications.",
|
||||
"Permanently add it to the ignore list, suppressing further notifications.": "Permanent tilføj til ignore liste, undertrykker yderligere påmindelse.",
|
||||
"Permissions": "Tilladelser",
|
||||
"Please consult the release notes before performing a major upgrade.": "Tjek venligst udgivelsesnoterne før opgradering til en ny hovedversion.",
|
||||
"Please set a GUI Authentication User and Password in the Settings dialog.": "Opret venligst en GUI-bruger og -adgangskode i opsætningen.",
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"Advanced": "Erweitert",
|
||||
"Advanced Configuration": "Erweiterte Konfiguration",
|
||||
"All Data": "Alle Daten",
|
||||
"All folders shared with this device must be protected by a password, such that all sent data is unreadable without the given password.": "Alle Ordner, welche mit diesem Gerät geteilt werden müssen von einem Passwort geschützt werden, sodass die gesendeten Daten ohne Kenntnis des Passworts nicht gelesen werden können. ",
|
||||
"All folders shared with this device must be protected by a password, such that all sent data is unreadable without the given password.": "Alle Ordner, welche mit diesem Gerät geteilt werden, müssen von einem Passwort geschützt werden, so dass die gesendeten Daten ohne Kenntnis des Passworts nicht gelesen werden können. ",
|
||||
"Allow Anonymous Usage Reporting?": "Übertragung von anonymen Nutzungsberichten erlauben?",
|
||||
"Allowed Networks": "Erlaubte Netzwerke",
|
||||
"Alphabetic": "Alphabetisch",
|
||||
@@ -63,16 +63,16 @@
|
||||
"Currently Shared With Devices": "Derzeit mit Geräten geteilt",
|
||||
"Danger!": "Achtung!",
|
||||
"Debugging Facilities": "Debugging-Möglichkeiten",
|
||||
"Default Configuration": "Standardkonfiguration",
|
||||
"Default Device": "Standardgerät",
|
||||
"Default Folder": "Standardordner",
|
||||
"Default Configuration": "Vorgabekonfiguration",
|
||||
"Default Device": "Vorgabegerät",
|
||||
"Default Folder": "Vorgabeordner",
|
||||
"Default Folder Path": "Standardmäßiger Ordnerpfad",
|
||||
"Defaults": "Vorgaben",
|
||||
"Delete Unexpected Items": "Unerwartete Elemente löschen",
|
||||
"Deleted": "Gelöscht",
|
||||
"Deselect All": "Alle abwählen",
|
||||
"Deselect devices to stop sharing this folder with.": "Geräte abwählen, um diesen Ordner nicht mehr dafür freizugeben.",
|
||||
"Deselect folders to stop sharing with this device.": "Ordner abwählen, um sie nicht mehr für dieses Gerät freizugeben.",
|
||||
"Deselect devices to stop sharing this folder with.": "Geräte abwählen, um diesen Ordner nicht mehr damit zu teilen.",
|
||||
"Deselect folders to stop sharing with this device.": "Ordner abwählen, um sie nicht mehr für mit diesem Gerät zu teilen.",
|
||||
"Device": "Gerät",
|
||||
"Device \"{%name%}\" ({%device%} at {%address%}) wants to connect. Add new device?": "Gerät \"{{name}}\" ({{device}} {{address}}) möchte sich verbinden. Gerät hinzufügen?",
|
||||
"Device ID": "Gerätekennung",
|
||||
@@ -95,7 +95,8 @@
|
||||
"Discovery": "Gerätesuche",
|
||||
"Discovery Failures": "Gerätesuchfehler",
|
||||
"Dismiss": "Ausblenden",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Do not add it to the ignore list, so this notification may recur.",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Nicht zur Ignorierliste hinzufügen, diese Benachrichtigung kann erneut auftauchen.",
|
||||
"Do not add it to the ignore list, so this notification may recurr.": "Nicht zur Ignorierliste hinzufügen, diese Benachrichtigung kann erneut auftauchen.",
|
||||
"Do not restore": "Nicht wiederherstellen",
|
||||
"Do not restore all": "Nicht alle wiederherstellen",
|
||||
"Do you want to enable watching for changes for all your folders?": "Möchten Sie das nach Änderungen für alle Ihre Ordner gesucht wird aktivieren?",
|
||||
@@ -272,8 +273,8 @@
|
||||
"See external versioning help for supported templated command line parameters.": "Siehe Hilfe zur externen Versionierung für unterstützte Befehlszeilenparameter.",
|
||||
"Select All": "Alle auswählen",
|
||||
"Select a version": "Wählen Sie eine Version",
|
||||
"Select additional devices to share this folder with.": "Weitere Geräte auswählen, um diesen Ordner dafür freizugegeben.",
|
||||
"Select additional folders to share with this device.": "Weitere Ordner auswählen, um sie für dieses Gerät freizugeben.",
|
||||
"Select additional devices to share this folder with.": "Weitere Geräte auswählen, um diesen Ordner damit zu teilen.",
|
||||
"Select additional folders to share with this device.": "Weitere Ordner auswählen, um mit diesem Gerät zu teilen.",
|
||||
"Select latest version": "Letzte Version auswählen",
|
||||
"Select oldest version": "Älteste Version auswählen",
|
||||
"Select the folders to share with this device.": "Wähle Sie die Ordner aus, die Sie mit diesem Gerät teilen möchten.",
|
||||
|
||||
@@ -96,6 +96,7 @@
|
||||
"Discovery Failures": "Αποτυχίες ανεύρεσης συσκευών",
|
||||
"Dismiss": "Dismiss",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Do not add it to the ignore list, so this notification may recur.",
|
||||
"Do not add it to the ignore list, so this notification may recurr.": "Do not add it to the ignore list, so this notification may recurr.",
|
||||
"Do not restore": "Να μη γίνει επαναφορά",
|
||||
"Do not restore all": "Να μη γίνει επαναφορά όλων",
|
||||
"Do you want to enable watching for changes for all your folders?": "Επιθυμείτε να ενεργοποιήσετε την επιτήρηση για όλους τους φακέλους σας;",
|
||||
|
||||
@@ -96,6 +96,7 @@
|
||||
"Discovery Failures": "Discovery Failures",
|
||||
"Dismiss": "Dismiss",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Do not add it to the ignore list, so this notification may recur.",
|
||||
"Do not add it to the ignore list, so this notification may recurr.": "Do not add it to the ignore list, so this notification may recurr.",
|
||||
"Do not restore": "Do not restore",
|
||||
"Do not restore all": "Do not restore all",
|
||||
"Do you want to enable watching for changes for all your folders?": "Do you want to enable watching for changes for all your folders?",
|
||||
|
||||
@@ -96,6 +96,7 @@
|
||||
"Discovery Failures": "Discovery Failures",
|
||||
"Dismiss": "Dismiss",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Do not add it to the ignore list, so this notification may recur.",
|
||||
"Do not add it to the ignore list, so this notification may recurr.": "Do not add it to the ignore list, so this notification may recurr.",
|
||||
"Do not restore": "Do not restore",
|
||||
"Do not restore all": "Do not restore all",
|
||||
"Do you want to enable watching for changes for all your folders?": "Do you want to enable watching for changes for all your folders?",
|
||||
|
||||
@@ -94,9 +94,9 @@
|
||||
"Discovered": "Discovered",
|
||||
"Discovery": "Discovery",
|
||||
"Discovery Failures": "Discovery Failures",
|
||||
"Discovery Status": "Discovery Status",
|
||||
"Dismiss": "Dismiss",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Do not add it to the ignore list, so this notification may recur.",
|
||||
"Do not add it to the ignore list, so this notification may recurr.": "Do not add it to the ignore list, so this notification may recurr.",
|
||||
"Do not restore": "Do not restore",
|
||||
"Do not restore all": "Do not restore all",
|
||||
"Do you want to enable watching for changes for all your folders?": "Do you want to enable watching for changes for all your folders?",
|
||||
@@ -122,6 +122,7 @@
|
||||
"Error": "Error",
|
||||
"External File Versioning": "External File Versioning",
|
||||
"Failed Items": "Failed Items",
|
||||
"Failed to load ignore patterns.": "Failed to load ignore patterns.",
|
||||
"Failed to setup, retrying": "Failed to setup, retrying",
|
||||
"Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.": "Failure to connect to IPv6 servers is expected if there is no IPv6 connectivity.",
|
||||
"File Pull Order": "File Pull Order",
|
||||
@@ -179,6 +180,8 @@
|
||||
"Latest Change": "Latest Change",
|
||||
"Learn more": "Learn more",
|
||||
"Limit": "Limit",
|
||||
"Listener Failures": "Listener Failures",
|
||||
"Listener Status": "Listener Status",
|
||||
"Listeners": "Listeners",
|
||||
"Loading data...": "Loading data...",
|
||||
"Loading...": "Loading...",
|
||||
@@ -217,6 +220,7 @@
|
||||
"Out of Sync": "Out of Sync",
|
||||
"Out of Sync Items": "Out of Sync Items",
|
||||
"Outgoing Rate Limit (KiB/s)": "Outgoing Rate Limit (KiB/s)",
|
||||
"Override": "Override",
|
||||
"Override Changes": "Override Changes",
|
||||
"Path": "Path",
|
||||
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for",
|
||||
@@ -266,6 +270,7 @@
|
||||
"Resume": "Resume",
|
||||
"Resume All": "Resume All",
|
||||
"Reused": "Reused",
|
||||
"Revert": "Revert",
|
||||
"Revert Local Changes": "Revert Local Changes",
|
||||
"Save": "Save",
|
||||
"Scan Time Remaining": "Scan Time Remaining",
|
||||
@@ -392,6 +397,7 @@
|
||||
"Uptime": "Uptime",
|
||||
"Usage reporting is always enabled for candidate releases.": "Usage reporting is always enabled for candidate releases.",
|
||||
"Use HTTPS for GUI": "Use HTTPS for GUI",
|
||||
"Use notifications from the filesystem to detect changed items.": "Use notifications from the filesystem to detect changed items.",
|
||||
"Username/Password has not been set for the GUI authentication. Please consider setting it up.": "Username/Password has not been set for the GUI authentication. Please consider setting it up.",
|
||||
"Version": "Version",
|
||||
"Versions": "Versions",
|
||||
|
||||
@@ -96,6 +96,7 @@
|
||||
"Discovery Failures": "Malsukcesoj de Malkovro",
|
||||
"Dismiss": "Dismiss",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Do not add it to the ignore list, so this notification may recur.",
|
||||
"Do not add it to the ignore list, so this notification may recurr.": "Do not add it to the ignore list, so this notification may recurr.",
|
||||
"Do not restore": "Ne restarigu",
|
||||
"Do not restore all": "Ne restarigu ĉion",
|
||||
"Do you want to enable watching for changes for all your folders?": "Ĉu vi volas ebligi rigardado je ŝanĝoj por ĉiuj viaj dosierujoj?",
|
||||
|
||||
@@ -96,6 +96,7 @@
|
||||
"Discovery Failures": "Fallos de Descubrimiento",
|
||||
"Dismiss": "Dismiss",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Do not add it to the ignore list, so this notification may recur.",
|
||||
"Do not add it to the ignore list, so this notification may recurr.": "Do not add it to the ignore list, so this notification may recurr.",
|
||||
"Do not restore": "No restaurar",
|
||||
"Do not restore all": "No restaurar todo",
|
||||
"Do you want to enable watching for changes for all your folders?": "Quieres activar la vigilancia de cambios para todas tus carpetas?",
|
||||
|
||||
@@ -96,6 +96,7 @@
|
||||
"Discovery Failures": "Fallos de Descubrimiento",
|
||||
"Dismiss": "Dismiss",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Do not add it to the ignore list, so this notification may recur.",
|
||||
"Do not add it to the ignore list, so this notification may recurr.": "Do not add it to the ignore list, so this notification may recurr.",
|
||||
"Do not restore": "No restaurar",
|
||||
"Do not restore all": "No restaurar todos",
|
||||
"Do you want to enable watching for changes for all your folders?": "¿Deseas activar el control de cambios en todas tus carpetas?",
|
||||
@@ -194,8 +195,8 @@
|
||||
"Maximum Age": "Edad máxima",
|
||||
"Metadata Only": "Sólo metadatos",
|
||||
"Minimum Free Disk Space": "Espacio mínimo libre en disco",
|
||||
"Mod. Device": "Editar dispositivo",
|
||||
"Mod. Time": "Editar Hora",
|
||||
"Mod. Device": "Dispositivo mod.",
|
||||
"Mod. Time": "Hora mod.",
|
||||
"Move to top of queue": "Mover al principio de la cola",
|
||||
"Multi level wildcard (matches multiple directory levels)": "Comodín multinivel (coincide con múltiples niveles de directorio)",
|
||||
"Never": "Nunca",
|
||||
@@ -217,7 +218,7 @@
|
||||
"Out of Sync Items": "Elementos no sincronizados",
|
||||
"Outgoing Rate Limit (KiB/s)": "Límite de subida (KiB/s)",
|
||||
"Override Changes": "Anular cambios",
|
||||
"Path": "Parche",
|
||||
"Path": "Ruta",
|
||||
"Path to the folder on the local computer. Will be created if it does not exist. The tilde character (~) can be used as a shortcut for": "Ruta a la carpeta en la máquina local. Se creará si no existe. El carácter de la tilde (~) puede usarse como atajo.",
|
||||
"Path where new auto accepted folders will be created, as well as the default suggested path when adding new folders via the UI. Tilde character (~) expands to {%tilde%}.": "La ruta donde las nuevas carpetas automáticamente aceptadas será creada, así como la ruta por defecto sugerida al agregar nuevas carpetas mediante la UI. La letra (~) se expande a {{tilde}}.",
|
||||
"Path where versions should be stored (leave empty for the default .stversions directory in the shared folder).": "La ruta donde las versiones deben ser almacenadas (dejar vacío para el directorio .stversions por defecto en la carpeta compartida).",
|
||||
|
||||
@@ -96,6 +96,7 @@
|
||||
"Discovery Failures": "Agertze hutsegiteak",
|
||||
"Dismiss": "Dismiss",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Do not add it to the ignore list, so this notification may recur.",
|
||||
"Do not add it to the ignore list, so this notification may recurr.": "Do not add it to the ignore list, so this notification may recurr.",
|
||||
"Do not restore": "Ez berrezarri",
|
||||
"Do not restore all": "Ez berrezarri denak",
|
||||
"Do you want to enable watching for changes for all your folders?": "Zure karpeta guztietan aldaketen kontrola aktibatu nahi duzu?",
|
||||
|
||||
@@ -96,6 +96,7 @@
|
||||
"Discovery Failures": "Etsinnässä tapahtuneet virheet",
|
||||
"Dismiss": "Dismiss",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Do not add it to the ignore list, so this notification may recur.",
|
||||
"Do not add it to the ignore list, so this notification may recurr.": "Do not add it to the ignore list, so this notification may recurr.",
|
||||
"Do not restore": "Älä palauta",
|
||||
"Do not restore all": "Älä palauta kaikkia",
|
||||
"Do you want to enable watching for changes for all your folders?": "Haluatko aktivoida jatkuvan muutoksien seurannan kaikkiin kansioihin?",
|
||||
|
||||
@@ -63,7 +63,7 @@
|
||||
"Currently Shared With Devices": "Appareils membres actuels de ce partage :",
|
||||
"Danger!": "Attention !",
|
||||
"Debugging Facilities": "Outils de débogage",
|
||||
"Default Configuration": "Personnalisation des créations (non rétroactif)",
|
||||
"Default Configuration": "Préférences pour les créations (non rétroactif)",
|
||||
"Default Device": "Nouveaux appareils",
|
||||
"Default Folder": "Nouveaux partages",
|
||||
"Default Folder Path": "Chemin parent par défaut pour les nouveaux partages",
|
||||
@@ -95,7 +95,8 @@
|
||||
"Discovery": "Découverte",
|
||||
"Discovery Failures": "Échecs de découverte",
|
||||
"Dismiss": "Écarter",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Do not add it to the ignore list, so this notification may recur.",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Ne pas l'ajouter à la liste à ignorer, pour que cette notification puisse réapparaître.",
|
||||
"Do not add it to the ignore list, so this notification may recurr.": "Ne pas l'ajouter à la liste à ignorer, pour que cette notification puisse réapparaître.",
|
||||
"Do not restore": "Ne pas restaurer",
|
||||
"Do not restore all": "Ne pas tout restaurer",
|
||||
"Do you want to enable watching for changes for all your folders?": "Voulez-vous activer la surveillance des changements sur tous vos partages ?",
|
||||
|
||||
@@ -96,6 +96,7 @@
|
||||
"Discovery Failures": "Untdekkingsflaters",
|
||||
"Dismiss": "Dismiss",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Do not add it to the ignore list, so this notification may recur.",
|
||||
"Do not add it to the ignore list, so this notification may recurr.": "Do not add it to the ignore list, so this notification may recurr.",
|
||||
"Do not restore": "Net tebeksette",
|
||||
"Do not restore all": "Hielendal net tebeksette",
|
||||
"Do you want to enable watching for changes for all your folders?": "Wolle jo it konstant byhâlden fan feroarings foar al jo mappen oansette?",
|
||||
|
||||
@@ -96,6 +96,7 @@
|
||||
"Discovery Failures": "Felfedezési hibák",
|
||||
"Dismiss": "Elutasítás",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Ne vegye fel a mellőzési listára, így ez az értesítés megismétlődhet. ",
|
||||
"Do not add it to the ignore list, so this notification may recurr.": "Ne vegye fel a mellőzési listára, így ez az értesítés megismétlődhet. ",
|
||||
"Do not restore": "Ne legyen visszaállítva",
|
||||
"Do not restore all": "Semmit se állítson vissza",
|
||||
"Do you want to enable watching for changes for all your folders?": "Minden mappára bekapcsolható a változásfigyelés?",
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
"Cleaning Versions": "Pulizia Versioni",
|
||||
"Cleanup Interval": "Intervallo di Pulizia",
|
||||
"Click to see discovery failures": "Clicca per vedere gli errori di rilevamento",
|
||||
"Click to see full identification string and QR code.": "Click to see full identification string and QR code.",
|
||||
"Click to see full identification string and QR code.": "Fare clic per visualizzare la stringa di identificazione completa e il codice QR.",
|
||||
"Close": "Chiudi",
|
||||
"Command": "Comando",
|
||||
"Comment, when used at the start of a line": "Per commentare, va inserito all'inizio di una riga",
|
||||
@@ -95,7 +95,8 @@
|
||||
"Discovery": "Individuazione",
|
||||
"Discovery Failures": "Individuazione fallita",
|
||||
"Dismiss": "Dismiss",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Do not add it to the ignore list, so this notification may recur.",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Non aggiungerlo all'elenco da ignorare, quindi questa notifica potrebbe ripresentarsi.",
|
||||
"Do not add it to the ignore list, so this notification may recurr.": "Do not add it to the ignore list, so this notification may recurr.",
|
||||
"Do not restore": "Non ripristinare",
|
||||
"Do not restore all": "Non ripristinare tutto",
|
||||
"Do you want to enable watching for changes for all your folders?": "Vuoi abilitare il monitoraggio delle modifiche per tutte le tue cartelle?",
|
||||
@@ -137,7 +138,7 @@
|
||||
"Folder Label": "Etichetta per la Cartella",
|
||||
"Folder Path": "Percorso Cartella",
|
||||
"Folder Type": "Tipo di Cartella",
|
||||
"Folder type \"{%receiveEncrypted%}\" can only be set when adding a new folder.": "Folder type \"{{receiveEncrypted}}\" can only be set when adding a new folder.",
|
||||
"Folder type \"{%receiveEncrypted%}\" can only be set when adding a new folder.": "Il tipo di cartella \"{{receiveEncrypted}}\" può essere impostata solo quando si aggiunge una nuova cartella.",
|
||||
"Folder type \"{%receiveEncrypted%}\" cannot be changed after adding the folder. You need to remove the folder, delete or decrypt the data on disk, and add the folder again.": "Il tipo di cartella \"{{receiveEncrypted}}\" non può essere modificato dopo aver aggiunto la cartella. È necessario rimuovere la cartella, eliminare o decrittografare i dati sul disco e aggiungere nuovamente la cartella.",
|
||||
"Folders": "Cartelle",
|
||||
"For the following folders an error occurred while starting to watch for changes. It will be retried every minute, so the errors might go away soon. If they persist, try to fix the underlying issue and ask for help if you can't.": "Per le seguenti cartelle si è verificato un errore durante l'avvio della ricerca delle modifiche. Sarà ripetuto ogni minuto, quindi gli errori potrebbero risolversi presto. Se persistono, prova a risolvere il problema sottostante e chiedi aiuto se non puoi.",
|
||||
@@ -156,7 +157,7 @@
|
||||
"Help": "Aiuto",
|
||||
"Home page": "Pagina home",
|
||||
"However, your current settings indicate you might not want it enabled. We have disabled automatic crash reporting for you.": "Tuttavia, le impostazioni correnti indicano che potresti non volerla attiva. Abbiamo disattivato la segnalazione automatica degli arresti anomali per te.",
|
||||
"Identification": "Identification",
|
||||
"Identification": "Identificazione",
|
||||
"If untrusted, enter encryption password": "Se non attendibile, immettere la password di crittografia",
|
||||
"If you want to prevent other users on this computer from accessing Syncthing and through it your files, consider setting up authentication.": "Se vuoi impedire ad altri utenti di questo computer di accedere a Syncthing e attraverso di esso ai tuoi file, prendi in considerazione la configurazione dell'autenticazione.",
|
||||
"Ignore": "Ignora",
|
||||
@@ -229,7 +230,7 @@
|
||||
"Periodic scanning at given interval and disabled watching for changes": "Scansione periodica a intervalli determinati e monitoraggio cambiamenti disabilitata",
|
||||
"Periodic scanning at given interval and enabled watching for changes": "Scansione periodica a intervalli determinati e monitoraggio cambiamenti abilitata",
|
||||
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Scansione periodica a intervalli determinati e configurazione fallita del monitoraggio cambiamenti, nuovo tentativo ogni 1m:",
|
||||
"Permanently add it to the ignore list, suppressing further notifications.": "Permanently add it to the ignore list, suppressing further notifications.",
|
||||
"Permanently add it to the ignore list, suppressing further notifications.": "Aggiungi in modo permanente all'elenco da ignorare, sopprimendo ulteriori notifiche.",
|
||||
"Permissions": "Permessi",
|
||||
"Please consult the release notes before performing a major upgrade.": "Si prega di consultare le note di rilascio prima di eseguire un aggiornamento principale.",
|
||||
"Please set a GUI Authentication User and Password in the Settings dialog.": "Per favore impostare Utente e Password dell'Interfaccia Grafica nelle Impostazioni.",
|
||||
@@ -289,8 +290,8 @@
|
||||
"Sharing": "Condivisione",
|
||||
"Show ID": "Mostra ID",
|
||||
"Show QR": "Mostra QR",
|
||||
"Show detailed discovery status": "Show detailed discovery status",
|
||||
"Show detailed listener status": "Show detailed listener status",
|
||||
"Show detailed discovery status": "Mostra stato dettagliato dell'individuazione",
|
||||
"Show detailed listener status": "Mostra lo stato dettagliato di ascolto",
|
||||
"Show diff with previous version": "Mostra le differenze con la versione precedente",
|
||||
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Visibile al posto dell'ID Dispositivo nello stato del cluster. Negli altri dispositivi verrà presentato come nome predefinito opzionale.",
|
||||
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Visibile al posto dell'ID Dispositivo nello stato del cluster. Se viene lasciato vuoto, verrà utilizzato il nome proposto dal dispositivo.",
|
||||
@@ -302,7 +303,7 @@
|
||||
"Smallest First": "Prima il più piccolo",
|
||||
"Some discovery methods could not be established for finding other devices or announcing this device:": "Some discovery methods could not be established for finding other devices or announcing this device:",
|
||||
"Some items could not be restored:": "Alcuni elementi non possono essere ripristinati:",
|
||||
"Some listening addresses could not be enabled to accept connections:": "Some listening addresses could not be enabled to accept connections:",
|
||||
"Some listening addresses could not be enabled to accept connections:": "Alcuni indirizzi di ascolto non possono essere abilitati per accettare connessioni:",
|
||||
"Source Code": "Codice Sorgente",
|
||||
"Stable releases and release candidates": "Versioni stabili e versioni candidate al rilascio",
|
||||
"Stable releases are delayed by about two weeks. During this time they go through testing as release candidates.": "Le versioni stabili sono in ritardo di circa due settimane. Durante questo tempo verranno testati come candidati di rilascio.",
|
||||
@@ -314,13 +315,13 @@
|
||||
"Stores and syncs only encrypted data. Folders on all connected devices need to be set up with the same password or be of type \"{%receiveEncrypted%}\" too.": "Memorizza e sincronizza solo i dati crittografati. Le cartelle su tutti i dispositivi collegati devono essere configurate con la stessa password o essere del tipo \"{{receiveEncrypted}}\".",
|
||||
"Support": "Supporto",
|
||||
"Support Bundle": "Pacchetto di supporto",
|
||||
"Sync Protocol Listen Addresses": "Indirizzi del Protocollo di Sincronizzazione",
|
||||
"Sync Protocol Listen Addresses": "Indirizzi di ascolto del Protocollo di Sincronizzazione",
|
||||
"Syncing": "Sincronizzazione in corso",
|
||||
"Syncthing has been shut down.": "Syncthing è stato arrestato.",
|
||||
"Syncthing includes the following software or portions thereof:": "Syncthing utilizza i seguenti software o porzioni di questi:",
|
||||
"Syncthing is Free and Open Source Software licensed as MPL v2.0.": "Syncthing è un software Libero e Open Source concesso in licenza MPL v2.0.",
|
||||
"Syncthing is listening on the following network addresses for connection attempts from other devices:": "Syncthing is listening on the following network addresses for connection attempts from other devices:",
|
||||
"Syncthing is not listening for connection attempts from other devices on any address. Only outgoing connections from this device may work.": "Syncthing is not listening for connection attempts from other devices on any address. Only outgoing connections from this device may work.",
|
||||
"Syncthing is listening on the following network addresses for connection attempts from other devices:": "Syncthing è in ascolto sui seguenti indirizzi di rete per i tentativi di connessione da altri dispositivi:",
|
||||
"Syncthing is not listening for connection attempts from other devices on any address. Only outgoing connections from this device may work.": "Syncthing non è in ascolto di tentativi di connessione da altri dispositivi su qualsiasi indirizzo. Possono funzionare solo le connessioni in uscita da questo dispositivo.",
|
||||
"Syncthing is restarting.": "Riavvio di Syncthing in corso.",
|
||||
"Syncthing is upgrading.": "Aggiornamento di Syncthing in corso.",
|
||||
"Syncthing now supports automatically reporting crashes to the developers. This feature is enabled by default.": "Syncthing ora supporta la segnalazione automaticamente agli sviluppatori degli arresti anomali. Questa funzione è abilitata per impostazione predefinita.",
|
||||
@@ -339,13 +340,13 @@
|
||||
"The entered device ID does not look valid. It should be a 52 or 56 character string consisting of letters and numbers, with spaces and dashes being optional.": "L'ID del dispositivo inserito non sembra valido. Dovrebbe essere una stringa di 52 o 56 caratteri costituita da lettere e numeri, con spazi e trattini opzionali.",
|
||||
"The folder ID cannot be blank.": "L'ID della cartella non può essere vuoto.",
|
||||
"The folder ID must be unique.": "L'ID della cartella dev'essere unico.",
|
||||
"The folder content on other devices will be overwritten to become identical with this device. Files not present here will be deleted on other devices.": "The folder content on other devices will be overwritten to become identical with this device. Files not present here will be deleted on other devices.",
|
||||
"The folder content on this device will be overwritten to become identical with other devices. Files newly added here will be deleted.": "The folder content on this device will be overwritten to become identical with other devices. Files newly added here will be deleted.",
|
||||
"The folder content on other devices will be overwritten to become identical with this device. Files not present here will be deleted on other devices.": "Il contenuto della cartella su altri dispositivi verrà sovrascritto per diventare identico a questo dispositivo. I file non presenti qui verranno eliminati su altri dispositivi.",
|
||||
"The folder content on this device will be overwritten to become identical with other devices. Files newly added here will be deleted.": "Il contenuto della cartella su questo dispositivo verrà sovrascritto per diventare identico ad altri dispositivi. I file appena aggiunti qui verranno eliminati.",
|
||||
"The folder path cannot be blank.": "Il percorso della cartella non può essere vuoto.",
|
||||
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "Vengono utilizzati i seguenti intervalli temporali: per la prima ora viene mantenuta una versione ogni 30 secondi, per il primo giorno viene mantenuta una versione ogni ora, per i primi 30 giorni viene mantenuta una versione al giorno, successivamente viene mantenuta una versione ogni settimana fino al periodo massimo impostato.",
|
||||
"The following items could not be synchronized.": "Non è stato possibile sincronizzare i seguenti elementi.",
|
||||
"The following items were changed locally.": "I seguenti elementi sono stati modificati localmente.",
|
||||
"The following methods are used to discover other devices on the network and announce this device to be found by others:": "The following methods are used to discover other devices on the network and announce this device to be found by others:",
|
||||
"The following methods are used to discover other devices on the network and announce this device to be found by others:": "I seguenti metodi vengono utilizzati per rilevare altri dispositivi sulla rete e annunciare questo dispositivo per essere trovato da altri:",
|
||||
"The following unexpected items were found.": "Sono stati trovati i seguenti elementi imprevisti.",
|
||||
"The interval must be a positive number of seconds.": "L'intervallo deve essere un numero positivo di secondi.",
|
||||
"The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.": "L'intervallo, in secondi, per l'esecuzione della pulizia nella directory delle versioni. Zero per disabilitare la pulizia periodica.",
|
||||
@@ -363,7 +364,7 @@
|
||||
"They are retried automatically and will be synced when the error is resolved.": "Verranno effettuati tentativi in automatico e verranno sincronizzati quando l'errore sarà risolto.",
|
||||
"This Device": "Questo Dispositivo",
|
||||
"This can easily give hackers access to read and change any files on your computer.": "Ciò potrebbe facilmente permettere agli hackers accesso alla lettura e modifica di qualunque file del tuo computer.",
|
||||
"This device cannot automatically discover other devices or announce its own address to be found by others. Only devices with statically configured addresses can connect.": "This device cannot automatically discover other devices or announce its own address to be found by others. Only devices with statically configured addresses can connect.",
|
||||
"This device cannot automatically discover other devices or announce its own address to be found by others. Only devices with statically configured addresses can connect.": "Questo dispositivo non può rilevare automaticamente altri dispositivi o annunciare il proprio indirizzo per essere trovato da altri. Possono connettersi solo i dispositivi con indirizzi configurati staticamente.",
|
||||
"This is a major version upgrade.": "Questo è un aggiornamento di versione principale",
|
||||
"This setting controls the free space required on the home (i.e., index database) disk.": "Questa impostazione controlla lo spazio libero richiesto sul disco home (cioè, database di indice).",
|
||||
"Time": "Tempo",
|
||||
|
||||
@@ -96,6 +96,7 @@
|
||||
"Discovery Failures": "探索サーバーへの接続失敗",
|
||||
"Dismiss": "Dismiss",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Do not add it to the ignore list, so this notification may recur.",
|
||||
"Do not add it to the ignore list, so this notification may recurr.": "Do not add it to the ignore list, so this notification may recurr.",
|
||||
"Do not restore": "Do not restore",
|
||||
"Do not restore all": "Do not restore all",
|
||||
"Do you want to enable watching for changes for all your folders?": "Do you want to enable watching for changes for all your folders?",
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
"Cleaning Versions": "버전 정리 중",
|
||||
"Cleanup Interval": "정리 간격",
|
||||
"Click to see discovery failures": "탐색 실패 보기",
|
||||
"Click to see full identification string and QR code.": "Click to see full identification string and QR code.",
|
||||
"Click to see full identification string and QR code.": "클릭해서 기기 식별자와 QR 코드 보기.",
|
||||
"Close": "닫기",
|
||||
"Command": "커맨드",
|
||||
"Comment, when used at the start of a line": "명령행에서 시작을 할수 있어요.",
|
||||
@@ -96,6 +96,7 @@
|
||||
"Discovery Failures": "탐색 실패",
|
||||
"Dismiss": "Dismiss",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Do not add it to the ignore list, so this notification may recur.",
|
||||
"Do not add it to the ignore list, so this notification may recurr.": "Do not add it to the ignore list, so this notification may recurr.",
|
||||
"Do not restore": "복구 하지 않기",
|
||||
"Do not restore all": "모두 복구 하지 않기",
|
||||
"Do you want to enable watching for changes for all your folders?": "변경 사항 감시를 당신의 모든 폴더에서 활성화 하는걸 원하시나요?",
|
||||
@@ -156,7 +157,7 @@
|
||||
"Help": "도움말",
|
||||
"Home page": "홈페이지",
|
||||
"However, your current settings indicate you might not want it enabled. We have disabled automatic crash reporting for you.": "However, your current settings indicate you might not want it enabled. We have disabled automatic crash reporting for you.",
|
||||
"Identification": "Identification",
|
||||
"Identification": "식별자",
|
||||
"If untrusted, enter encryption password": "신뢰하지 않는 경우 암호화 비밀번호를 입력하세요",
|
||||
"If you want to prevent other users on this computer from accessing Syncthing and through it your files, consider setting up authentication.": "If you want to prevent other users on this computer from accessing Syncthing and through it your files, consider setting up authentication.",
|
||||
"Ignore": "무시",
|
||||
|
||||
@@ -96,6 +96,7 @@
|
||||
"Discovery Failures": "Matomumo nesėkmės",
|
||||
"Dismiss": "Dismiss",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Do not add it to the ignore list, so this notification may recur.",
|
||||
"Do not add it to the ignore list, so this notification may recurr.": "Do not add it to the ignore list, so this notification may recurr.",
|
||||
"Do not restore": "Neatkurti",
|
||||
"Do not restore all": "Neatkurti visus",
|
||||
"Do you want to enable watching for changes for all your folders?": "Ar norite įjungti pakeitimų stebėjimą visiems savo aplankams?",
|
||||
|
||||
@@ -96,6 +96,7 @@
|
||||
"Discovery Failures": "Oppslagsfeil",
|
||||
"Dismiss": "Dismiss",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Do not add it to the ignore list, so this notification may recur.",
|
||||
"Do not add it to the ignore list, so this notification may recurr.": "Do not add it to the ignore list, so this notification may recurr.",
|
||||
"Do not restore": "Ikke gjenopprett",
|
||||
"Do not restore all": "Ikke gjenopprett alle",
|
||||
"Do you want to enable watching for changes for all your folders?": "Ønsker du å skru på oppsyn med endringer for alle dine mapper?",
|
||||
|
||||
@@ -96,6 +96,7 @@
|
||||
"Discovery Failures": "Detectiefouten",
|
||||
"Dismiss": "Verwerpen",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Niet toevoegen aan de negeerlijst zodat deze melding kan terugkomen.",
|
||||
"Do not add it to the ignore list, so this notification may recurr.": "Niet toevoegen aan de negeerlijst zodat deze melding kan terugkomen.",
|
||||
"Do not restore": "Niet herstellen",
|
||||
"Do not restore all": "Niet alles herstellen",
|
||||
"Do you want to enable watching for changes for all your folders?": "Wilt u het opvolgen van wijzigingen voor al uw mappen inschakelen?",
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
"Cleaning Versions": "Czyszczenie wersji",
|
||||
"Cleanup Interval": "Przedział czasowy czyszczenia",
|
||||
"Click to see discovery failures": "Kliknij tutaj, aby zobaczyć błędy odnajdywania",
|
||||
"Click to see full identification string and QR code.": "Kliknij, żeby zobaczyć pełny identyfikator oraz kod QR.",
|
||||
"Click to see full identification string and QR code.": "Kliknij tutaj, aby zobaczyć pełny identyfikator oraz kod QR.",
|
||||
"Close": "Zamknij",
|
||||
"Command": "Polecenie",
|
||||
"Comment, when used at the start of a line": "Komentarz, jeżeli znajduje się na początku linii",
|
||||
@@ -94,8 +94,9 @@
|
||||
"Discovered": "Odnaleziony",
|
||||
"Discovery": "Odnajdywanie",
|
||||
"Discovery Failures": "Błędy odnajdywania",
|
||||
"Dismiss": "Dismiss",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Do not add it to the ignore list, so this notification may recur.",
|
||||
"Dismiss": "Odrzuć",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Nie dodaje do listy ignorowanych, więc powiadomienie to może się powtórzyć.",
|
||||
"Do not add it to the ignore list, so this notification may recurr.": "Nie dodaje do listy ignorowanych, więc powiadomienie to może się powtórzyć.",
|
||||
"Do not restore": "Nie przywracaj",
|
||||
"Do not restore all": "Nie przywracaj wszystkich",
|
||||
"Do you want to enable watching for changes for all your folders?": "Czy chcesz włączyć obserwowanie zmian we wszystkich folderach?",
|
||||
@@ -156,7 +157,7 @@
|
||||
"Help": "Pomoc",
|
||||
"Home page": "Strona domowa",
|
||||
"However, your current settings indicate you might not want it enabled. We have disabled automatic crash reporting for you.": "Niemniej jednak, obecne ustawienia wskazują, że możesz nie chcieć włączać tej funkcji. Automatyczne zgłaszanie awarii zostało wyłączone na tym urządzeniu.",
|
||||
"Identification": "Identification",
|
||||
"Identification": "Identyfikator",
|
||||
"If untrusted, enter encryption password": "Jeżeli folder jest niezaufany, wprowadź szyfrujące hasło",
|
||||
"If you want to prevent other users on this computer from accessing Syncthing and through it your files, consider setting up authentication.": "Jeżeli chcesz zakazać innym użytkownikom tego komputera dostępu do Syncthing, a przez niego do swoich plików, zastanów się nad włączeniem uwierzytelniania.",
|
||||
"Ignore": "Ignoruj",
|
||||
@@ -229,7 +230,7 @@
|
||||
"Periodic scanning at given interval and disabled watching for changes": "Okresowe skanowanie w podanym przedziale czasowym i wyłączone obserwowanie zmian",
|
||||
"Periodic scanning at given interval and enabled watching for changes": "Okresowe skanowanie w podanym przedziale czasowym i włączone obserwowanie zmian",
|
||||
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Okresowe skanowanie w podanym przedziale czasowym i nieudane ustawienie obserwowania zmian, ponawiam co minutę:",
|
||||
"Permanently add it to the ignore list, suppressing further notifications.": "Permanently add it to the ignore list, suppressing further notifications.",
|
||||
"Permanently add it to the ignore list, suppressing further notifications.": "Dodaje na stałe od listy ignorowanych, wyciszając kolejne powiadomienia.",
|
||||
"Permissions": "Uprawnienia",
|
||||
"Please consult the release notes before performing a major upgrade.": "Zapoznaj się z informacjami o wersji przed przeprowadzeniem dużej aktualizacji.",
|
||||
"Please set a GUI Authentication User and Password in the Settings dialog.": "Ustaw użytkownika i hasło do uwierzytelniania GUI w oknie Ustawień.",
|
||||
@@ -289,8 +290,8 @@
|
||||
"Sharing": "Współdzielenie",
|
||||
"Show ID": "Pokaż ID",
|
||||
"Show QR": "Pokaż kod QR",
|
||||
"Show detailed discovery status": "Show detailed discovery status",
|
||||
"Show detailed listener status": "Show detailed listener status",
|
||||
"Show detailed discovery status": "Pokaż szczegółowy stan odnajdywania",
|
||||
"Show detailed listener status": "Pokaż szczegółowy stan nasłuchujących",
|
||||
"Show diff with previous version": "Pokaż różnice z poprzednią wersją",
|
||||
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Widoczna na liście urządzeń zamiast ID urządzenia. Zostanie wysłana do innych urządzeń jako opcjonalna nazwa domyślna.",
|
||||
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Widoczna na liście urządzeń zamiast ID urządzenia. Zostanie zaktualizowana do nazwy wysłanej przez urządzenie, jeżeli pozostanie pusta.",
|
||||
@@ -300,9 +301,9 @@
|
||||
"Single level wildcard (matches within a directory only)": "Wieloznacznik jednopoziomowy (wyszukuje tylko wewnątrz katalogu)",
|
||||
"Size": "Rozmiar",
|
||||
"Smallest First": "Najmniejsze na początku",
|
||||
"Some discovery methods could not be established for finding other devices or announcing this device:": "Some discovery methods could not be established for finding other devices or announcing this device:",
|
||||
"Some discovery methods could not be established for finding other devices or announcing this device:": "Nie udało się ustawić niektórych metod odnajdywania w celu szukania innych urządzeń oraz ogłaszania tego urządzenia:",
|
||||
"Some items could not be restored:": "Niektórych elementów nie udało się przywrócić:",
|
||||
"Some listening addresses could not be enabled to accept connections:": "Some listening addresses could not be enabled to accept connections:",
|
||||
"Some listening addresses could not be enabled to accept connections:": "Nie udało się włączyć niektórych adresów nasłuchujących w celu przyjmowania połączeń:",
|
||||
"Source Code": "Kod źródłowy",
|
||||
"Stable releases and release candidates": "Wydania stabilne i wydania kandydujące",
|
||||
"Stable releases are delayed by about two weeks. During this time they go through testing as release candidates.": "Wydania stabilne są opóźnione o ok. dwa tygodnie. W tym czasie są one testowane jako wydania kandydujące.",
|
||||
@@ -319,8 +320,8 @@
|
||||
"Syncthing has been shut down.": "Syncthing został wyłączony.",
|
||||
"Syncthing includes the following software or portions thereof:": "Syncthing zawiera następujące oprogramowanie lub ich części:",
|
||||
"Syncthing is Free and Open Source Software licensed as MPL v2.0.": "Syncthing to wolne i otwarte oprogramowanie na licencji MPL 2.0.",
|
||||
"Syncthing is listening on the following network addresses for connection attempts from other devices:": "Syncthing is listening on the following network addresses for connection attempts from other devices:",
|
||||
"Syncthing is not listening for connection attempts from other devices on any address. Only outgoing connections from this device may work.": "Syncthing is not listening for connection attempts from other devices on any address. Only outgoing connections from this device may work.",
|
||||
"Syncthing is listening on the following network addresses for connection attempts from other devices:": "Syncthing nasłuchuje prób połączeń z innych urządzeń pod następującymi adresami sieciowymi:",
|
||||
"Syncthing is not listening for connection attempts from other devices on any address. Only outgoing connections from this device may work.": "Syncthing nie nasłuchuje prób połączeń z innych urządzeń pod żadnym adresem. Tylko połączenia wychodzące z tego urządzenia są w stanie działać.",
|
||||
"Syncthing is restarting.": "Syncthing jest uruchamiany ponownie.",
|
||||
"Syncthing is upgrading.": "Syncthing jest aktualizowany.",
|
||||
"Syncthing now supports automatically reporting crashes to the developers. This feature is enabled by default.": "Syncthing zawiera teraz automatyczne zgłaszanie awarii do autorów. Ta funkcja jest domyślnie włączona.",
|
||||
@@ -345,7 +346,7 @@
|
||||
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "Używane są następujące przedziały czasowe: w pierwszej godzinie wersja zachowywana jest co 30 sekund, w pierwszym dniu co godzinę, w pierwszych 30 dniach codziennie, a do czasu osiągnięcia maksymalnego wieku co tydzień.",
|
||||
"The following items could not be synchronized.": "Następujące elementy nie mogły zostać zsynchronizowane.",
|
||||
"The following items were changed locally.": "Następujące elementy zostały zmienione lokalnie.",
|
||||
"The following methods are used to discover other devices on the network and announce this device to be found by others:": "The following methods are used to discover other devices on the network and announce this device to be found by others:",
|
||||
"The following methods are used to discover other devices on the network and announce this device to be found by others:": "Poniższe metody są używane do odnajdywania innych urządzeń w sieci oraz ogłaszania tego urządzenia, aby mogło ono zostać znalezione przez nie:",
|
||||
"The following unexpected items were found.": "Znaleziono następujące elementy nieoczekiwane.",
|
||||
"The interval must be a positive number of seconds.": "Przedział czasowy musi być dodatnią liczbą sekund.",
|
||||
"The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.": "Przedział czasowy, w sekundach, w którym nastąpi czyszczenie katalogu wersjonowania. Ustaw na zero, aby wyłączyć czyszczenie okresowe.",
|
||||
@@ -363,7 +364,7 @@
|
||||
"They are retried automatically and will be synced when the error is resolved.": "Ponowne próby zachodzą automatycznie. Synchronizacja nastąpi po usunięciu błędu.",
|
||||
"This Device": "To urządzenie",
|
||||
"This can easily give hackers access to read and change any files on your computer.": "Może to umożliwić hakerom dostęp do odczytu i zmian dowolnych plików na tym komputerze.",
|
||||
"This device cannot automatically discover other devices or announce its own address to be found by others. Only devices with statically configured addresses can connect.": "This device cannot automatically discover other devices or announce its own address to be found by others. Only devices with statically configured addresses can connect.",
|
||||
"This device cannot automatically discover other devices or announce its own address to be found by others. Only devices with statically configured addresses can connect.": "To urządzenie nie jest w stanie automatycznie odnajdować innych urządzeń oraz ogłaszać swojego adresu, aby mogło ono zostać znalezione przez nie. Tylko urządzenia z adresem ustawionym statycznie są w stanie się połączyć.",
|
||||
"This is a major version upgrade.": "To jest duża aktualizacja.",
|
||||
"This setting controls the free space required on the home (i.e., index database) disk.": "To ustawienie kontroluje ilość wolnej przestrzeni na dysku domowym (np. do indeksowania bazy danych).",
|
||||
"Time": "Czas",
|
||||
|
||||
@@ -96,6 +96,7 @@
|
||||
"Discovery Failures": "Falhas na descoberta",
|
||||
"Dismiss": "Descartar",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Não o adicione à lista de ignorados, portanto, esta notificação pode ocorrer novamente.",
|
||||
"Do not add it to the ignore list, so this notification may recurr.": "Não o adicione à lista de ignorados, portanto, esta notificação pode ocorrer novamente.",
|
||||
"Do not restore": "Não restaurar",
|
||||
"Do not restore all": "Não restaurar nenhum",
|
||||
"Do you want to enable watching for changes for all your folders?": "Você deseja ativar a observação de alterações em todas as suas pastas?",
|
||||
|
||||
@@ -94,8 +94,9 @@
|
||||
"Discovered": "Descoberto",
|
||||
"Discovery": "Pesquisa",
|
||||
"Discovery Failures": "Falhas da pesquisa",
|
||||
"Dismiss": "Dispensar",
|
||||
"Dismiss": "Descartar",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Não adicionar à lista dos ignorados, para que esta notificação volte a aparecer.",
|
||||
"Do not add it to the ignore list, so this notification may recurr.": "Não adicionar à lista dos ignorados, para que esta notificação volte a aparecer.",
|
||||
"Do not restore": "Não restaurar",
|
||||
"Do not restore all": "Não restaurar nenhum",
|
||||
"Do you want to enable watching for changes for all your folders?": "Quer activar a vigilância de alterações para todas as suas pastas?",
|
||||
@@ -229,7 +230,7 @@
|
||||
"Periodic scanning at given interval and disabled watching for changes": "Verificação periódica no intervalo dado e desactivada a vigilância de alterações",
|
||||
"Periodic scanning at given interval and enabled watching for changes": "Verificação periódica no intervalo dado e activada a vigilância de alterações",
|
||||
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Verificação periódica no intervalo dado e falha ao preparar a vigilância de alterações, tentando novamente a cada minuto:",
|
||||
"Permanently add it to the ignore list, suppressing further notifications.": "Adicionar permanentemente à lista de ignorados, eliminando novas notificações.",
|
||||
"Permanently add it to the ignore list, suppressing further notifications.": "Adicionar permanentemente à lista de ignorados, inibindo novas notificações.",
|
||||
"Permissions": "Permissões",
|
||||
"Please consult the release notes before performing a major upgrade.": "Consulte as notas de lançamento antes de fazer uma actualização importante.",
|
||||
"Please set a GUI Authentication User and Password in the Settings dialog.": "Por favor, defina um utilizador e senha de autenticação para a interface gráfica, nas configurações.",
|
||||
@@ -300,7 +301,7 @@
|
||||
"Single level wildcard (matches within a directory only)": "Símbolo polivalente de um só nível (faz corresponder apenas dentro de uma pasta)",
|
||||
"Size": "Tamanho",
|
||||
"Smallest First": "Primeiro os menores",
|
||||
"Some discovery methods could not be established for finding other devices or announcing this device:": "Alguns métodos de pesquisa não podem ser estabelecidos para encontrar outros dispositivos ou anunciar este dispositivo:",
|
||||
"Some discovery methods could not be established for finding other devices or announcing this device:": "Não foi possível estabelecer alguns dos métodos de pesquisa para encontrar outros dispositivos ou anunciar este dispositivo:",
|
||||
"Some items could not be restored:": "Não foi possível restaurar alguns dos itens:",
|
||||
"Some listening addresses could not be enabled to accept connections:": "Alguns endereços de auscultação não puderam ser activados para aceitar ligações:",
|
||||
"Source Code": "Código fonte",
|
||||
@@ -320,7 +321,7 @@
|
||||
"Syncthing includes the following software or portions thereof:": "O Syncthing inclui as seguintes aplicações ou partes delas:",
|
||||
"Syncthing is Free and Open Source Software licensed as MPL v2.0.": "Syncthing é Software Livre e de Código Aberto licenciado como MPL v2.0.",
|
||||
"Syncthing is listening on the following network addresses for connection attempts from other devices:": "O Syncthing está à escuta de tentativas de ligação por parte de outros dispositivos nos seguintes endereços de rede:",
|
||||
"Syncthing is not listening for connection attempts from other devices on any address. Only outgoing connections from this device may work.": "O Syncthing não está à escuta de tentativas de ligação por parte de outros dispositivos em nenhum endereço. Apenas poderão funcionar ligações de dentro para fora deste dispositivo.",
|
||||
"Syncthing is not listening for connection attempts from other devices on any address. Only outgoing connections from this device may work.": "O Syncthing não está à escuta de tentativas de ligação por parte de outros dispositivos em nenhum endereço. Apenas poderão funcionar ligações deste dispositivo para fora.",
|
||||
"Syncthing is restarting.": "O Syncthing está a reiniciar.",
|
||||
"Syncthing is upgrading.": "O Syncthing está a actualizar-se.",
|
||||
"Syncthing now supports automatically reporting crashes to the developers. This feature is enabled by default.": "O Syncthing agora suporta o envio automático de relatórios de estouro para os programadores. Esta funcionalidade vem inicialmente activada.",
|
||||
|
||||
@@ -96,6 +96,7 @@
|
||||
"Discovery Failures": "Discovery Failures",
|
||||
"Dismiss": "Dismiss",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Do not add it to the ignore list, so this notification may recur.",
|
||||
"Do not add it to the ignore list, so this notification may recurr.": "Do not add it to the ignore list, so this notification may recurr.",
|
||||
"Do not restore": "Do not restore",
|
||||
"Do not restore all": "Do not restore all",
|
||||
"Do you want to enable watching for changes for all your folders?": "Do you want to enable watching for changes for all your folders?",
|
||||
|
||||
@@ -96,6 +96,7 @@
|
||||
"Discovery Failures": "Ошибки обнаружения",
|
||||
"Dismiss": "Dismiss",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Do not add it to the ignore list, so this notification may recur.",
|
||||
"Do not add it to the ignore list, so this notification may recurr.": "Do not add it to the ignore list, so this notification may recurr.",
|
||||
"Do not restore": "Не восстанавливать",
|
||||
"Do not restore all": "Не восстанавливать все",
|
||||
"Do you want to enable watching for changes for all your folders?": "Хотите включить слежение за изменениями для всех своих папок?",
|
||||
@@ -187,7 +188,7 @@
|
||||
"Local State (Total)": "Локальное состояние (всего)",
|
||||
"Locally Changed Items": "Объекты, изменённые на этом компьютере",
|
||||
"Log": "Журнал",
|
||||
"Log tailing paused. Scroll to the bottom to continue.": "Вывод журнала приостановлен. Чтобы продолжить, прокрутите до журнал конца.",
|
||||
"Log tailing paused. Scroll to the bottom to continue.": "Вывод журнала приостановлен. Чтобы продолжить, прокрутите до конца журнала.",
|
||||
"Logs": "Журналы",
|
||||
"Major Upgrade": "Обновление основной версии",
|
||||
"Mass actions": "Массовые действия",
|
||||
@@ -378,9 +379,9 @@
|
||||
"Unexpected items have been found in this folder.": "В папке найдены неожиданные элементы.",
|
||||
"Unignore": "Не игнорировать",
|
||||
"Unknown": "Неизвестно",
|
||||
"Unshared": "Необщедоступно",
|
||||
"Unshared": "Не общедоступно",
|
||||
"Unshared Devices": "Устройства без общего доступа",
|
||||
"Unshared Folders": "Необщедоступные папки",
|
||||
"Unshared Folders": "Не общедоступные папки",
|
||||
"Untrusted": "Ненадёжный",
|
||||
"Up to Date": "В актуальном состоянии",
|
||||
"Updated": "Обновлено",
|
||||
|
||||
@@ -96,6 +96,7 @@
|
||||
"Discovery Failures": "Zlyhania zisťovania",
|
||||
"Dismiss": "Dismiss",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Do not add it to the ignore list, so this notification may recur.",
|
||||
"Do not add it to the ignore list, so this notification may recurr.": "Do not add it to the ignore list, so this notification may recurr.",
|
||||
"Do not restore": "Neobnovovať",
|
||||
"Do not restore all": "Neobnovovať všetko",
|
||||
"Do you want to enable watching for changes for all your folders?": "Chcete zapnúť sledovanie zmien vo všetkých priečinkoch?",
|
||||
|
||||
@@ -95,7 +95,8 @@
|
||||
"Discovery": "Annonsering",
|
||||
"Discovery Failures": "Annonseringsmisslyckanden",
|
||||
"Dismiss": "Avfärda",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Do not add it to the ignore list, so this notification may recur.",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Lägg inte till den i ignoreringslistan, så denna avisering kan återkomma.",
|
||||
"Do not add it to the ignore list, so this notification may recurr.": "Lägg inte till den i ignoreringslistan, så denna avisering kan återkomma.",
|
||||
"Do not restore": "Återställ inte",
|
||||
"Do not restore all": "Återställ inte allt",
|
||||
"Do you want to enable watching for changes for all your folders?": "Vill du aktivera bevakning av ändringar på alla dina mappar?",
|
||||
|
||||
@@ -94,8 +94,9 @@
|
||||
"Discovered": "Keşfedildi",
|
||||
"Discovery": "Keşif",
|
||||
"Discovery Failures": "Keşif Hataları",
|
||||
"Dismiss": "Dismiss",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Do not add it to the ignore list, so this notification may recur.",
|
||||
"Dismiss": "Yoksay",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Yoksayma listesine eklemeyin, böylece bu bildirim tekrarlayabilir.",
|
||||
"Do not add it to the ignore list, so this notification may recurr.": "Yoksayma listesine eklemeyin, böylece bu bildirim tekrarlayabilir.",
|
||||
"Do not restore": "Geri yükleme yapma",
|
||||
"Do not restore all": "Hiçbirini geri yükleme",
|
||||
"Do you want to enable watching for changes for all your folders?": "Tüm klasörleriniz için değişiklikleri izlemeyi etkinleştirmek istiyor musunuz?",
|
||||
@@ -229,7 +230,7 @@
|
||||
"Periodic scanning at given interval and disabled watching for changes": "Belirli aralıklarla düzenli tarama ve değişiklikleri izleme etkisizleştirildi",
|
||||
"Periodic scanning at given interval and enabled watching for changes": "Belirli aralıklarla düzenli tarama ve değişiklikleri izleme etkinleştirildi",
|
||||
"Periodic scanning at given interval and failed setting up watching for changes, retrying every 1m:": "Belirli aralıklarla düzenli tarama ve değişiklikleri izlemeyi ayarlama başarısız oldu, her 1 dakikada yeniden deneniyor:",
|
||||
"Permanently add it to the ignore list, suppressing further notifications.": "Permanently add it to the ignore list, suppressing further notifications.",
|
||||
"Permanently add it to the ignore list, suppressing further notifications.": "Diğer bildirimleri bastırarak, yoksayma listesine kalıcı olarak ekleyin.",
|
||||
"Permissions": "İzinler",
|
||||
"Please consult the release notes before performing a major upgrade.": "Büyük bir yükseltme gerçekleştirmeden önce lütfen yayım notlarına başvurun.",
|
||||
"Please set a GUI Authentication User and Password in the Settings dialog.": "Lütfen Ayarlar ileti öğesinde GKA Kimlik Doğrulama Kullanıcısı ve Parolasını ayarlayın.",
|
||||
@@ -289,8 +290,8 @@
|
||||
"Sharing": "Paylaşma",
|
||||
"Show ID": "Kimliği Göster",
|
||||
"Show QR": "QR Göster",
|
||||
"Show detailed discovery status": "Show detailed discovery status",
|
||||
"Show detailed listener status": "Show detailed listener status",
|
||||
"Show detailed discovery status": "Ayrıntılı keşif durumunu göster",
|
||||
"Show detailed listener status": "Ayrıntılı dinleyici durumunu göster",
|
||||
"Show diff with previous version": "Önceki sürüm ile farklılıkları göster",
|
||||
"Shown instead of Device ID in the cluster status. Will be advertised to other devices as an optional default name.": "Küme durumunda Cihaz Kimliği yerine gösterilir. İsteğe bağlı varsayılan ad olarak diğer cihazlara duyurulacaktır.",
|
||||
"Shown instead of Device ID in the cluster status. Will be updated to the name the device advertises if left empty.": "Küme durumunda Cihaz Kimliği yerine gösterilir. Boş bırakılırsa duyurulan cihaz adına güncellenecektir.",
|
||||
@@ -300,9 +301,9 @@
|
||||
"Single level wildcard (matches within a directory only)": "Tek seviyeli joker karakter (yalnızca bir dizin içinde eşleşir)",
|
||||
"Size": "Boyut",
|
||||
"Smallest First": "Önce En Küçük Olan",
|
||||
"Some discovery methods could not be established for finding other devices or announcing this device:": "Some discovery methods could not be established for finding other devices or announcing this device:",
|
||||
"Some discovery methods could not be established for finding other devices or announcing this device:": "Bazı keşif yöntemleri, diğer cihazları bulmak veya bu cihazı duyurmak için kurulamadı:",
|
||||
"Some items could not be restored:": "Bazı öğeler geri yüklenemedi:",
|
||||
"Some listening addresses could not be enabled to accept connections:": "Some listening addresses could not be enabled to accept connections:",
|
||||
"Some listening addresses could not be enabled to accept connections:": "Bazı dinleme adresleri bağlantıları kabul etmek için etkinleştirilemedi:",
|
||||
"Source Code": "Kaynak Kodu",
|
||||
"Stable releases and release candidates": "Kararlı yayımlar ve yayım adayları",
|
||||
"Stable releases are delayed by about two weeks. During this time they go through testing as release candidates.": "Kararlı yayımlar yaklaşık iki hafta gecikir. Bu süre zarfında yayım adayları olarak denemelerden geçerler.",
|
||||
@@ -319,8 +320,8 @@
|
||||
"Syncthing has been shut down.": "Syncthing kapatıldı.",
|
||||
"Syncthing includes the following software or portions thereof:": "Syncthing aşağıdaki yazılımları veya bunların bölümlerini içermektedir:",
|
||||
"Syncthing is Free and Open Source Software licensed as MPL v2.0.": "Syncthing, MPL v2.0 ile lisanslanan Özgür ve Açık Kaynaklı Yazılım'dır.",
|
||||
"Syncthing is listening on the following network addresses for connection attempts from other devices:": "Syncthing is listening on the following network addresses for connection attempts from other devices:",
|
||||
"Syncthing is not listening for connection attempts from other devices on any address. Only outgoing connections from this device may work.": "Syncthing is not listening for connection attempts from other devices on any address. Only outgoing connections from this device may work.",
|
||||
"Syncthing is listening on the following network addresses for connection attempts from other devices:": "Syncthing, diğer cihazlardan gelen bağlantı girişimleri için aşağıdaki ağ adreslerini dinliyor:",
|
||||
"Syncthing is not listening for connection attempts from other devices on any address. Only outgoing connections from this device may work.": "Syncthing, herhangi bir adresteki diğer cihazlardan gelen bağlantı girişimlerini dinlemiyor. Bu cihazdan yalnızca giden bağlantılar çalışabilir.",
|
||||
"Syncthing is restarting.": "Syncthing yeniden başlatılıyor.",
|
||||
"Syncthing is upgrading.": "Syncthing yükseltiliyor.",
|
||||
"Syncthing now supports automatically reporting crashes to the developers. This feature is enabled by default.": "Syncthing artık çökmeleri geliştiricilere otomatik olarak bildirmeyi destekler. Bu özellik varsayılan olarak etkinleştirilmiştir.",
|
||||
@@ -345,7 +346,7 @@
|
||||
"The following intervals are used: for the first hour a version is kept every 30 seconds, for the first day a version is kept every hour, for the first 30 days a version is kept every day, until the maximum age a version is kept every week.": "Şu aralıklar kullanılır: ilk saat için her 30 saniyede bir sürüm tutulur, ilk gün için her saat bir sürüm tutulur, ilk 30 gün için her gün bir sürüm tutulur, en fazla yaşa kadar her hafta bir sürüm tutulur.",
|
||||
"The following items could not be synchronized.": "Aşağıdaki öğeler eşitlenemedi.",
|
||||
"The following items were changed locally.": "Aşağıdaki öğeler yerel olarak değiştirildi.",
|
||||
"The following methods are used to discover other devices on the network and announce this device to be found by others:": "The following methods are used to discover other devices on the network and announce this device to be found by others:",
|
||||
"The following methods are used to discover other devices on the network and announce this device to be found by others:": "Aşağıdaki yöntemler, ağdaki diğer cihazları keşfetmek ve bu cihazı başkaları tarafından bulunacak şekilde duyurmak için kullanılır:",
|
||||
"The following unexpected items were found.": "Aşağıdaki beklenmeyen öğeler bulundu.",
|
||||
"The interval must be a positive number of seconds.": "Aralık, pozitif bir saniye sayısı olmak zorundadır.",
|
||||
"The interval, in seconds, for running cleanup in the versions directory. Zero to disable periodic cleaning.": "Sürüm dizininde temizlemeyi çalıştırmak için saniye olarak aralık değeri. Düzenli temizliği etkisizleştirmek için sıfır.",
|
||||
@@ -363,7 +364,7 @@
|
||||
"They are retried automatically and will be synced when the error is resolved.": "Otomatik olarak yeniden denenirler ve hata çözüldüğünde eşitleneceklerdir.",
|
||||
"This Device": "Bu Cihaz",
|
||||
"This can easily give hackers access to read and change any files on your computer.": "Bu, bilgisayar korsanlarının bilgisayarınızdaki herhangi bir dosyayı okumasına ve değiştirmesine kolayca erişim sağlayabilir.",
|
||||
"This device cannot automatically discover other devices or announce its own address to be found by others. Only devices with statically configured addresses can connect.": "This device cannot automatically discover other devices or announce its own address to be found by others. Only devices with statically configured addresses can connect.",
|
||||
"This device cannot automatically discover other devices or announce its own address to be found by others. Only devices with statically configured addresses can connect.": "Bu cihaz diğer cihazları otomatik olarak keşfedemez veya başkaları tarafından bulunacak kendi adresini duyuramaz. Yalnızca sabit olarak yapılandırılmış adreslere sahip cihazlar bağlanabilir.",
|
||||
"This is a major version upgrade.": "Bu büyük sürüm yükseltmesidir.",
|
||||
"This setting controls the free space required on the home (i.e., index database) disk.": "Bu ayar, ev (yani indeks veritabanı) diskindeki gereken boş alanı denetler.",
|
||||
"Time": "Zaman",
|
||||
|
||||
@@ -96,6 +96,7 @@
|
||||
"Discovery Failures": "Помилки виявлення",
|
||||
"Dismiss": "Dismiss",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Do not add it to the ignore list, so this notification may recur.",
|
||||
"Do not add it to the ignore list, so this notification may recurr.": "Do not add it to the ignore list, so this notification may recurr.",
|
||||
"Do not restore": "Не відновлювати",
|
||||
"Do not restore all": "Не відновлювати все",
|
||||
"Do you want to enable watching for changes for all your folders?": "Бажаєте увімкнути стеження за змінами у всіх ваших папках?",
|
||||
|
||||
@@ -96,6 +96,7 @@
|
||||
"Discovery Failures": "设备发现错误",
|
||||
"Dismiss": "解散",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Do not add it to the ignore list, so this notification may recur.",
|
||||
"Do not add it to the ignore list, so this notification may recurr.": "不将其添加到忽略列表中,该通知可能会重复出现。",
|
||||
"Do not restore": "不要恢复",
|
||||
"Do not restore all": "不要全部恢复",
|
||||
"Do you want to enable watching for changes for all your folders?": "您想要启用对监视您所有文件夹的更改吗?",
|
||||
|
||||
@@ -96,6 +96,7 @@
|
||||
"Discovery Failures": "設備發現錯誤",
|
||||
"Dismiss": "Dismiss",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Do not add it to the ignore list, so this notification may recur.",
|
||||
"Do not add it to the ignore list, so this notification may recurr.": "Do not add it to the ignore list, so this notification may recurr.",
|
||||
"Do not restore": "不要恢復",
|
||||
"Do not restore all": "不要全部恢復",
|
||||
"Do you want to enable watching for changes for all your folders?": "您想要啟用對監視您所有文件夾的更改嗎?",
|
||||
|
||||
@@ -96,6 +96,7 @@
|
||||
"Discovery Failures": "探索失敗",
|
||||
"Dismiss": "Dismiss",
|
||||
"Do not add it to the ignore list, so this notification may recur.": "Do not add it to the ignore list, so this notification may recur.",
|
||||
"Do not add it to the ignore list, so this notification may recurr.": "Do not add it to the ignore list, so this notification may recurr.",
|
||||
"Do not restore": "不要還原",
|
||||
"Do not restore all": "不要還原全部",
|
||||
"Do you want to enable watching for changes for all your folders?": "您要對全部的資料夾啟用變動監視嗎?",
|
||||
|
||||
@@ -209,7 +209,7 @@
|
||||
</div>
|
||||
<div class="panel-footer clearfix">
|
||||
<div class="pull-right">
|
||||
<button type="button" class="btn btn-sm btn-default" ng-click="dismissPendingDevice(deviceID)" tooltip data-original-title="{{'Do not add it to the ignore list, so this notification may recurr.' | translate}}">
|
||||
<button type="button" class="btn btn-sm btn-default" ng-click="dismissPendingDevice(deviceID)" tooltip data-original-title="{{'Do not add it to the ignore list, so this notification may recur.' | translate}}">
|
||||
<span class="far fa-clock"></span> <span translate>Dismiss</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-success" ng-click="addDevice(deviceID, pendingDevice.name)">
|
||||
@@ -253,7 +253,7 @@
|
||||
</div>
|
||||
<div class="panel-footer clearfix">
|
||||
<div class="pull-right">
|
||||
<button type="button" class="btn btn-sm btn-default" ng-click="dismissPendingFolder(folderID, deviceID)" tooltip data-original-title="{{'Do not add it to the ignore list, so this notification may recurr.' | translate}}">
|
||||
<button type="button" class="btn btn-sm btn-default" ng-click="dismissPendingFolder(folderID, deviceID)" tooltip data-original-title="{{'Do not add it to the ignore list, so this notification may recur.' | translate}}">
|
||||
<span class="far fa-clock"></span> <span translate>Dismiss</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-success" ng-click="addFolderAndShare(folderID, pendingFolder, deviceID)" ng-if="!folders[folderID]">
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<h4 class="text-center" translate>The Syncthing Authors</h4>
|
||||
<div class="row">
|
||||
<div class="col-md-12" id="contributor-list">
|
||||
Jakob Borg, Audrius Butkevicius, Jesse Lucas, Simon Frei, Alexander Graf, Alexandre Viau, Anderson Mesquita, André Colomb, Antony Male, Ben Schulz, Caleb Callaway, Daniel Harte, Evgeny Kuznetsov, Lars K.W. Gohlke, Lode Hoste, Michael Ploujnikov, Nate Morrison, Philippe Schommers, Ryan Sullivan, Sergey Mishin, Stefan Tatschner, Tomasz Wilczyński, Wulf Weich, dependabot-preview[bot], greatroar, Aaron Bieber, Adam Piggott, Adel Qalieh, Alan Pope, Alberto Donato, Alessandro G., Alex Lindeman, Alex Xu, Aman Gupta, Andrew Dunham, Andrew Rabert, Andrey D, Anjan Momi, Antoine Lamielle, Anur, Aranjedeath, Arkadiusz Tymiński, Arthur Axel fREW Schmidt, Artur Zubilewicz, Aurélien Rainone, BAHADIR YILMAZ, Bart De Vries, Ben Curthoys, Ben Shepherd, Ben Sidhom, Benedikt Heine, Benedikt Morbach, Benno Fünfstück, Benny Ng, Boqin Qin, Boris Rybalkin, Brandon Philips, Brendan Long, Brian R. Becker, Carsten Hagemann, Cathryne Linenweaver, Cedric Staniewski, Choongkyu, Chris Howie, Chris Joel, Chris Tonkinson, Christian Prescott, Colin Kennedy, Cromefire_, Cyprien Devillez, Dale Visser, Dan, Daniel Bergmann, Daniel Martí, Darshil Chanpura, David Rimmer, Denis A., Dennis Wilson, Dmitry Saveliev, Domenic Horner, Dominik Heidler, Elias Jarlebring, Elliot Huffman, Emil Hessman, Eric Lesiuta, Erik Meitner, Federico Castagnini, Felix Ableitner, Felix Lampe, Felix Unterpaintner, Francois-Xavier Gsell, Frank Isemann, Gahl Saraf, Gilli Sigurdsson, Gleb Sinyavskiy, Graham Miln, Han Boetes, HansK-p, Harrison Jones, Heiko Zuerker, Hugo Locurcio, Iain Barnett, Ian Johnson, Ikko Ashimine, Ilya Brin, Iskander Sharipov, Jaakko Hannikainen, Jacek Szafarkiewicz, Jack Croft, Jacob, Jake Peterson, James Patterson, Jaroslav Lichtblau, Jaroslav Malec, Jaya Chithra, Jens Diemer, Jerry Jacobs, Jochen Voss, Johan Andersson, Johan Vromans, John Rinehart, Jonas Thelemann, Jonathan, Jonathan Cross, Jonta, Jose Manuel Delicado, Jörg Thalheim, Jędrzej Kula, Kalle Laine, Karol Różycki, Keith Turner, Kelong Cong, Ken'ichi Kamada, Kevin Allen, Kevin Bushiri, Kevin White, Jr., Kurt Fitzner, Lars Lehtonen, Laurent Arnoud, Laurent Etiemble, Leo Arias, Liu Siyuan, Lord Landon Agahnim, Lukas Lihotzki, Majed Abdulaziz, Marc Laporte, Marc Pujol, Marcin Dziadus, Marcus Legendre, Mario Majila, Mark Pulford, Mateusz Naściszewski, Mateusz Ż, Matic Potočnik, Matt Burke, Matt Robenolt, Matteo Ruina, Maurizio Tomasi, Max Schulze, MaximAL, Maxime Thirouin, MichaIng, Michael Jephcote, Michael Rienstra, Michael Tilli, Mike Boone, MikeLund, MikolajTwarog, Mingxuan Lin, Nicholas Rishel, Nico Stapelbroek, Nicolas Braud-Santoni, Nicolas Perraut, Niels Peter Roest, Nils Jakobi, NinoM4ster, Nitroretro, NoLooseEnds, Oliver Freyermuth, Otiel, Oyebanji Jacob Mayowa, Pablo, Pascal Jungblut, Paul Brit, Pawel Palenica, Paweł Rozlach, Peter Badida, Peter Dave Hello, Peter Hoeg, Peter Marquardt, Phani Rithvij, Phil Davis, Phill Luby, Pier Paolo Ramon, Piotr Bejda, Pramodh KP, Quentin Hibon, Rahmi Pruitt, Richard Hartmann, Robert Carosi, Roberto Santalla, Robin Schoonover, Roman Zaynetdinov, Ross Smith II, Ruslan Yevdokymov, Sacheendra Talluri, Scott Klupfel, Shaarad Dalvi, Simon Mwepu, Sly_tom_cat, Stefan Kuntz, Steven Eckhoff, Suhas Gundimeda, Taylor Khan, Thomas Hipp, Tim Abell, Tim Howes, Tobias Klauser, Tobias Nygren, Tobias Tom, Tom Jakubowski, Tommy Thorn, Tully Robinson, Tyler Brazier, Tyler Kropp, Unrud, Veeti Paananen, Victor Buinsky, Vil Brekin, Vladimir Rusinov, William A. Kennington III, Xavier O., Yannic A., andresvia, andyleap, boomsquared, bt90, chenrui, chucic, deepsource-autofix[bot], dependabot[bot], derekriemer, desbma, georgespatton, ghjklw, janost, jaseg, jelle van der Waa, klemens, marco-m, mclang, mv1005, otbutz, overkill, perewa, rubenbe, wangguoliang, wouter bolsterlee, xarx00, xjtdy888, 佛跳墙
|
||||
Jakob Borg, Audrius Butkevicius, Jesse Lucas, Simon Frei, Alexander Graf, Alexandre Viau, Anderson Mesquita, André Colomb, Antony Male, Ben Schulz, Caleb Callaway, Daniel Harte, Evgeny Kuznetsov, Lars K.W. Gohlke, Lode Hoste, Michael Ploujnikov, Nate Morrison, Philippe Schommers, Ryan Sullivan, Sergey Mishin, Stefan Tatschner, Tomasz Wilczyński, Wulf Weich, dependabot-preview[bot], greatroar, Aaron Bieber, Adam Piggott, Adel Qalieh, Alan Pope, Alberto Donato, Alessandro G., Alex Lindeman, Alex Xu, Aman Gupta, Andrew Dunham, Andrew Rabert, Andrey D, Anjan Momi, Antoine Lamielle, Anur, Aranjedeath, Arkadiusz Tymiński, Arthur Axel fREW Schmidt, Artur Zubilewicz, Aurélien Rainone, BAHADIR YILMAZ, Bart De Vries, Ben Curthoys, Ben Shepherd, Ben Sidhom, Benedikt Heine, Benedikt Morbach, Benno Fünfstück, Benny Ng, Boqin Qin, Boris Rybalkin, Brandon Philips, Brendan Long, Brian R. Becker, Carsten Hagemann, Cathryne Linenweaver, Cedric Staniewski, Chih-Hsuan Yen, Choongkyu, Chris Howie, Chris Joel, Chris Tonkinson, Christian Prescott, Colin Kennedy, Cromefire_, Cyprien Devillez, Dale Visser, Dan, Daniel Bergmann, Daniel Martí, Darshil Chanpura, David Rimmer, Denis A., Dennis Wilson, Dmitry Saveliev, Domenic Horner, Dominik Heidler, Elias Jarlebring, Elliot Huffman, Emil Hessman, Eric Lesiuta, Erik Meitner, Federico Castagnini, Felix Ableitner, Felix Lampe, Felix Unterpaintner, Francois-Xavier Gsell, Frank Isemann, Gahl Saraf, Gilli Sigurdsson, Gleb Sinyavskiy, Graham Miln, Han Boetes, HansK-p, Harrison Jones, Heiko Zuerker, Hugo Locurcio, Iain Barnett, Ian Johnson, Ikko Ashimine, Ilya Brin, Iskander Sharipov, Jaakko Hannikainen, Jacek Szafarkiewicz, Jack Croft, Jacob, Jake Peterson, James Patterson, Jaroslav Lichtblau, Jaroslav Malec, Jaya Chithra, Jens Diemer, Jerry Jacobs, Jochen Voss, Johan Andersson, Johan Vromans, John Rinehart, Jonas Thelemann, Jonathan, Jonathan Cross, Jonta, Jose Manuel Delicado, Jörg Thalheim, Jędrzej Kula, Kalle Laine, Karol Różycki, Keith Turner, Kelong Cong, Ken'ichi Kamada, Kevin Allen, Kevin Bushiri, Kevin White, Jr., Kurt Fitzner, Lars Lehtonen, Laurent Arnoud, Laurent Etiemble, Leo Arias, Liu Siyuan, Lord Landon Agahnim, Lukas Lihotzki, Majed Abdulaziz, Marc Laporte, Marc Pujol, Marcin Dziadus, Marcus Legendre, Mario Majila, Mark Pulford, Mateusz Naściszewski, Mateusz Ż, Matic Potočnik, Matt Burke, Matt Robenolt, Matteo Ruina, Maurizio Tomasi, Max Schulze, MaximAL, Maxime Thirouin, MichaIng, Michael Jephcote, Michael Rienstra, Michael Tilli, Mike Boone, MikeLund, MikolajTwarog, Mingxuan Lin, Nicholas Rishel, Nico Stapelbroek, Nicolas Braud-Santoni, Nicolas Perraut, Niels Peter Roest, Nils Jakobi, NinoM4ster, Nitroretro, NoLooseEnds, Oliver Freyermuth, Otiel, Oyebanji Jacob Mayowa, Pablo, Pascal Jungblut, Paul Brit, Pawel Palenica, Paweł Rozlach, Peter Badida, Peter Dave Hello, Peter Hoeg, Peter Marquardt, Phani Rithvij, Phil Davis, Phill Luby, Pier Paolo Ramon, Piotr Bejda, Pramodh KP, Quentin Hibon, Rahmi Pruitt, Richard Hartmann, Robert Carosi, Roberto Santalla, Robin Schoonover, Roman Zaynetdinov, Ross Smith II, Ruslan Yevdokymov, Sacheendra Talluri, Scott Klupfel, Shaarad Dalvi, Simon Mwepu, Sly_tom_cat, Stefan Kuntz, Steven Eckhoff, Suhas Gundimeda, Taylor Khan, Thomas Hipp, Tim Abell, Tim Howes, Tobias Klauser, Tobias Nygren, Tobias Tom, Tom Jakubowski, Tommy Thorn, Tully Robinson, Tyler Brazier, Tyler Kropp, Unrud, Veeti Paananen, Victor Buinsky, Vil Brekin, Vladimir Rusinov, William A. Kennington III, Xavier O., Yannic A., andresvia, andyleap, boomsquared, bt90, chenrui, chucic, deepsource-autofix[bot], dependabot[bot], derekriemer, desbma, georgespatton, ghjklw, janost, jaseg, jelle van der Waa, jtagcat, klemens, marco-m, mclang, mv1005, otbutz, overkill, perewa, rubenbe, wangguoliang, wouter bolsterlee, xarx00, xjtdy888, 佛跳墙
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
|
||||
@@ -128,7 +128,7 @@
|
||||
<div class="form-group" ng-if="currentFolder._guiVersioning.selector=='external'" ng-class="{'has-error': folderEditor.externalCommand.$invalid && folderEditor.externalCommand.$dirty}">
|
||||
<p translate class="help-block">An external command handles the versioning. It has to remove the file from the shared folder. If the path to the application contains spaces, it should be quoted.</p>
|
||||
<label translate for="externalCommand">Command</label>
|
||||
<textarea name="externalCommand" id="externalCommand" class="form-control" rows="1" ng-model="currentFolder._guiVersioning.externalCommand" required="" aria-required="true" />
|
||||
<textarea name="externalCommand" id="externalCommand" class="form-control" rows="1" ng-model="currentFolder._guiVersioning.externalCommand" required="" aria-required="true"></textarea>
|
||||
<p class="help-block">
|
||||
<span translate ng-if="folderEditor.externalCommand.$valid || folderEditor.externalCommand.$pristine">See external versioning help for supported templated command line parameters.</span>
|
||||
<span translate ng-if="folderEditor.externalCommand.$error.required && folderEditor.externalCommand.$dirty">The path cannot be blank.</span>
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
<div class="panel-group" id="advancedAccordion" role="tablist" aria-multiselectable="true">
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading" role="tab" id="guiHeading" data-toggle="collapse" data-parent="#advancedAccordion" href="#guiConfig" aria-expanded="true" aria-controls="guiConfig" style="cursor: pointer;">
|
||||
<div class="panel-heading" role="tab" id="guiHeading" data-toggle="collapse" data-parent="#advancedAccordion" href="#guiConfig" aria-expanded="false" aria-controls="guiConfig" style="cursor: pointer;">
|
||||
<h4 class="panel-title" translate tabindex="0">GUI</h4>
|
||||
</div>
|
||||
<div id="guiConfig" class="panel-collapse collapse in" role="tabpanel" aria-labelledby="guiHeading">
|
||||
<div id="guiConfig" class="panel-collapse collapse" role="tabpanel" aria-labelledby="guiHeading">
|
||||
<div class="panel-body">
|
||||
<form class="form-horizontal" role="form">
|
||||
<div ng-repeat="(key, value) in advancedConfig.gui" ng-init="type = inputTypeFor(key, value)" ng-if="type != 'skip'" class="form-group">
|
||||
|
||||
@@ -1185,7 +1185,7 @@ func (s *service) getSupportBundle(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// Report Data as a JSON
|
||||
if r, err := s.urService.ReportData(context.TODO()); err != nil {
|
||||
if r, err := s.urService.ReportDataPreview(r.Context(), ur.Version); err != nil {
|
||||
l.Warnln("Support bundle: failed to create usage-reporting.json.txt:", err)
|
||||
} else {
|
||||
if usageReportingData, err := json.MarshalIndent(r, "", " "); err != nil {
|
||||
|
||||
@@ -45,7 +45,9 @@ type quicDialer struct {
|
||||
func (d *quicDialer) Dial(ctx context.Context, _ protocol.DeviceID, uri *url.URL) (internalConn, error) {
|
||||
uri = fixupPort(uri, config.DefaultQUICPort)
|
||||
|
||||
addr, err := net.ResolveUDPAddr("udp", uri.Host)
|
||||
network := quicNetwork(uri)
|
||||
|
||||
addr, err := net.ResolveUDPAddr(network, uri.Host)
|
||||
if err != nil {
|
||||
return internalConn{}, err
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
@@ -81,7 +80,7 @@ func (t *quicListener) OnExternalAddressChanged(address *stun.Host, via string)
|
||||
}
|
||||
|
||||
func (t *quicListener) serve(ctx context.Context) error {
|
||||
network := strings.ReplaceAll(t.uri.Scheme, "quic", "udp")
|
||||
network := quicNetwork(t.uri)
|
||||
|
||||
udpAddr, err := net.ResolveUDPAddr(network, t.uri.Host)
|
||||
if err != nil {
|
||||
@@ -204,7 +203,7 @@ func (t *quicListener) LANAddresses() []*url.URL {
|
||||
uri := maybeReplacePort(t.uri, t.laddr)
|
||||
t.mut.Unlock()
|
||||
addrs := []*url.URL{uri}
|
||||
network := strings.ReplaceAll(uri.Scheme, "quic", "udp")
|
||||
network := quicNetwork(uri)
|
||||
addrs = append(addrs, getURLsForAllAdaptersIfUnspecified(network, uri)...)
|
||||
return addrs
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ package connections
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/url"
|
||||
|
||||
"github.com/lucas-clemente/quic-go"
|
||||
"github.com/syncthing/syncthing/lib/util"
|
||||
@@ -23,6 +24,17 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
func quicNetwork(uri *url.URL) string {
|
||||
switch uri.Scheme {
|
||||
case "quic4":
|
||||
return "udp4"
|
||||
case "quic6":
|
||||
return "udp6"
|
||||
default:
|
||||
return "udp"
|
||||
}
|
||||
}
|
||||
|
||||
type quicTlsConn struct {
|
||||
quic.Session
|
||||
quic.Stream
|
||||
|
||||
@@ -66,6 +66,8 @@ const (
|
||||
worstDialerPriority = math.MaxInt32
|
||||
recentlySeenCutoff = 7 * 24 * time.Hour
|
||||
shortLivedConnectionThreshold = 5 * time.Second
|
||||
dialMaxParallel = 64
|
||||
dialMaxParallelPerDevice = 8
|
||||
)
|
||||
|
||||
// From go/src/crypto/tls/cipher_suites.go
|
||||
@@ -490,14 +492,40 @@ func (s *service) dialDevices(ctx context.Context, now time.Time, cfg config.Con
|
||||
// Perform dials according to the queue, stopping when we've reached the
|
||||
// allowed additional number of connections (if limited).
|
||||
numConns := 0
|
||||
for _, entry := range queue {
|
||||
if conn, ok := s.dialParallel(ctx, entry.id, entry.targets); ok {
|
||||
s.conns <- conn
|
||||
numConns++
|
||||
if allowAdditional > 0 && numConns >= allowAdditional {
|
||||
break
|
||||
}
|
||||
var numConnsMut stdsync.Mutex
|
||||
dialSemaphore := util.NewSemaphore(dialMaxParallel)
|
||||
dialWG := new(stdsync.WaitGroup)
|
||||
dialCtx, dialCancel := context.WithCancel(ctx)
|
||||
defer func() {
|
||||
dialWG.Wait()
|
||||
dialCancel()
|
||||
}()
|
||||
for i := range queue {
|
||||
select {
|
||||
case <-dialCtx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
dialWG.Add(1)
|
||||
go func(entry dialQueueEntry) {
|
||||
defer dialWG.Done()
|
||||
conn, ok := s.dialParallel(dialCtx, entry.id, entry.targets, dialSemaphore)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
numConnsMut.Lock()
|
||||
if allowAdditional == 0 || numConns < allowAdditional {
|
||||
select {
|
||||
case s.conns <- conn:
|
||||
numConns++
|
||||
if allowAdditional > 0 && numConns >= allowAdditional {
|
||||
dialCancel()
|
||||
}
|
||||
case <-dialCtx.Done():
|
||||
}
|
||||
}
|
||||
numConnsMut.Unlock()
|
||||
}(queue[i])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -758,13 +786,23 @@ func (s *service) CommitConfiguration(from, to config.Configuration) bool {
|
||||
|
||||
func (s *service) checkAndSignalConnectLoopOnUpdatedDevices(from, to config.Configuration) {
|
||||
oldDevices := from.DeviceMap()
|
||||
dial := false
|
||||
s.dialNowDevicesMut.Lock()
|
||||
for _, dev := range to.Devices {
|
||||
oldDev, ok := oldDevices[dev.DeviceID]
|
||||
if !ok || !util.EqualStrings(oldDev.Addresses, dev.Addresses) {
|
||||
s.scheduleDialNow()
|
||||
break
|
||||
if dev.Paused {
|
||||
continue
|
||||
}
|
||||
if oldDev, ok := oldDevices[dev.DeviceID]; !ok || oldDev.Paused {
|
||||
s.dialNowDevices[dev.DeviceID] = struct{}{}
|
||||
dial = true
|
||||
} else if !util.EqualStrings(oldDev.Addresses, dev.Addresses) {
|
||||
dial = true
|
||||
}
|
||||
}
|
||||
if dial {
|
||||
s.scheduleDialNow()
|
||||
}
|
||||
s.dialNowDevicesMut.Unlock()
|
||||
}
|
||||
|
||||
func (s *service) scheduleDialNow() {
|
||||
@@ -959,7 +997,7 @@ func IsAllowedNetwork(host string, allowed []string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *service) dialParallel(ctx context.Context, deviceID protocol.DeviceID, dialTargets []dialTarget) (internalConn, bool) {
|
||||
func (s *service) dialParallel(ctx context.Context, deviceID protocol.DeviceID, dialTargets []dialTarget, parentSema *util.Semaphore) (internalConn, bool) {
|
||||
// Group targets into buckets by priority
|
||||
dialTargetBuckets := make(map[int][]dialTarget, len(dialTargets))
|
||||
for _, tgt := range dialTargets {
|
||||
@@ -975,13 +1013,19 @@ func (s *service) dialParallel(ctx context.Context, deviceID protocol.DeviceID,
|
||||
// Sort the priorities so that we dial lowest first (which means highest...)
|
||||
sort.Ints(priorities)
|
||||
|
||||
sema := util.MultiSemaphore{util.NewSemaphore(dialMaxParallelPerDevice), parentSema}
|
||||
for _, prio := range priorities {
|
||||
tgts := dialTargetBuckets[prio]
|
||||
res := make(chan internalConn, len(tgts))
|
||||
wg := stdsync.WaitGroup{}
|
||||
for _, tgt := range tgts {
|
||||
sema.Take(1)
|
||||
wg.Add(1)
|
||||
go func(tgt dialTarget) {
|
||||
defer func() {
|
||||
wg.Done()
|
||||
sema.Give(1)
|
||||
}()
|
||||
conn, err := tgt.Dial(ctx)
|
||||
if err == nil {
|
||||
// Closes the connection on error
|
||||
@@ -994,7 +1038,6 @@ func (s *service) dialParallel(ctx context.Context, deviceID protocol.DeviceID,
|
||||
l.Debugln("dialing", deviceID, tgt.uri, "success:", conn)
|
||||
res <- conn
|
||||
}
|
||||
wg.Done()
|
||||
}(tgt)
|
||||
}
|
||||
|
||||
|
||||
@@ -35,11 +35,15 @@ func (db *Lowlevel) newReadOnlyTransaction() (readOnlyTransaction, error) {
|
||||
if err != nil {
|
||||
return readOnlyTransaction{}, err
|
||||
}
|
||||
return db.readOnlyTransactionFromBackendTransaction(tran), nil
|
||||
}
|
||||
|
||||
func (db *Lowlevel) readOnlyTransactionFromBackendTransaction(tran backend.ReadTransaction) readOnlyTransaction {
|
||||
return readOnlyTransaction{
|
||||
ReadTransaction: tran,
|
||||
keyer: db.keyer,
|
||||
evLogger: db.evLogger,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (t readOnlyTransaction) close() {
|
||||
@@ -551,12 +555,9 @@ func (db *Lowlevel) newReadWriteTransaction(hooks ...backend.CommitHook) (readWr
|
||||
return readWriteTransaction{}, err
|
||||
}
|
||||
return readWriteTransaction{
|
||||
WriteTransaction: tran,
|
||||
readOnlyTransaction: readOnlyTransaction{
|
||||
ReadTransaction: tran,
|
||||
keyer: db.keyer,
|
||||
},
|
||||
indirectionTracker: db,
|
||||
WriteTransaction: tran,
|
||||
readOnlyTransaction: db.readOnlyTransactionFromBackendTransaction(tran),
|
||||
indirectionTracker: db,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -386,7 +386,6 @@ func (f *caseFilesystem) checkCaseExisting(name string) error {
|
||||
}
|
||||
|
||||
type defaultRealCaser struct {
|
||||
fs Filesystem
|
||||
cache caseCache
|
||||
}
|
||||
|
||||
@@ -396,13 +395,12 @@ func newDefaultRealCaser(fs Filesystem) *defaultRealCaser {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
caser := &defaultRealCaser{
|
||||
fs: fs,
|
||||
return &defaultRealCaser{
|
||||
cache: caseCache{
|
||||
fs: fs,
|
||||
TwoQueueCache: cache,
|
||||
},
|
||||
}
|
||||
return caser
|
||||
}
|
||||
|
||||
func (r *defaultRealCaser) realCase(name string) (string, error) {
|
||||
@@ -414,27 +412,6 @@ func (r *defaultRealCaser) realCase(name string) (string, error) {
|
||||
for _, comp := range PathComponents(name) {
|
||||
node := r.cache.getExpireAdd(realName)
|
||||
|
||||
node.once.Do(func() {
|
||||
dirNames, err := r.fs.DirNames(realName)
|
||||
if err != nil {
|
||||
r.cache.Remove(realName)
|
||||
node.err = err
|
||||
return
|
||||
}
|
||||
|
||||
num := len(dirNames)
|
||||
node.children = make(map[string]struct{}, num)
|
||||
node.lowerToReal = make(map[string]string, num)
|
||||
lastLower := ""
|
||||
for _, n := range dirNames {
|
||||
node.children[n] = struct{}{}
|
||||
lower := UnicodeLowercaseNormalized(n)
|
||||
if lower != lastLower {
|
||||
node.lowerToReal[lower] = n
|
||||
lastLower = n
|
||||
}
|
||||
}
|
||||
})
|
||||
if node.err != nil {
|
||||
return "", node.err
|
||||
}
|
||||
@@ -457,10 +434,29 @@ func (r *defaultRealCaser) dropCache() {
|
||||
r.cache.Purge()
|
||||
}
|
||||
|
||||
func newCaseNode() *caseNode {
|
||||
return &caseNode{
|
||||
expires: time.Now().Add(caseCacheTimeout),
|
||||
type caseCache struct {
|
||||
*lru.TwoQueueCache
|
||||
fs Filesystem
|
||||
mut sync.Mutex
|
||||
}
|
||||
|
||||
// getExpireAdd gets an entry for the given key. If no entry exists, or it is
|
||||
// expired a new one is created and added to the cache.
|
||||
func (c *caseCache) getExpireAdd(key string) *caseNode {
|
||||
c.mut.Lock()
|
||||
defer c.mut.Unlock()
|
||||
v, ok := c.Get(key)
|
||||
if !ok {
|
||||
node := newCaseNode(key, c.fs)
|
||||
c.Add(key, node)
|
||||
return node
|
||||
}
|
||||
node := v.(*caseNode)
|
||||
if node.expires.Before(time.Now()) {
|
||||
node = newCaseNode(key, c.fs)
|
||||
c.Add(key, node)
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
// The keys to children are "real", case resolved names of the path
|
||||
@@ -474,30 +470,32 @@ type caseNode struct {
|
||||
expires time.Time
|
||||
lowerToReal map[string]string
|
||||
children map[string]struct{}
|
||||
once sync.Once
|
||||
err error
|
||||
}
|
||||
|
||||
type caseCache struct {
|
||||
*lru.TwoQueueCache
|
||||
mut sync.Mutex
|
||||
}
|
||||
|
||||
// getExpireAdd gets an entry for the given key. If no entry exists, or it is
|
||||
// expired a new one is created and added to the cache.
|
||||
func (c *caseCache) getExpireAdd(key string) *caseNode {
|
||||
c.mut.Lock()
|
||||
defer c.mut.Unlock()
|
||||
v, ok := c.Get(key)
|
||||
if !ok {
|
||||
node := newCaseNode()
|
||||
c.Add(key, node)
|
||||
func newCaseNode(name string, filesystem Filesystem) *caseNode {
|
||||
node := new(caseNode)
|
||||
dirNames, err := filesystem.DirNames(name)
|
||||
// Set expiry after calling DirNames in case this is super-slow
|
||||
// (e.g. dirs with many children on android)
|
||||
node.expires = time.Now().Add(caseCacheTimeout)
|
||||
if err != nil {
|
||||
node.err = err
|
||||
return node
|
||||
}
|
||||
node := v.(*caseNode)
|
||||
if node.expires.Before(time.Now()) {
|
||||
node = newCaseNode()
|
||||
c.Add(key, node)
|
||||
|
||||
num := len(dirNames)
|
||||
node.children = make(map[string]struct{}, num)
|
||||
node.lowerToReal = make(map[string]string, num)
|
||||
lastLower := ""
|
||||
for _, n := range dirNames {
|
||||
node.children[n] = struct{}{}
|
||||
lower := UnicodeLowercaseNormalized(n)
|
||||
if lower != lastLower {
|
||||
node.lowerToReal[lower] = n
|
||||
lastLower = n
|
||||
}
|
||||
}
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
// Copyright (C) 2018 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type byteSemaphore struct {
|
||||
max int
|
||||
available int
|
||||
mut sync.Mutex
|
||||
cond *sync.Cond
|
||||
}
|
||||
|
||||
func newByteSemaphore(max int) *byteSemaphore {
|
||||
if max < 0 {
|
||||
max = 0
|
||||
}
|
||||
s := byteSemaphore{
|
||||
max: max,
|
||||
available: max,
|
||||
}
|
||||
s.cond = sync.NewCond(&s.mut)
|
||||
return &s
|
||||
}
|
||||
|
||||
func (s *byteSemaphore) takeWithContext(ctx context.Context, bytes int) error {
|
||||
done := make(chan struct{})
|
||||
var err error
|
||||
go func() {
|
||||
err = s.takeInner(ctx, bytes)
|
||||
close(done)
|
||||
}()
|
||||
select {
|
||||
case <-done:
|
||||
case <-ctx.Done():
|
||||
s.cond.Broadcast()
|
||||
<-done
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *byteSemaphore) take(bytes int) {
|
||||
_ = s.takeInner(context.Background(), bytes)
|
||||
}
|
||||
|
||||
func (s *byteSemaphore) takeInner(ctx context.Context, bytes int) error {
|
||||
// Checking context for bytes <= s.available is required for testing and doesn't do any harm.
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
s.mut.Lock()
|
||||
defer s.mut.Unlock()
|
||||
if bytes > s.max {
|
||||
bytes = s.max
|
||||
}
|
||||
for bytes > s.available {
|
||||
s.cond.Wait()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
if bytes > s.max {
|
||||
bytes = s.max
|
||||
}
|
||||
}
|
||||
s.available -= bytes
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *byteSemaphore) give(bytes int) {
|
||||
s.mut.Lock()
|
||||
if bytes > s.max {
|
||||
bytes = s.max
|
||||
}
|
||||
if s.available+bytes > s.max {
|
||||
s.available = s.max
|
||||
} else {
|
||||
s.available += bytes
|
||||
}
|
||||
s.cond.Broadcast()
|
||||
s.mut.Unlock()
|
||||
}
|
||||
|
||||
func (s *byteSemaphore) setCapacity(cap int) {
|
||||
if cap < 0 {
|
||||
cap = 0
|
||||
}
|
||||
s.mut.Lock()
|
||||
diff := cap - s.max
|
||||
s.max = cap
|
||||
s.available += diff
|
||||
if s.available < 0 {
|
||||
s.available = 0
|
||||
} else if s.available > s.max {
|
||||
s.available = s.max
|
||||
}
|
||||
s.cond.Broadcast()
|
||||
s.mut.Unlock()
|
||||
}
|
||||
@@ -38,7 +38,7 @@ type folder struct {
|
||||
stateTracker
|
||||
config.FolderConfiguration
|
||||
*stats.FolderStatisticsReference
|
||||
ioLimiter *byteSemaphore
|
||||
ioLimiter *util.Semaphore
|
||||
|
||||
localFlags uint32
|
||||
|
||||
@@ -91,7 +91,7 @@ type puller interface {
|
||||
pull() (bool, error) // true when successful and should not be retried
|
||||
}
|
||||
|
||||
func newFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, evLogger events.Logger, ioLimiter *byteSemaphore, ver versioner.Versioner) folder {
|
||||
func newFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, evLogger events.Logger, ioLimiter *util.Semaphore, ver versioner.Versioner) folder {
|
||||
f := folder{
|
||||
stateTracker: newStateTracker(cfg.ID, evLogger),
|
||||
FolderConfiguration: cfg,
|
||||
@@ -375,10 +375,10 @@ func (f *folder) pull() (success bool, err error) {
|
||||
if f.Type != config.FolderTypeSendOnly {
|
||||
f.setState(FolderSyncWaiting)
|
||||
|
||||
if err := f.ioLimiter.takeWithContext(f.ctx, 1); err != nil {
|
||||
if err := f.ioLimiter.TakeWithContext(f.ctx, 1); err != nil {
|
||||
return true, err
|
||||
}
|
||||
defer f.ioLimiter.give(1)
|
||||
defer f.ioLimiter.Give(1)
|
||||
}
|
||||
|
||||
startTime := time.Now()
|
||||
@@ -439,10 +439,10 @@ func (f *folder) scanSubdirs(subDirs []string) error {
|
||||
f.setState(FolderScanWaiting)
|
||||
defer f.setState(FolderIdle)
|
||||
|
||||
if err := f.ioLimiter.takeWithContext(f.ctx, 1); err != nil {
|
||||
if err := f.ioLimiter.TakeWithContext(f.ctx, 1); err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.ioLimiter.give(1)
|
||||
defer f.ioLimiter.Give(1)
|
||||
|
||||
for i := range subDirs {
|
||||
sub := osutil.NativeFilename(subDirs[i])
|
||||
@@ -473,16 +473,7 @@ func (f *folder) scanSubdirs(subDirs []string) error {
|
||||
f.setState(FolderScanning)
|
||||
f.clearScanErrors(subDirs)
|
||||
|
||||
batch := db.NewFileInfoBatch(func(fs []protocol.FileInfo) error {
|
||||
if err := f.getHealthErrorWithoutIgnores(); err != nil {
|
||||
l.Debugf("Stopping scan of folder %s due to: %s", f.Description(), err)
|
||||
return err
|
||||
}
|
||||
f.updateLocalsFromScanning(fs)
|
||||
return nil
|
||||
})
|
||||
|
||||
batchAppend := f.scanSubdirsBatchAppendFunc(batch)
|
||||
batch := f.newScanBatch()
|
||||
|
||||
// Schedule a pull after scanning, but only if we actually detected any
|
||||
// changes.
|
||||
@@ -494,7 +485,7 @@ func (f *folder) scanSubdirs(subDirs []string) error {
|
||||
}
|
||||
}()
|
||||
|
||||
changesHere, err := f.scanSubdirsChangedAndNew(subDirs, batch, batchAppend)
|
||||
changesHere, err := f.scanSubdirsChangedAndNew(subDirs, batch)
|
||||
changes += changesHere
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -513,7 +504,7 @@ func (f *folder) scanSubdirs(subDirs []string) error {
|
||||
// Do a scan of the database for each prefix, to check for deleted and
|
||||
// ignored files.
|
||||
|
||||
changesHere, err = f.scanSubdirsDeletedAndIgnored(subDirs, batch, batchAppend)
|
||||
changesHere, err = f.scanSubdirsDeletedAndIgnored(subDirs, batch)
|
||||
changes += changesHere
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -527,58 +518,57 @@ func (f *folder) scanSubdirs(subDirs []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type batchAppendFunc func(protocol.FileInfo, *db.Snapshot) bool
|
||||
|
||||
func (f *folder) scanSubdirsBatchAppendFunc(batch *db.FileInfoBatch) batchAppendFunc {
|
||||
// Resolve items which are identical with the global state.
|
||||
switch f.Type {
|
||||
case config.FolderTypeReceiveOnly:
|
||||
return func(fi protocol.FileInfo, snap *db.Snapshot) bool {
|
||||
switch gf, ok := snap.GetGlobal(fi.Name); {
|
||||
case !ok:
|
||||
case gf.IsEquivalentOptional(fi, f.modTimeWindow, false, false, protocol.FlagLocalReceiveOnly):
|
||||
// What we have locally is equivalent to the global file.
|
||||
fi.Version = gf.Version
|
||||
l.Debugf("%v scanning: Merging identical locally changed item with global", f, fi)
|
||||
fallthrough
|
||||
case fi.IsDeleted() && (gf.IsReceiveOnlyChanged() || gf.IsDeleted()):
|
||||
// Our item is deleted and the global item is our own
|
||||
// receive only file or deleted too. In the former
|
||||
// case we can't delete file infos, so we just
|
||||
// pretend it is a normal deleted file (nobody
|
||||
// cares about that).
|
||||
l.Debugf("%v scanning: Marking item as not locally changed", f, fi)
|
||||
fi.LocalFlags &^= protocol.FlagLocalReceiveOnly
|
||||
}
|
||||
batch.Append(fi)
|
||||
return true
|
||||
}
|
||||
case config.FolderTypeReceiveEncrypted:
|
||||
return func(fi protocol.FileInfo, _ *db.Snapshot) bool {
|
||||
// This is a "virtual" parent directory of encrypted files.
|
||||
// We don't track it, but check if anything still exists
|
||||
// within and delete it otherwise.
|
||||
if fi.IsDirectory() && protocol.IsEncryptedParent(fs.PathComponents(fi.Name)) {
|
||||
if names, err := f.mtimefs.DirNames(fi.Name); err == nil && len(names) == 0 {
|
||||
f.mtimefs.Remove(fi.Name)
|
||||
}
|
||||
return false
|
||||
}
|
||||
// Any local change must not be sent as index entry to
|
||||
// remotes and show up as an error in the UI.
|
||||
fi.LocalFlags = protocol.FlagLocalReceiveOnly
|
||||
batch.Append(fi)
|
||||
return true
|
||||
}
|
||||
default:
|
||||
return func(fi protocol.FileInfo, _ *db.Snapshot) bool {
|
||||
batch.Append(fi)
|
||||
return true
|
||||
}
|
||||
}
|
||||
type scanBatch struct {
|
||||
*db.FileInfoBatch
|
||||
f *folder
|
||||
}
|
||||
|
||||
func (f *folder) scanSubdirsChangedAndNew(subDirs []string, batch *db.FileInfoBatch, batchAppend batchAppendFunc) (int, error) {
|
||||
func (f *folder) newScanBatch() *scanBatch {
|
||||
b := &scanBatch{
|
||||
f: f,
|
||||
}
|
||||
b.FileInfoBatch = db.NewFileInfoBatch(func(fs []protocol.FileInfo) error {
|
||||
if err := b.f.getHealthErrorWithoutIgnores(); err != nil {
|
||||
l.Debugf("Stopping scan of folder %s due to: %s", b.f.Description(), err)
|
||||
return err
|
||||
}
|
||||
b.f.updateLocalsFromScanning(fs)
|
||||
return nil
|
||||
})
|
||||
return b
|
||||
}
|
||||
|
||||
// Append adds the fileinfo to the batch for updating, and does a few checks.
|
||||
// It returns false if the checks result in the file not going to be updated or removed.
|
||||
func (b *scanBatch) Append(fi protocol.FileInfo, snap *db.Snapshot) bool {
|
||||
// Check for a "virtual" parent directory of encrypted files. We don't track
|
||||
// it, but check if anything still exists within and delete it otherwise.
|
||||
if b.f.Type == config.FolderTypeReceiveEncrypted && fi.IsDirectory() && protocol.IsEncryptedParent(fs.PathComponents(fi.Name)) {
|
||||
if names, err := b.f.mtimefs.DirNames(fi.Name); err == nil && len(names) == 0 {
|
||||
b.f.mtimefs.Remove(fi.Name)
|
||||
}
|
||||
return false
|
||||
}
|
||||
// Resolve receive-only items which are identical with the global state or
|
||||
// the global item is our own receive-only item.
|
||||
// Except if they are in a receive-encrypted folder and are locally added.
|
||||
// Those must never be sent in index updates and thus must retain the flag.
|
||||
switch gf, ok := snap.GetGlobal(fi.Name); {
|
||||
case !ok:
|
||||
case gf.IsReceiveOnlyChanged():
|
||||
if b.f.Type == config.FolderTypeReceiveOnly && fi.Deleted {
|
||||
l.Debugf("%v scanning: Marking deleted item as not locally changed", b.f, fi)
|
||||
fi.LocalFlags &^= protocol.FlagLocalReceiveOnly
|
||||
}
|
||||
case gf.IsEquivalentOptional(fi, b.f.modTimeWindow, false, false, protocol.FlagLocalReceiveOnly):
|
||||
l.Debugf("%v scanning: Replacing scanned file info with global as it's equivalent", b.f, fi)
|
||||
fi = gf
|
||||
}
|
||||
b.FileInfoBatch.Append(fi)
|
||||
return true
|
||||
}
|
||||
|
||||
func (f *folder) scanSubdirsChangedAndNew(subDirs []string, batch *scanBatch) (int, error) {
|
||||
changes := 0
|
||||
snap, err := f.dbSnapshot()
|
||||
if err != nil {
|
||||
@@ -630,7 +620,7 @@ func (f *folder) scanSubdirsChangedAndNew(subDirs []string, batch *db.FileInfoBa
|
||||
return changes, err
|
||||
}
|
||||
|
||||
if batchAppend(res.File, snap) {
|
||||
if batch.Append(res.File, snap) {
|
||||
changes++
|
||||
}
|
||||
|
||||
@@ -638,7 +628,7 @@ func (f *folder) scanSubdirsChangedAndNew(subDirs []string, batch *db.FileInfoBa
|
||||
case config.FolderTypeReceiveOnly, config.FolderTypeReceiveEncrypted:
|
||||
default:
|
||||
if nf, ok := f.findRename(snap, res.File, alreadyUsedOrExisting); ok {
|
||||
if batchAppend(nf, snap) {
|
||||
if batch.Append(nf, snap) {
|
||||
changes++
|
||||
}
|
||||
}
|
||||
@@ -648,7 +638,7 @@ func (f *folder) scanSubdirsChangedAndNew(subDirs []string, batch *db.FileInfoBa
|
||||
return changes, nil
|
||||
}
|
||||
|
||||
func (f *folder) scanSubdirsDeletedAndIgnored(subDirs []string, batch *db.FileInfoBatch, batchAppend batchAppendFunc) (int, error) {
|
||||
func (f *folder) scanSubdirsDeletedAndIgnored(subDirs []string, batch *scanBatch) (int, error) {
|
||||
var toIgnore []db.FileInfoTruncated
|
||||
ignoredParent := ""
|
||||
changes := 0
|
||||
@@ -679,7 +669,7 @@ func (f *folder) scanSubdirsDeletedAndIgnored(subDirs []string, batch *db.FileIn
|
||||
for _, file := range toIgnore {
|
||||
l.Debugln("marking file as ignored", file)
|
||||
nf := file.ConvertToIgnoredFileInfo()
|
||||
if batchAppend(nf, snap) {
|
||||
if batch.Append(nf, snap) {
|
||||
changes++
|
||||
}
|
||||
if err := batch.FlushIfFull(); err != nil {
|
||||
@@ -709,7 +699,7 @@ func (f *folder) scanSubdirsDeletedAndIgnored(subDirs []string, batch *db.FileIn
|
||||
|
||||
l.Debugln("marking file as ignored", file)
|
||||
nf := file.ConvertToIgnoredFileInfo()
|
||||
if batchAppend(nf, snap) {
|
||||
if batch.Append(nf, snap) {
|
||||
changes++
|
||||
}
|
||||
|
||||
@@ -739,23 +729,35 @@ func (f *folder) scanSubdirsDeletedAndIgnored(subDirs []string, batch *db.FileIn
|
||||
nf.Version = protocol.Vector{}
|
||||
}
|
||||
l.Debugln("marking file as deleted", nf)
|
||||
if batchAppend(nf, snap) {
|
||||
if batch.Append(nf, snap) {
|
||||
changes++
|
||||
}
|
||||
case file.IsDeleted() && file.IsReceiveOnlyChanged() && f.Type == config.FolderTypeReceiveOnly && len(snap.Availability(file.Name)) == 0:
|
||||
file.Version = protocol.Vector{}
|
||||
file.LocalFlags &^= protocol.FlagLocalReceiveOnly
|
||||
l.Debugln("marking deleted item that doesn't exist anywhere as not receive-only", file)
|
||||
if batchAppend(file.ConvertDeletedToFileInfo(), snap) {
|
||||
changes++
|
||||
}
|
||||
case file.IsDeleted() && file.IsReceiveOnlyChanged() && f.Type != config.FolderTypeReceiveOnly:
|
||||
// No need to bump the version for a file that was and is
|
||||
// deleted and just the folder type/local flags changed.
|
||||
file.LocalFlags &^= protocol.FlagLocalReceiveOnly
|
||||
l.Debugln("removing receive-only flag on deleted item", file)
|
||||
if batchAppend(file.ConvertDeletedToFileInfo(), snap) {
|
||||
changes++
|
||||
case file.IsDeleted() && file.IsReceiveOnlyChanged():
|
||||
switch f.Type {
|
||||
case config.FolderTypeReceiveOnly, config.FolderTypeReceiveEncrypted:
|
||||
if gf, _ := snap.GetGlobal(file.Name); gf.IsDeleted() {
|
||||
// Our item is deleted and the global item is deleted too. We just
|
||||
// pretend it is a normal deleted file (nobody cares about that).
|
||||
// Except if this is a receive-encrypted folder and it
|
||||
// is a locally added file. Those must never be sent
|
||||
// in index updates and thus must retain the flag.
|
||||
if f.Type == config.FolderTypeReceiveEncrypted && gf.IsReceiveOnlyChanged() {
|
||||
return true
|
||||
}
|
||||
l.Debugf("%v scanning: Marking globally deleted item as not locally changed: %v", f, file.Name)
|
||||
file.LocalFlags &^= protocol.FlagLocalReceiveOnly
|
||||
if batch.Append(file.ConvertDeletedToFileInfo(), snap) {
|
||||
changes++
|
||||
}
|
||||
}
|
||||
default:
|
||||
// No need to bump the version for a file that was and is
|
||||
// deleted and just the folder type/local flags changed.
|
||||
file.LocalFlags &^= protocol.FlagLocalReceiveOnly
|
||||
l.Debugln("removing receive-only flag on deleted item", file)
|
||||
if batch.Append(file.ConvertDeletedToFileInfo(), snap) {
|
||||
changes++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -772,7 +774,7 @@ func (f *folder) scanSubdirsDeletedAndIgnored(subDirs []string, batch *db.FileIn
|
||||
for _, file := range toIgnore {
|
||||
l.Debugln("marking file as ignored", f)
|
||||
nf := file.ConvertToIgnoredFileInfo()
|
||||
if batchAppend(nf, snap) {
|
||||
if batch.Append(nf, snap) {
|
||||
changes++
|
||||
}
|
||||
if iterError = batch.FlushIfFull(); iterError != nil {
|
||||
@@ -870,10 +872,10 @@ func (f *folder) versionCleanupTimerFired() {
|
||||
f.setState(FolderCleanWaiting)
|
||||
defer f.setState(FolderIdle)
|
||||
|
||||
if err := f.ioLimiter.takeWithContext(f.ctx, 1); err != nil {
|
||||
if err := f.ioLimiter.TakeWithContext(f.ctx, 1); err != nil {
|
||||
return
|
||||
}
|
||||
defer f.ioLimiter.give(1)
|
||||
defer f.ioLimiter.Give(1)
|
||||
|
||||
f.setState(FolderCleaning)
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ import (
|
||||
"github.com/syncthing/syncthing/lib/fs"
|
||||
"github.com/syncthing/syncthing/lib/ignore"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syncthing/syncthing/lib/util"
|
||||
"github.com/syncthing/syncthing/lib/versioner"
|
||||
)
|
||||
|
||||
@@ -27,8 +28,10 @@ type receiveEncryptedFolder struct {
|
||||
*sendReceiveFolder
|
||||
}
|
||||
|
||||
func newReceiveEncryptedFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, ver versioner.Versioner, evLogger events.Logger, ioLimiter *byteSemaphore) service {
|
||||
return &receiveEncryptedFolder{newSendReceiveFolder(model, fset, ignores, cfg, ver, evLogger, ioLimiter).(*sendReceiveFolder)}
|
||||
func newReceiveEncryptedFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, ver versioner.Versioner, evLogger events.Logger, ioLimiter *util.Semaphore) service {
|
||||
f := &receiveEncryptedFolder{newSendReceiveFolder(model, fset, ignores, cfg, ver, evLogger, ioLimiter).(*sendReceiveFolder)}
|
||||
f.localFlags = protocol.FlagLocalReceiveOnly // gets propagated to the scanner, and set on locally changed files
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *receiveEncryptedFolder) Revert() {
|
||||
@@ -108,5 +111,6 @@ func (f *receiveEncryptedFolder) revertHandleDirs(dirs []string, snap *db.Snapsh
|
||||
if err := f.deleteDirOnDisk(dir, snap, scanChan); err != nil {
|
||||
f.newScanError(dir, fmt.Errorf("deleting unexpected dir: %w", err))
|
||||
}
|
||||
scanChan <- dir
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/syncthing/syncthing/lib/events"
|
||||
"github.com/syncthing/syncthing/lib/ignore"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syncthing/syncthing/lib/util"
|
||||
"github.com/syncthing/syncthing/lib/versioner"
|
||||
)
|
||||
|
||||
@@ -56,7 +57,7 @@ type receiveOnlyFolder struct {
|
||||
*sendReceiveFolder
|
||||
}
|
||||
|
||||
func newReceiveOnlyFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, ver versioner.Versioner, evLogger events.Logger, ioLimiter *byteSemaphore) service {
|
||||
func newReceiveOnlyFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, ver versioner.Versioner, evLogger events.Logger, ioLimiter *util.Semaphore) service {
|
||||
sr := newSendReceiveFolder(model, fset, ignores, cfg, ver, evLogger, ioLimiter).(*sendReceiveFolder)
|
||||
sr.localFlags = protocol.FlagLocalReceiveOnly // gets propagated to the scanner, and set on locally changed files
|
||||
return &receiveOnlyFolder{sr}
|
||||
@@ -111,7 +112,10 @@ func (f *receiveOnlyFolder) revert() error {
|
||||
// The global file is our own. A revert then means to delete it.
|
||||
// We'll delete files directly, directories get queued and
|
||||
// handled below.
|
||||
|
||||
if fi.Deleted {
|
||||
fi.Version = protocol.Vector{} // if this file ever resurfaces anywhere we want our delete to be strictly older
|
||||
break
|
||||
}
|
||||
handled, err := delQueue.handle(fi, snap)
|
||||
if err != nil {
|
||||
l.Infof("Revert: deleting %s: %v\n", fi.Name, err)
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/syncthing/syncthing/lib/events"
|
||||
"github.com/syncthing/syncthing/lib/ignore"
|
||||
"github.com/syncthing/syncthing/lib/protocol"
|
||||
"github.com/syncthing/syncthing/lib/util"
|
||||
"github.com/syncthing/syncthing/lib/versioner"
|
||||
)
|
||||
|
||||
@@ -23,7 +24,7 @@ type sendOnlyFolder struct {
|
||||
folder
|
||||
}
|
||||
|
||||
func newSendOnlyFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, _ versioner.Versioner, evLogger events.Logger, ioLimiter *byteSemaphore) service {
|
||||
func newSendOnlyFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, _ versioner.Versioner, evLogger events.Logger, ioLimiter *util.Semaphore) service {
|
||||
f := &sendOnlyFolder{
|
||||
folder: newFolder(model, fset, ignores, cfg, evLogger, ioLimiter, nil),
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"github.com/syncthing/syncthing/lib/scanner"
|
||||
"github.com/syncthing/syncthing/lib/sha256"
|
||||
"github.com/syncthing/syncthing/lib/sync"
|
||||
"github.com/syncthing/syncthing/lib/util"
|
||||
"github.com/syncthing/syncthing/lib/versioner"
|
||||
"github.com/syncthing/syncthing/lib/weakhash"
|
||||
)
|
||||
@@ -123,17 +124,17 @@ type sendReceiveFolder struct {
|
||||
|
||||
queue *jobQueue
|
||||
blockPullReorderer blockPullReorderer
|
||||
writeLimiter *byteSemaphore
|
||||
writeLimiter *util.Semaphore
|
||||
|
||||
tempPullErrors map[string]string // pull errors that might be just transient
|
||||
}
|
||||
|
||||
func newSendReceiveFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, ver versioner.Versioner, evLogger events.Logger, ioLimiter *byteSemaphore) service {
|
||||
func newSendReceiveFolder(model *model, fset *db.FileSet, ignores *ignore.Matcher, cfg config.FolderConfiguration, ver versioner.Versioner, evLogger events.Logger, ioLimiter *util.Semaphore) service {
|
||||
f := &sendReceiveFolder{
|
||||
folder: newFolder(model, fset, ignores, cfg, evLogger, ioLimiter, ver),
|
||||
queue: newJobQueue(),
|
||||
blockPullReorderer: newBlockPullReorderer(cfg.BlockPullOrder, model.id, cfg.DeviceIDs()),
|
||||
writeLimiter: newByteSemaphore(cfg.MaxConcurrentWrites),
|
||||
writeLimiter: util.NewSemaphore(cfg.MaxConcurrentWrites),
|
||||
}
|
||||
f.folder.puller = f
|
||||
|
||||
@@ -1435,7 +1436,7 @@ func (f *sendReceiveFolder) verifyBuffer(buf []byte, block protocol.BlockInfo) e
|
||||
}
|
||||
|
||||
func (f *sendReceiveFolder) pullerRoutine(snap *db.Snapshot, in <-chan pullBlockState, out chan<- *sharedPullerState) {
|
||||
requestLimiter := newByteSemaphore(f.PullerMaxPendingKiB * 1024)
|
||||
requestLimiter := util.NewSemaphore(f.PullerMaxPendingKiB * 1024)
|
||||
wg := sync.NewWaitGroup()
|
||||
|
||||
for state := range in {
|
||||
@@ -1453,7 +1454,7 @@ func (f *sendReceiveFolder) pullerRoutine(snap *db.Snapshot, in <-chan pullBlock
|
||||
state := state
|
||||
bytes := int(state.block.Size)
|
||||
|
||||
if err := requestLimiter.takeWithContext(f.ctx, bytes); err != nil {
|
||||
if err := requestLimiter.TakeWithContext(f.ctx, bytes); err != nil {
|
||||
state.fail(err)
|
||||
out <- state.sharedPullerState
|
||||
continue
|
||||
@@ -1463,7 +1464,7 @@ func (f *sendReceiveFolder) pullerRoutine(snap *db.Snapshot, in <-chan pullBlock
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer requestLimiter.give(bytes)
|
||||
defer requestLimiter.Give(bytes)
|
||||
|
||||
f.pullBlock(state, snap, out)
|
||||
}()
|
||||
@@ -2085,10 +2086,10 @@ func (f *sendReceiveFolder) limitedWriteAt(fd io.WriterAt, data []byte, offset i
|
||||
}
|
||||
|
||||
func (f *sendReceiveFolder) withLimiter(fn func() error) error {
|
||||
if err := f.writeLimiter.takeWithContext(f.ctx, 1); err != nil {
|
||||
if err := f.writeLimiter.TakeWithContext(f.ctx, 1); err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.writeLimiter.give(1)
|
||||
defer f.writeLimiter.Give(1)
|
||||
return fn()
|
||||
}
|
||||
|
||||
|
||||
@@ -111,20 +111,47 @@ func newIndexHandler(conn protocol.Connection, downloads *deviceDownloadState, f
|
||||
}
|
||||
}
|
||||
|
||||
// waitForFileset waits for the handler to resume and fetches the current fileset.
|
||||
func (s *indexHandler) waitForFileset(ctx context.Context) (*db.FileSet, error) {
|
||||
s.cond.L.Lock()
|
||||
defer s.cond.L.Unlock()
|
||||
|
||||
for s.paused {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, ctx.Err()
|
||||
default:
|
||||
s.cond.Wait()
|
||||
}
|
||||
}
|
||||
|
||||
return s.fset, nil
|
||||
}
|
||||
|
||||
func (s *indexHandler) Serve(ctx context.Context) (err error) {
|
||||
l.Debugf("Starting index handler for %s to %s at %s (slv=%d)", s.folder, s.conn.ID(), s.conn, s.prevSequence)
|
||||
stop := make(chan struct{})
|
||||
|
||||
defer func() {
|
||||
err = svcutil.NoRestartErr(err)
|
||||
l.Debugf("Exiting index handler for %s to %s at %s: %v", s.folder, s.conn.ID(), s.conn, err)
|
||||
close(stop)
|
||||
}()
|
||||
|
||||
// Broadcast the pause cond when the context quits
|
||||
go func() {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
s.cond.Broadcast()
|
||||
case <-stop:
|
||||
}
|
||||
}()
|
||||
|
||||
// We need to send one index, regardless of whether there is something to send or not
|
||||
s.cond.L.Lock()
|
||||
for s.paused {
|
||||
s.cond.Wait()
|
||||
fset, err := s.waitForFileset(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fset := s.fset
|
||||
s.cond.L.Unlock()
|
||||
err = s.sendIndexTo(ctx, fset)
|
||||
|
||||
// Subscribe to LocalIndexUpdated (we have new information to send) and
|
||||
@@ -138,12 +165,10 @@ func (s *indexHandler) Serve(ctx context.Context) (err error) {
|
||||
defer ticker.Stop()
|
||||
|
||||
for err == nil {
|
||||
s.cond.L.Lock()
|
||||
for s.paused {
|
||||
s.cond.Wait()
|
||||
fset, err = s.waitForFileset(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fset := s.fset
|
||||
s.cond.L.Unlock()
|
||||
|
||||
// While we have sent a sequence at least equal to the one
|
||||
// currently in the database, wait for the local index to update. The
|
||||
@@ -181,6 +206,7 @@ func (s *indexHandler) resume(fset *db.FileSet, runner service) {
|
||||
s.paused = false
|
||||
s.fset = fset
|
||||
s.runner = runner
|
||||
s.cond.Broadcast()
|
||||
s.cond.L.Unlock()
|
||||
}
|
||||
|
||||
@@ -192,6 +218,7 @@ func (s *indexHandler) pause() {
|
||||
s.paused = true
|
||||
s.fset = nil
|
||||
s.runner = nil
|
||||
s.cond.Broadcast()
|
||||
s.cond.L.Unlock()
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ import (
|
||||
"github.com/syncthing/syncthing/lib/svcutil"
|
||||
"github.com/syncthing/syncthing/lib/sync"
|
||||
"github.com/syncthing/syncthing/lib/ur/contract"
|
||||
"github.com/syncthing/syncthing/lib/util"
|
||||
"github.com/syncthing/syncthing/lib/versioner"
|
||||
)
|
||||
|
||||
@@ -132,10 +133,10 @@ type model struct {
|
||||
shortID protocol.ShortID
|
||||
// globalRequestLimiter limits the amount of data in concurrent incoming
|
||||
// requests
|
||||
globalRequestLimiter *byteSemaphore
|
||||
globalRequestLimiter *util.Semaphore
|
||||
// folderIOLimiter limits the number of concurrent I/O heavy operations,
|
||||
// such as scans and pulls.
|
||||
folderIOLimiter *byteSemaphore
|
||||
folderIOLimiter *util.Semaphore
|
||||
fatalChan chan error
|
||||
started chan struct{}
|
||||
|
||||
@@ -155,7 +156,7 @@ type model struct {
|
||||
// fields protected by pmut
|
||||
pmut sync.RWMutex
|
||||
conn map[protocol.DeviceID]protocol.Connection
|
||||
connRequestLimiters map[protocol.DeviceID]*byteSemaphore
|
||||
connRequestLimiters map[protocol.DeviceID]*util.Semaphore
|
||||
closed map[protocol.DeviceID]chan struct{}
|
||||
helloMessages map[protocol.DeviceID]protocol.Hello
|
||||
deviceDownloads map[protocol.DeviceID]*deviceDownloadState
|
||||
@@ -166,7 +167,7 @@ type model struct {
|
||||
foldersRunning int32
|
||||
}
|
||||
|
||||
type folderFactory func(*model, *db.FileSet, *ignore.Matcher, config.FolderConfiguration, versioner.Versioner, events.Logger, *byteSemaphore) service
|
||||
type folderFactory func(*model, *db.FileSet, *ignore.Matcher, config.FolderConfiguration, versioner.Versioner, events.Logger, *util.Semaphore) service
|
||||
|
||||
var (
|
||||
folderFactories = make(map[config.FolderType]folderFactory)
|
||||
@@ -220,8 +221,8 @@ func NewModel(cfg config.Wrapper, id protocol.DeviceID, clientName, clientVersio
|
||||
finder: db.NewBlockFinder(ldb),
|
||||
progressEmitter: NewProgressEmitter(cfg, evLogger),
|
||||
shortID: id.Short(),
|
||||
globalRequestLimiter: newByteSemaphore(1024 * cfg.Options().MaxConcurrentIncomingRequestKiB()),
|
||||
folderIOLimiter: newByteSemaphore(cfg.Options().MaxFolderConcurrency()),
|
||||
globalRequestLimiter: util.NewSemaphore(1024 * cfg.Options().MaxConcurrentIncomingRequestKiB()),
|
||||
folderIOLimiter: util.NewSemaphore(cfg.Options().MaxFolderConcurrency()),
|
||||
fatalChan: make(chan error),
|
||||
started: make(chan struct{}),
|
||||
|
||||
@@ -240,7 +241,7 @@ func NewModel(cfg config.Wrapper, id protocol.DeviceID, clientName, clientVersio
|
||||
// fields protected by pmut
|
||||
pmut: sync.NewRWMutex(),
|
||||
conn: make(map[protocol.DeviceID]protocol.Connection),
|
||||
connRequestLimiters: make(map[protocol.DeviceID]*byteSemaphore),
|
||||
connRequestLimiters: make(map[protocol.DeviceID]*util.Semaphore),
|
||||
closed: make(map[protocol.DeviceID]chan struct{}),
|
||||
helloMessages: make(map[protocol.DeviceID]protocol.Hello),
|
||||
deviceDownloads: make(map[protocol.DeviceID]*deviceDownloadState),
|
||||
@@ -1342,12 +1343,14 @@ func (m *model) ccHandleFolders(folders []protocol.Folder, deviceCfg config.Devi
|
||||
|
||||
if err := m.ccCheckEncryption(cfg, folderDevice, ccDeviceInfos[folder.ID], deviceCfg.Untrusted); err != nil {
|
||||
sameError := false
|
||||
m.fmut.Lock()
|
||||
if devs, ok := m.folderEncryptionFailures[folder.ID]; ok {
|
||||
sameError = devs[deviceID] == err
|
||||
} else {
|
||||
m.folderEncryptionFailures[folder.ID] = make(map[protocol.DeviceID]error)
|
||||
}
|
||||
m.folderEncryptionFailures[folder.ID][deviceID] = err
|
||||
m.fmut.Unlock()
|
||||
msg := fmt.Sprintf("Failure checking encryption consistency with device %v for folder %v: %v", deviceID, cfg.Description(), err)
|
||||
if sameError {
|
||||
l.Debugln(msg)
|
||||
@@ -1360,6 +1363,7 @@ func (m *model) ccHandleFolders(folders []protocol.Folder, deviceCfg config.Devi
|
||||
}
|
||||
return tempIndexFolders, paused, err
|
||||
}
|
||||
m.fmut.Lock()
|
||||
if devErrs, ok := m.folderEncryptionFailures[folder.ID]; ok {
|
||||
if len(devErrs) == 1 {
|
||||
delete(m.folderEncryptionFailures, folder.ID)
|
||||
@@ -1367,6 +1371,7 @@ func (m *model) ccHandleFolders(folders []protocol.Folder, deviceCfg config.Devi
|
||||
delete(m.folderEncryptionFailures[folder.ID], deviceID)
|
||||
}
|
||||
}
|
||||
m.fmut.Unlock()
|
||||
|
||||
// Handle indexes
|
||||
|
||||
@@ -1906,23 +1911,15 @@ func (m *model) Request(deviceID protocol.DeviceID, folder, name string, blockNo
|
||||
// skipping nil limiters, then returns a requestResponse of the given size.
|
||||
// When the requestResponse is closed the limiters are given back the bytes,
|
||||
// in reverse order.
|
||||
func newLimitedRequestResponse(size int, limiters ...*byteSemaphore) *requestResponse {
|
||||
for _, limiter := range limiters {
|
||||
if limiter != nil {
|
||||
limiter.take(size)
|
||||
}
|
||||
}
|
||||
func newLimitedRequestResponse(size int, limiters ...*util.Semaphore) *requestResponse {
|
||||
multi := util.MultiSemaphore(limiters)
|
||||
multi.Take(size)
|
||||
|
||||
res := newRequestResponse(size)
|
||||
|
||||
go func() {
|
||||
res.Wait()
|
||||
for i := range limiters {
|
||||
limiter := limiters[len(limiters)-1-i]
|
||||
if limiter != nil {
|
||||
limiter.give(size)
|
||||
}
|
||||
}
|
||||
multi.Give(size)
|
||||
}()
|
||||
|
||||
return res
|
||||
@@ -2230,9 +2227,9 @@ func (m *model) AddConnection(conn protocol.Connection, hello protocol.Hello) {
|
||||
// 0: default, <0: no limiting
|
||||
switch {
|
||||
case device.MaxRequestKiB > 0:
|
||||
m.connRequestLimiters[deviceID] = newByteSemaphore(1024 * device.MaxRequestKiB)
|
||||
m.connRequestLimiters[deviceID] = util.NewSemaphore(1024 * device.MaxRequestKiB)
|
||||
case device.MaxRequestKiB == 0:
|
||||
m.connRequestLimiters[deviceID] = newByteSemaphore(1024 * defaultPullerPendingKiB)
|
||||
m.connRequestLimiters[deviceID] = util.NewSemaphore(1024 * defaultPullerPendingKiB)
|
||||
}
|
||||
|
||||
m.helloMessages[deviceID] = hello
|
||||
@@ -2774,6 +2771,13 @@ func (m *model) String() string {
|
||||
}
|
||||
|
||||
func (m *model) VerifyConfiguration(from, to config.Configuration) error {
|
||||
toFolders := to.FolderMap()
|
||||
for _, from := range from.Folders {
|
||||
to, ok := toFolders[from.ID]
|
||||
if ok && from.Type != to.Type && (from.Type == config.FolderTypeReceiveEncrypted || to.Type == config.FolderTypeReceiveEncrypted) {
|
||||
return errors.New("folder type must not be changed from/to receive-encrypted")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -2927,8 +2931,8 @@ func (m *model) CommitConfiguration(from, to config.Configuration) bool {
|
||||
ignoredDevices := observedDeviceSet(to.IgnoredDevices)
|
||||
m.cleanPending(toDevices, toFolders, ignoredDevices, removedFolders)
|
||||
|
||||
m.globalRequestLimiter.setCapacity(1024 * to.Options.MaxConcurrentIncomingRequestKiB())
|
||||
m.folderIOLimiter.setCapacity(to.Options.MaxFolderConcurrency())
|
||||
m.globalRequestLimiter.SetCapacity(1024 * to.Options.MaxConcurrentIncomingRequestKiB())
|
||||
m.folderIOLimiter.SetCapacity(to.Options.MaxFolderConcurrency())
|
||||
|
||||
// Some options don't require restart as those components handle it fine
|
||||
// by themselves. Compare the options structs containing only the
|
||||
|
||||
@@ -38,6 +38,7 @@ import (
|
||||
protocolmocks "github.com/syncthing/syncthing/lib/protocol/mocks"
|
||||
srand "github.com/syncthing/syncthing/lib/rand"
|
||||
"github.com/syncthing/syncthing/lib/testutils"
|
||||
"github.com/syncthing/syncthing/lib/util"
|
||||
"github.com/syncthing/syncthing/lib/versioner"
|
||||
)
|
||||
|
||||
@@ -3319,14 +3320,14 @@ func TestDeviceWasSeen(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNewLimitedRequestResponse(t *testing.T) {
|
||||
l0 := newByteSemaphore(0)
|
||||
l1 := newByteSemaphore(1024)
|
||||
l2 := (*byteSemaphore)(nil)
|
||||
l0 := util.NewSemaphore(0)
|
||||
l1 := util.NewSemaphore(1024)
|
||||
l2 := (*util.Semaphore)(nil)
|
||||
|
||||
// Should take 500 bytes from any non-unlimited non-nil limiters.
|
||||
res := newLimitedRequestResponse(500, l0, l1, l2)
|
||||
|
||||
if l1.available != 1024-500 {
|
||||
if l1.Available() != 1024-500 {
|
||||
t.Error("should have taken bytes from limited limiter")
|
||||
}
|
||||
|
||||
@@ -3336,7 +3337,7 @@ func TestNewLimitedRequestResponse(t *testing.T) {
|
||||
// Try to take 1024 bytes to make sure the bytes were returned.
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
l1.take(1024)
|
||||
l1.Take(1024)
|
||||
close(done)
|
||||
}()
|
||||
select {
|
||||
|
||||
@@ -523,7 +523,7 @@ func (c *rawConnection) readMessageAfterHeader(hdr Header, fourByteBuf []byte) (
|
||||
// Nothing
|
||||
|
||||
case MessageCompressionLZ4:
|
||||
decomp, err := c.lz4Decompress(buf)
|
||||
decomp, err := lz4Decompress(buf)
|
||||
BufferPool.Put(buf)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "decompressing message")
|
||||
@@ -740,26 +740,56 @@ func (c *rawConnection) writerLoop() {
|
||||
func (c *rawConnection) writeMessage(msg message) error {
|
||||
msgContext, _ := messageContext(msg)
|
||||
l.Debugf("Writing %v", msgContext)
|
||||
if c.shouldCompressMessage(msg) {
|
||||
return c.writeCompressedMessage(msg)
|
||||
}
|
||||
return c.writeUncompressedMessage(msg)
|
||||
}
|
||||
|
||||
func (c *rawConnection) writeCompressedMessage(msg message) error {
|
||||
size := msg.ProtoSize()
|
||||
buf := BufferPool.Get(size)
|
||||
if _, err := msg.MarshalTo(buf); err != nil {
|
||||
BufferPool.Put(buf)
|
||||
hdr := Header{
|
||||
Type: c.typeOf(msg),
|
||||
}
|
||||
hdrSize := hdr.ProtoSize()
|
||||
if hdrSize > 1<<16-1 {
|
||||
panic("impossibly large header")
|
||||
}
|
||||
|
||||
overhead := 2 + hdrSize + 4
|
||||
totSize := overhead + size
|
||||
buf := BufferPool.Get(totSize)
|
||||
defer BufferPool.Put(buf)
|
||||
|
||||
// Message
|
||||
if _, err := msg.MarshalTo(buf[2+hdrSize+4:]); err != nil {
|
||||
return errors.Wrap(err, "marshalling message")
|
||||
}
|
||||
|
||||
compressed, err := c.lz4Compress(buf)
|
||||
if err != nil {
|
||||
BufferPool.Put(buf)
|
||||
return errors.Wrap(err, "compressing message")
|
||||
if c.shouldCompressMessage(msg) {
|
||||
ok, err := c.writeCompressedMessage(msg, buf[overhead:], overhead)
|
||||
if ok {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Header length
|
||||
binary.BigEndian.PutUint16(buf, uint16(hdrSize))
|
||||
// Header
|
||||
if _, err := hdr.MarshalTo(buf[2:]); err != nil {
|
||||
return errors.Wrap(err, "marshalling header")
|
||||
}
|
||||
// Message length
|
||||
binary.BigEndian.PutUint32(buf[2+hdrSize:], uint32(size))
|
||||
|
||||
n, err := c.cw.Write(buf)
|
||||
|
||||
l.Debugf("wrote %d bytes on the wire (2 bytes length, %d bytes header, 4 bytes message length, %d bytes message), err=%v", n, hdrSize, size, err)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "writing message")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write msg out compressed, given its uncompressed marshaled payload and overhead.
|
||||
//
|
||||
// The first return value indicates whether compression succeeded.
|
||||
// If not, the caller should retry without compression.
|
||||
func (c *rawConnection) writeCompressedMessage(msg message, marshaled []byte, overhead int) (ok bool, err error) {
|
||||
hdr := Header{
|
||||
Type: c.typeOf(msg),
|
||||
Compression: MessageCompressionLZ4,
|
||||
@@ -769,71 +799,32 @@ func (c *rawConnection) writeCompressedMessage(msg message) error {
|
||||
panic("impossibly large header")
|
||||
}
|
||||
|
||||
compressedSize := len(compressed)
|
||||
totSize := 2 + hdrSize + 4 + compressedSize
|
||||
buf = BufferPool.Upgrade(buf, totSize)
|
||||
cOverhead := 2 + hdrSize + 4
|
||||
maxCompressed := cOverhead + lz4.CompressBound(len(marshaled))
|
||||
buf := BufferPool.Get(maxCompressed)
|
||||
defer BufferPool.Put(buf)
|
||||
|
||||
compressedSize, err := lz4Compress(marshaled, buf[cOverhead:])
|
||||
totSize := compressedSize + cOverhead
|
||||
if err != nil || totSize >= len(marshaled)+overhead {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Header length
|
||||
binary.BigEndian.PutUint16(buf, uint16(hdrSize))
|
||||
// Header
|
||||
if _, err := hdr.MarshalTo(buf[2:]); err != nil {
|
||||
BufferPool.Put(buf)
|
||||
BufferPool.Put(compressed)
|
||||
return errors.Wrap(err, "marshalling header")
|
||||
return true, errors.Wrap(err, "marshalling header")
|
||||
}
|
||||
// Message length
|
||||
binary.BigEndian.PutUint32(buf[2+hdrSize:], uint32(compressedSize))
|
||||
// Message
|
||||
copy(buf[2+hdrSize+4:], compressed)
|
||||
BufferPool.Put(compressed)
|
||||
|
||||
n, err := c.cw.Write(buf)
|
||||
BufferPool.Put(buf)
|
||||
|
||||
l.Debugf("wrote %d bytes on the wire (2 bytes length, %d bytes header, 4 bytes message length, %d bytes message (%d uncompressed)), err=%v", n, hdrSize, compressedSize, size, err)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "writing message")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *rawConnection) writeUncompressedMessage(msg message) error {
|
||||
size := msg.ProtoSize()
|
||||
|
||||
hdr := Header{
|
||||
Type: c.typeOf(msg),
|
||||
}
|
||||
hdrSize := hdr.ProtoSize()
|
||||
if hdrSize > 1<<16-1 {
|
||||
panic("impossibly large header")
|
||||
}
|
||||
|
||||
totSize := 2 + hdrSize + 4 + size
|
||||
buf := BufferPool.Get(totSize)
|
||||
|
||||
// Header length
|
||||
binary.BigEndian.PutUint16(buf, uint16(hdrSize))
|
||||
// Header
|
||||
if _, err := hdr.MarshalTo(buf[2:]); err != nil {
|
||||
BufferPool.Put(buf)
|
||||
return errors.Wrap(err, "marshalling header")
|
||||
}
|
||||
// Message length
|
||||
binary.BigEndian.PutUint32(buf[2+hdrSize:], uint32(size))
|
||||
// Message
|
||||
if _, err := msg.MarshalTo(buf[2+hdrSize+4:]); err != nil {
|
||||
BufferPool.Put(buf)
|
||||
return errors.Wrap(err, "marshalling message")
|
||||
}
|
||||
|
||||
n, err := c.cw.Write(buf[:totSize])
|
||||
BufferPool.Put(buf)
|
||||
|
||||
l.Debugf("wrote %d bytes on the wire (2 bytes length, %d bytes header, 4 bytes message length, %d bytes message), err=%v", n, hdrSize, size, err)
|
||||
l.Debugf("wrote %d bytes on the wire (2 bytes length, %d bytes header, 4 bytes message length, %d bytes message (%d uncompressed)), err=%v", n, hdrSize, compressedSize, len(marshaled), err)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "writing message")
|
||||
return true, errors.Wrap(err, "writing message")
|
||||
}
|
||||
return nil
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (c *rawConnection) typeOf(msg message) MessageType {
|
||||
@@ -1018,23 +1009,20 @@ func (c *rawConnection) Statistics() Statistics {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *rawConnection) lz4Compress(src []byte) ([]byte, error) {
|
||||
var err error
|
||||
buf := BufferPool.Get(lz4.CompressBound(len(src)))
|
||||
func lz4Compress(src, buf []byte) (int, error) {
|
||||
compressed, err := lz4.Encode(buf, src)
|
||||
if err != nil {
|
||||
BufferPool.Put(buf)
|
||||
return nil, err
|
||||
return -1, err
|
||||
}
|
||||
if &compressed[0] != &buf[0] {
|
||||
panic("bug: lz4.Compress allocated, which it must not (should use buffer pool)")
|
||||
}
|
||||
|
||||
binary.BigEndian.PutUint32(compressed, binary.LittleEndian.Uint32(compressed))
|
||||
return compressed, nil
|
||||
return len(compressed), nil
|
||||
}
|
||||
|
||||
func (c *rawConnection) lz4Decompress(src []byte) ([]byte, error) {
|
||||
func lz4Decompress(src []byte) ([]byte, error) {
|
||||
size := binary.BigEndian.Uint32(src)
|
||||
binary.LittleEndian.PutUint32(src, size)
|
||||
var err error
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"testing/quick"
|
||||
"time"
|
||||
|
||||
lz4 "github.com/bkaradzic/go-lz4"
|
||||
"github.com/syncthing/syncthing/lib/rand"
|
||||
"github.com/syncthing/syncthing/lib/testutils"
|
||||
)
|
||||
@@ -439,9 +440,42 @@ func testMarshal(t *testing.T, prefix string, m1, m2 message) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func TestLZ4Compression(t *testing.T) {
|
||||
c := new(rawConnection)
|
||||
func TestWriteCompressed(t *testing.T) {
|
||||
for _, random := range []bool{false, true} {
|
||||
buf := new(bytes.Buffer)
|
||||
c := &rawConnection{
|
||||
cr: &countingReader{Reader: buf},
|
||||
cw: &countingWriter{Writer: buf},
|
||||
compression: CompressionAlways,
|
||||
}
|
||||
|
||||
msg := &Response{Data: make([]byte, 10240)}
|
||||
if random {
|
||||
// This should make the message uncompressible.
|
||||
rand.Read(msg.Data)
|
||||
}
|
||||
|
||||
if err := c.writeMessage(msg); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got, err := c.readMessage(make([]byte, 4))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(got.(*Response).Data, msg.Data) {
|
||||
t.Error("received the wrong message")
|
||||
}
|
||||
|
||||
hdr := Header{Type: c.typeOf(msg)}
|
||||
size := int64(2 + hdr.ProtoSize() + 4 + msg.ProtoSize())
|
||||
if c.cr.tot > size {
|
||||
t.Errorf("compression enlarged message from %d to %d",
|
||||
size, c.cr.tot)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLZ4Compression(t *testing.T) {
|
||||
for i := 0; i < 10; i++ {
|
||||
dataLen := 150 + rand.Intn(150)
|
||||
data := make([]byte, dataLen)
|
||||
@@ -449,13 +483,15 @@ func TestLZ4Compression(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
comp, err := c.lz4Compress(data)
|
||||
|
||||
comp := make([]byte, lz4.CompressBound(dataLen))
|
||||
compLen, err := lz4Compress(data, comp)
|
||||
if err != nil {
|
||||
t.Errorf("compressing %d bytes: %v", dataLen, err)
|
||||
continue
|
||||
}
|
||||
|
||||
res, err := c.lz4Decompress(comp)
|
||||
res, err := lz4Decompress(comp[:compLen])
|
||||
if err != nil {
|
||||
t.Errorf("decompressing %d bytes to %d: %v", len(comp), dataLen, err)
|
||||
continue
|
||||
@@ -470,38 +506,6 @@ func TestLZ4Compression(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStressLZ4CompressGrows(t *testing.T) {
|
||||
c := new(rawConnection)
|
||||
success := 0
|
||||
for i := 0; i < 100; i++ {
|
||||
// Create a slize that is precisely one min block size, fill it with
|
||||
// random data. This shouldn't compress at all, so will in fact
|
||||
// become larger when LZ4 does its thing.
|
||||
data := make([]byte, MinBlockSize)
|
||||
if _, err := rand.Reader.Read(data); err != nil {
|
||||
t.Fatal("randomness failure")
|
||||
}
|
||||
|
||||
comp, err := c.lz4Compress(data)
|
||||
if err != nil {
|
||||
t.Fatal("unexpected compression error: ", err)
|
||||
}
|
||||
if len(comp) < len(data) {
|
||||
// data size should grow. We must have been really unlucky in
|
||||
// the random generation, try again.
|
||||
continue
|
||||
}
|
||||
|
||||
// Putting it into the buffer pool shouldn't panic because the block
|
||||
// should come from there to begin with.
|
||||
BufferPool.Put(comp)
|
||||
success++
|
||||
}
|
||||
if success == 0 {
|
||||
t.Fatal("unable to find data that grows when compressed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCheckFilename(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
|
||||
@@ -11,58 +11,43 @@ import (
|
||||
|
||||
"github.com/syncthing/syncthing/lib/relay/protocol"
|
||||
"github.com/syncthing/syncthing/lib/svcutil"
|
||||
"github.com/syncthing/syncthing/lib/sync"
|
||||
|
||||
"github.com/thejerf/suture/v4"
|
||||
)
|
||||
|
||||
type relayClientFactory func(uri *url.URL, certs []tls.Certificate, invitations chan protocol.SessionInvitation, timeout time.Duration) RelayClient
|
||||
|
||||
var (
|
||||
supportedSchemes = map[string]relayClientFactory{
|
||||
"relay": newStaticClient,
|
||||
"dynamic+http": newDynamicClient,
|
||||
"dynamic+https": newDynamicClient,
|
||||
}
|
||||
)
|
||||
|
||||
type RelayClient interface {
|
||||
suture.Service
|
||||
Error() error
|
||||
Latency() time.Duration
|
||||
String() string
|
||||
Invitations() chan protocol.SessionInvitation
|
||||
Invitations() <-chan protocol.SessionInvitation
|
||||
URI() *url.URL
|
||||
}
|
||||
|
||||
func NewClient(uri *url.URL, certs []tls.Certificate, timeout time.Duration) (RelayClient, error) {
|
||||
factory, ok := supportedSchemes[uri.Scheme]
|
||||
if !ok {
|
||||
invitations := make(chan protocol.SessionInvitation)
|
||||
|
||||
switch uri.Scheme {
|
||||
case "relay":
|
||||
return newStaticClient(uri, certs, invitations, timeout), nil
|
||||
case "dynamic+http", "dynamic+https":
|
||||
return newDynamicClient(uri, certs, invitations, timeout), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported scheme: %s", uri.Scheme)
|
||||
}
|
||||
|
||||
invitations := make(chan protocol.SessionInvitation)
|
||||
return factory(uri, certs, invitations, timeout), nil
|
||||
}
|
||||
|
||||
type commonClient struct {
|
||||
svcutil.ServiceWithError
|
||||
|
||||
invitations chan protocol.SessionInvitation
|
||||
mut sync.RWMutex
|
||||
}
|
||||
|
||||
func newCommonClient(invitations chan protocol.SessionInvitation, serve func(context.Context) error, creator string) commonClient {
|
||||
c := commonClient{
|
||||
invitations: invitations,
|
||||
mut: sync.NewRWMutex(),
|
||||
return commonClient{
|
||||
ServiceWithError: svcutil.AsService(serve, creator),
|
||||
invitations: invitations,
|
||||
}
|
||||
c.ServiceWithError = svcutil.AsService(serve, creator)
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *commonClient) Invitations() chan protocol.SessionInvitation {
|
||||
c.mut.RLock()
|
||||
defer c.mut.RUnlock()
|
||||
func (c *commonClient) Invitations() <-chan protocol.SessionInvitation {
|
||||
return c.invitations
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/syncthing/syncthing/lib/osutil"
|
||||
@@ -25,10 +26,11 @@ type dynamicClient struct {
|
||||
certs []tls.Certificate
|
||||
timeout time.Duration
|
||||
|
||||
client RelayClient
|
||||
mut sync.RWMutex // Protects client.
|
||||
client *staticClient
|
||||
}
|
||||
|
||||
func newDynamicClient(uri *url.URL, certs []tls.Certificate, invitations chan protocol.SessionInvitation, timeout time.Duration) RelayClient {
|
||||
func newDynamicClient(uri *url.URL, certs []tls.Certificate, invitations chan protocol.SessionInvitation, timeout time.Duration) *dynamicClient {
|
||||
c := &dynamicClient{
|
||||
pooladdr: uri,
|
||||
certs: certs,
|
||||
@@ -114,15 +116,6 @@ func (c *dynamicClient) Error() error {
|
||||
return c.client.Error()
|
||||
}
|
||||
|
||||
func (c *dynamicClient) Latency() time.Duration {
|
||||
c.mut.RLock()
|
||||
defer c.mut.RUnlock()
|
||||
if c.client == nil {
|
||||
return time.Hour
|
||||
}
|
||||
return c.client.Latency()
|
||||
}
|
||||
|
||||
func (c *dynamicClient) String() string {
|
||||
return fmt.Sprintf("DynamicClient:%p:%s@%s", c, c.URI(), c.pooladdr)
|
||||
}
|
||||
|
||||
@@ -28,12 +28,9 @@ type staticClient struct {
|
||||
connectTimeout time.Duration
|
||||
|
||||
conn *tls.Conn
|
||||
|
||||
connected bool
|
||||
latency time.Duration
|
||||
}
|
||||
|
||||
func newStaticClient(uri *url.URL, certs []tls.Certificate, invitations chan protocol.SessionInvitation, timeout time.Duration) RelayClient {
|
||||
func newStaticClient(uri *url.URL, certs []tls.Certificate, invitations chan protocol.SessionInvitation, timeout time.Duration) *staticClient {
|
||||
c := &staticClient{
|
||||
uri: uri,
|
||||
|
||||
@@ -67,10 +64,6 @@ func (c *staticClient) serve(ctx context.Context) error {
|
||||
|
||||
l.Infof("Joined relay %s://%s", c.uri.Scheme, c.uri.Host)
|
||||
|
||||
c.mut.Lock()
|
||||
c.connected = true
|
||||
c.mut.Unlock()
|
||||
|
||||
messages := make(chan interface{})
|
||||
errorsc := make(chan error, 1)
|
||||
|
||||
@@ -128,20 +121,6 @@ func (c *staticClient) serve(ctx context.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *staticClient) StatusOK() bool {
|
||||
c.mut.RLock()
|
||||
con := c.connected
|
||||
c.mut.RUnlock()
|
||||
return con
|
||||
}
|
||||
|
||||
func (c *staticClient) Latency() time.Duration {
|
||||
c.mut.RLock()
|
||||
lat := c.latency
|
||||
c.mut.RUnlock()
|
||||
return lat
|
||||
}
|
||||
|
||||
func (c *staticClient) String() string {
|
||||
return fmt.Sprintf("StaticClient:%p@%s", c, c.URI())
|
||||
}
|
||||
@@ -155,7 +134,6 @@ func (c *staticClient) connect(ctx context.Context) error {
|
||||
return fmt.Errorf("unsupported relay scheme: %v", c.uri.Scheme)
|
||||
}
|
||||
|
||||
t0 := time.Now()
|
||||
timeoutCtx, cancel := context.WithTimeout(ctx, c.connectTimeout)
|
||||
defer cancel()
|
||||
tcpConn, err := dialer.DialContext(timeoutCtx, "tcp", c.uri.Host)
|
||||
@@ -163,10 +141,6 @@ func (c *staticClient) connect(ctx context.Context) error {
|
||||
return err
|
||||
}
|
||||
|
||||
c.mut.Lock()
|
||||
c.latency = time.Since(t0)
|
||||
c.mut.Unlock()
|
||||
|
||||
conn := tls.Client(tcpConn, c.config)
|
||||
|
||||
if err := conn.SetDeadline(time.Now().Add(c.connectTimeout)); err != nil {
|
||||
@@ -185,10 +159,6 @@ func (c *staticClient) connect(ctx context.Context) error {
|
||||
|
||||
func (c *staticClient) disconnect() {
|
||||
l.Debugln(c, "disconnecting")
|
||||
c.mut.Lock()
|
||||
c.connected = false
|
||||
c.mut.Unlock()
|
||||
|
||||
c.conn.Close()
|
||||
}
|
||||
|
||||
|
||||
148
lib/util/semaphore.go
Normal file
148
lib/util/semaphore.go
Normal file
@@ -0,0 +1,148 @@
|
||||
// Copyright (C) 2018 The Syncthing Authors.
|
||||
//
|
||||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package util
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type Semaphore struct {
|
||||
max int
|
||||
available int
|
||||
mut sync.Mutex
|
||||
cond *sync.Cond
|
||||
}
|
||||
|
||||
func NewSemaphore(max int) *Semaphore {
|
||||
if max < 0 {
|
||||
max = 0
|
||||
}
|
||||
s := Semaphore{
|
||||
max: max,
|
||||
available: max,
|
||||
}
|
||||
s.cond = sync.NewCond(&s.mut)
|
||||
return &s
|
||||
}
|
||||
|
||||
func (s *Semaphore) TakeWithContext(ctx context.Context, size int) error {
|
||||
done := make(chan struct{})
|
||||
var err error
|
||||
go func() {
|
||||
err = s.takeInner(ctx, size)
|
||||
close(done)
|
||||
}()
|
||||
select {
|
||||
case <-done:
|
||||
case <-ctx.Done():
|
||||
s.cond.Broadcast()
|
||||
<-done
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Semaphore) Take(size int) {
|
||||
_ = s.takeInner(context.Background(), size)
|
||||
}
|
||||
|
||||
func (s *Semaphore) takeInner(ctx context.Context, size int) error {
|
||||
// Checking context for size <= s.available is required for testing and doesn't do any harm.
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
s.mut.Lock()
|
||||
defer s.mut.Unlock()
|
||||
if size > s.max {
|
||||
size = s.max
|
||||
}
|
||||
for size > s.available {
|
||||
s.cond.Wait()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
if size > s.max {
|
||||
size = s.max
|
||||
}
|
||||
}
|
||||
s.available -= size
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Semaphore) Give(size int) {
|
||||
s.mut.Lock()
|
||||
if size > s.max {
|
||||
size = s.max
|
||||
}
|
||||
if s.available+size > s.max {
|
||||
s.available = s.max
|
||||
} else {
|
||||
s.available += size
|
||||
}
|
||||
s.cond.Broadcast()
|
||||
s.mut.Unlock()
|
||||
}
|
||||
|
||||
func (s *Semaphore) SetCapacity(capacity int) {
|
||||
if capacity < 0 {
|
||||
capacity = 0
|
||||
}
|
||||
s.mut.Lock()
|
||||
diff := capacity - s.max
|
||||
s.max = capacity
|
||||
s.available += diff
|
||||
if s.available < 0 {
|
||||
s.available = 0
|
||||
} else if s.available > s.max {
|
||||
s.available = s.max
|
||||
}
|
||||
s.cond.Broadcast()
|
||||
s.mut.Unlock()
|
||||
}
|
||||
|
||||
func (s *Semaphore) Available() int {
|
||||
s.mut.Lock()
|
||||
defer s.mut.Unlock()
|
||||
return s.available
|
||||
}
|
||||
|
||||
// MultiSemaphore combines semaphores, making sure to always take and give in
|
||||
// the same order (reversed for give). A semaphore may be nil, in which case it
|
||||
// is skipped.
|
||||
type MultiSemaphore []*Semaphore
|
||||
|
||||
func (s MultiSemaphore) TakeWithContext(ctx context.Context, size int) error {
|
||||
for _, limiter := range s {
|
||||
if limiter != nil {
|
||||
if err := limiter.TakeWithContext(ctx, size); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s MultiSemaphore) Take(size int) {
|
||||
for _, limiter := range s {
|
||||
if limiter != nil {
|
||||
limiter.Take(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s MultiSemaphore) Give(size int) {
|
||||
for i := range s {
|
||||
limiter := s[len(s)-1-i]
|
||||
if limiter != nil {
|
||||
limiter.Give(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,38 +4,38 @@
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
package model
|
||||
package util
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestZeroByteSempahore(t *testing.T) {
|
||||
// A semaphore with zero capacity is just a no-op.
|
||||
|
||||
s := newByteSemaphore(0)
|
||||
s := NewSemaphore(0)
|
||||
|
||||
// None of these should block or panic
|
||||
s.take(123)
|
||||
s.take(456)
|
||||
s.give(1 << 30)
|
||||
s.Take(123)
|
||||
s.Take(456)
|
||||
s.Give(1 << 30)
|
||||
}
|
||||
|
||||
func TestByteSempahoreCapChangeUp(t *testing.T) {
|
||||
// Waiting takes should unblock when the capacity increases
|
||||
|
||||
s := newByteSemaphore(100)
|
||||
s := NewSemaphore(100)
|
||||
|
||||
s.take(75)
|
||||
s.Take(75)
|
||||
if s.available != 25 {
|
||||
t.Error("bad state after take")
|
||||
}
|
||||
|
||||
gotit := make(chan struct{})
|
||||
go func() {
|
||||
s.take(75)
|
||||
s.Take(75)
|
||||
close(gotit)
|
||||
}()
|
||||
|
||||
s.setCapacity(155)
|
||||
s.SetCapacity(155)
|
||||
<-gotit
|
||||
if s.available != 5 {
|
||||
t.Error("bad state after both takes")
|
||||
@@ -45,19 +45,19 @@ func TestByteSempahoreCapChangeUp(t *testing.T) {
|
||||
func TestByteSempahoreCapChangeDown1(t *testing.T) {
|
||||
// Things should make sense when capacity is adjusted down
|
||||
|
||||
s := newByteSemaphore(100)
|
||||
s := NewSemaphore(100)
|
||||
|
||||
s.take(75)
|
||||
s.Take(75)
|
||||
if s.available != 25 {
|
||||
t.Error("bad state after take")
|
||||
}
|
||||
|
||||
s.setCapacity(90)
|
||||
s.SetCapacity(90)
|
||||
if s.available != 15 {
|
||||
t.Error("bad state after adjust")
|
||||
}
|
||||
|
||||
s.give(75)
|
||||
s.Give(75)
|
||||
if s.available != 90 {
|
||||
t.Error("bad state after give")
|
||||
}
|
||||
@@ -66,19 +66,19 @@ func TestByteSempahoreCapChangeDown1(t *testing.T) {
|
||||
func TestByteSempahoreCapChangeDown2(t *testing.T) {
|
||||
// Things should make sense when capacity is adjusted down, different case
|
||||
|
||||
s := newByteSemaphore(100)
|
||||
s := NewSemaphore(100)
|
||||
|
||||
s.take(75)
|
||||
s.Take(75)
|
||||
if s.available != 25 {
|
||||
t.Error("bad state after take")
|
||||
}
|
||||
|
||||
s.setCapacity(10)
|
||||
s.SetCapacity(10)
|
||||
if s.available != 0 {
|
||||
t.Error("bad state after adjust")
|
||||
}
|
||||
|
||||
s.give(75)
|
||||
s.Give(75)
|
||||
if s.available != 10 {
|
||||
t.Error("bad state after give")
|
||||
}
|
||||
@@ -87,26 +87,26 @@ func TestByteSempahoreCapChangeDown2(t *testing.T) {
|
||||
func TestByteSempahoreGiveMore(t *testing.T) {
|
||||
// We shouldn't end up with more available than we have capacity...
|
||||
|
||||
s := newByteSemaphore(100)
|
||||
s := NewSemaphore(100)
|
||||
|
||||
s.take(150)
|
||||
s.Take(150)
|
||||
if s.available != 0 {
|
||||
t.Errorf("bad state after large take")
|
||||
}
|
||||
|
||||
s.give(150)
|
||||
s.Give(150)
|
||||
if s.available != 100 {
|
||||
t.Errorf("bad state after large take + give")
|
||||
}
|
||||
|
||||
s.take(150)
|
||||
s.setCapacity(125)
|
||||
s.Take(150)
|
||||
s.SetCapacity(125)
|
||||
// available was zero before, we're increasing capacity by 25
|
||||
if s.available != 25 {
|
||||
t.Errorf("bad state after setcap")
|
||||
}
|
||||
|
||||
s.give(150)
|
||||
s.Give(150)
|
||||
if s.available != 125 {
|
||||
t.Errorf("bad state after large take + give with adjustment")
|
||||
}
|
||||
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
||||
..
|
||||
.TH "STDISCOSRV" "1" "Jun 14, 2021" "v1" "Syncthing"
|
||||
.TH "STDISCOSRV" "1" "Jul 17, 2021" "v1" "Syncthing"
|
||||
.SH NAME
|
||||
stdiscosrv \- Syncthing Discovery Server
|
||||
.SH SYNOPSIS
|
||||
|
||||
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
||||
..
|
||||
.TH "STRELAYSRV" "1" "Jun 14, 2021" "v1" "Syncthing"
|
||||
.TH "STRELAYSRV" "1" "Jul 17, 2021" "v1" "Syncthing"
|
||||
.SH NAME
|
||||
strelaysrv \- Syncthing Relay Server
|
||||
.SH SYNOPSIS
|
||||
|
||||
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
||||
..
|
||||
.TH "SYNCTHING-BEP" "7" "Jun 14, 2021" "v1" "Syncthing"
|
||||
.TH "SYNCTHING-BEP" "7" "Jul 17, 2021" "v1" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-bep \- Block Exchange Protocol v1
|
||||
.SH INTRODUCTION AND DEFINITIONS
|
||||
|
||||
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
||||
..
|
||||
.TH "SYNCTHING-CONFIG" "5" "Jun 14, 2021" "v1" "Syncthing"
|
||||
.TH "SYNCTHING-CONFIG" "5" "Jul 17, 2021" "v1" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-config \- Syncthing Configuration
|
||||
.SH SYNOPSIS
|
||||
|
||||
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
||||
..
|
||||
.TH "SYNCTHING-DEVICE-IDS" "7" "Jun 14, 2021" "v1" "Syncthing"
|
||||
.TH "SYNCTHING-DEVICE-IDS" "7" "Jul 17, 2021" "v1" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-device-ids \- Understanding Device IDs
|
||||
.sp
|
||||
|
||||
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
||||
..
|
||||
.TH "SYNCTHING-EVENT-API" "7" "Jun 14, 2021" "v1" "Syncthing"
|
||||
.TH "SYNCTHING-EVENT-API" "7" "Jul 17, 2021" "v1" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-event-api \- Event API
|
||||
.SH DESCRIPTION
|
||||
@@ -98,11 +98,14 @@ itself.
|
||||
"type": "ConfigSaved",
|
||||
"time": "2014\-12\-13T00:09:13.5166486Z",
|
||||
"data": {
|
||||
"Version": 7,
|
||||
"Options": {"..."},
|
||||
"GUI": {"..."},
|
||||
"Devices": [{"..."}],
|
||||
"Folders": [{"..."}]
|
||||
"version": 7,
|
||||
"folders": [{"..."}],
|
||||
"devices": [{"..."}],
|
||||
"gui": {"..."},
|
||||
"ldap": {"..."},
|
||||
"options": {"..."},
|
||||
"remoteIgnoredDevices": [{"..."}],
|
||||
"defaults": {"..."}
|
||||
}
|
||||
}
|
||||
.ft P
|
||||
|
||||
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
||||
..
|
||||
.TH "SYNCTHING-FAQ" "7" "Jun 14, 2021" "v1" "Syncthing"
|
||||
.TH "SYNCTHING-FAQ" "7" "Jul 17, 2021" "v1" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-faq \- Frequently Asked Questions
|
||||
.INDENT 0.0
|
||||
|
||||
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
||||
..
|
||||
.TH "SYNCTHING-GLOBALDISCO" "7" "Jun 14, 2021" "v1" "Syncthing"
|
||||
.TH "SYNCTHING-GLOBALDISCO" "7" "Jul 17, 2021" "v1" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-globaldisco \- Global Discovery Protocol v3
|
||||
.SH ANNOUNCEMENTS
|
||||
|
||||
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
||||
..
|
||||
.TH "SYNCTHING-LOCALDISCO" "7" "Jun 14, 2021" "v1" "Syncthing"
|
||||
.TH "SYNCTHING-LOCALDISCO" "7" "Jul 17, 2021" "v1" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-localdisco \- Local Discovery Protocol v4
|
||||
.SH MODE OF OPERATION
|
||||
|
||||
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
||||
..
|
||||
.TH "SYNCTHING-NETWORKING" "7" "Jun 14, 2021" "v1" "Syncthing"
|
||||
.TH "SYNCTHING-NETWORKING" "7" "Jul 17, 2021" "v1" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-networking \- Firewall Setup
|
||||
.SH ROUTER SETUP
|
||||
|
||||
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
||||
..
|
||||
.TH "SYNCTHING-RELAY" "7" "Jun 14, 2021" "v1" "Syncthing"
|
||||
.TH "SYNCTHING-RELAY" "7" "Jul 17, 2021" "v1" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-relay \- Relay Protocol v1
|
||||
.SH WHAT IS A RELAY?
|
||||
|
||||
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
||||
..
|
||||
.TH "SYNCTHING-REST-API" "7" "Jun 14, 2021" "v1" "Syncthing"
|
||||
.TH "SYNCTHING-REST-API" "7" "Jul 17, 2021" "v1" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-rest-api \- REST API
|
||||
.sp
|
||||
@@ -749,8 +749,9 @@ it’s replaced, otherwise a new one is added.
|
||||
.SS /rest/config/folders/*id*, /rest/config/devices/*id*
|
||||
.sp
|
||||
Put the desired folder\- respectively device\-ID in place of *id*. \fBGET\fP
|
||||
returns the folder/device for the given ID, \fBPUT\fP replaces the entire config
|
||||
and \fBPATCH\fP replaces only the given child objects.
|
||||
returns the folder/device for the given ID, \fBPUT\fP replaces the entire config,
|
||||
\fBPATCH\fP replaces only the given child objects and \fBDELETE\fP removes the
|
||||
folder/device.
|
||||
.SS /rest/config/options, /rest/config/ldap, /rest/config/gui
|
||||
.sp
|
||||
\fBGET\fP returns the respective object, \fBPUT\fP replaces the entire object and
|
||||
|
||||
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
||||
..
|
||||
.TH "SYNCTHING-SECURITY" "7" "Jun 14, 2021" "v1" "Syncthing"
|
||||
.TH "SYNCTHING-SECURITY" "7" "Jul 17, 2021" "v1" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-security \- Security Principles
|
||||
.sp
|
||||
|
||||
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
||||
..
|
||||
.TH "SYNCTHING-STIGNORE" "5" "Jun 14, 2021" "v1" "Syncthing"
|
||||
.TH "SYNCTHING-STIGNORE" "5" "Jul 17, 2021" "v1" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-stignore \- Prevent files from being synchronized to other nodes
|
||||
.SH SYNOPSIS
|
||||
|
||||
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
||||
..
|
||||
.TH "SYNCTHING-VERSIONING" "7" "Jun 14, 2021" "v1" "Syncthing"
|
||||
.TH "SYNCTHING-VERSIONING" "7" "Jul 17, 2021" "v1" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing-versioning \- Keep automatic backups of deleted files by other nodes
|
||||
.sp
|
||||
|
||||
@@ -27,7 +27,7 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
|
||||
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
|
||||
..
|
||||
.TH "SYNCTHING" "1" "Jun 14, 2021" "v1" "Syncthing"
|
||||
.TH "SYNCTHING" "1" "Jul 17, 2021" "v1" "Syncthing"
|
||||
.SH NAME
|
||||
syncthing \- Syncthing
|
||||
.SH SYNOPSIS
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"os"
|
||||
@@ -20,12 +21,13 @@ import (
|
||||
)
|
||||
|
||||
var trans = make(map[string]string)
|
||||
var attrRe = regexp.MustCompile(`\{\{'([^']+)'\s+\|\s+translate\}\}`)
|
||||
var attrReCond = regexp.MustCompile(`\{\{.+\s+\?\s+'([^']+)'\s+:\s+'([^']+)'\s+\|\s+translate\}\}`)
|
||||
var attrRe = regexp.MustCompile(`\{\{\s*'([^']+)'\s+\|\s+translate\s*\}\}`)
|
||||
var attrReCond = regexp.MustCompile(`\{\{.+\s+\?\s+'([^']+)'\s+:\s+'([^']+)'\s+\|\s+translate\s*\}\}`)
|
||||
var jsRe = regexp.MustCompile(`\$translate.instant\("([^"]+)"\)`)
|
||||
|
||||
// exceptions to the untranslated text warning
|
||||
var noStringRe = regexp.MustCompile(
|
||||
`^((\W*\{\{.*?\}\} ?.?\/?.?(bps)?\W*)+(\.stignore)?|[^a-zA-Z]+.?[^a-zA-Z]*|[kMGT]?B|Twitter|JS\W?|DEV|https?://\S+)$`)
|
||||
`^((\W*\{\{.*?\}\} ?.?\/?.?(bps)?\W*)+(\.stignore)?|[^a-zA-Z]+.?[^a-zA-Z]*|[kMGT]?B|Twitter|JS\W?|DEV|https?://\S+|TechUi)$`)
|
||||
|
||||
// exceptions to the untranslated text warning specific to aboutModalView.html
|
||||
var aboutRe = regexp.MustCompile(`^([^/]+/[^/]+|(The Go Pro|Font Awesome ).+|Build \{\{.+\}\}|Copyright .+ the Syncthing Authors\.)$`)
|
||||
@@ -108,17 +110,27 @@ func walkerFor(basePath string) filepath.WalkFunc {
|
||||
return err
|
||||
}
|
||||
|
||||
if filepath.Ext(name) == ".html" && info.Mode().IsRegular() {
|
||||
fd, err := os.Open(name)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if !info.Mode().IsRegular() {
|
||||
return nil
|
||||
}
|
||||
fd, err := os.Open(name)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer fd.Close()
|
||||
switch filepath.Ext(name) {
|
||||
case ".html":
|
||||
doc, err := html.Parse(fd)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fd.Close()
|
||||
generalNode(doc, filepath.Base(name))
|
||||
case ".js":
|
||||
for s := bufio.NewScanner(fd); s.Scan(); {
|
||||
for _, matches := range jsRe.FindAllStringSubmatch(s.Text(), -1) {
|
||||
translation(matches[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user