9 Commits

Author SHA1 Message Date
l-jonas 460d421a79 Release 0.3.2 2018-11-07 07:41:38 +01:00
Jonas L 96edbaa240 Update translations 2018-11-07 07:00:37 +01:00
l-jonas de1566915b Finish spanish translation (#73)
* Move google to the top of allrepositories.google

This should fix build issues according to https://gitlab.com/fdroid/fdroiddata/issues/1423

* Finish spanish translation

This is a import from Transifex because I got a automatic notification that the spanish translation was finished
2018-11-06 21:02:32 +01:00
Felix Ableitner a294d6f06c Add gradle play publisher for automated releases to Google Play 2018-11-06 17:11:07 +01:00
l-jonas 7ce017f48c Update translations (#68)
* Move google to the top of allrepositories.google

This should fix build issues according to https://gitlab.com/fdroid/fdroiddata/issues/1423

* Merge translations

* Import fixed localization of the remove device dialog title

* Add new translations

* Update strings with wrong escaping in Transifex and import them

* Fix escaping at some other strings

* Update translations
2018-11-06 13:46:57 +01:00
l-jonas 55edc592a0 Remove obsolete build instructions from the readme 2018-11-06 12:57:35 +01:00
l-jonas eb5dfcbd46 Refactor discovery code (#64)
* Move discovery server query out of GlobalDiscoveryHandler

* Refactor GlobalDiscoveryHandler

* Run global discovery in bulk (only pick discovery servers once)

* Readd ignoring failed global discovery

* Add new code for local discovery

* Use new code for local discovery

* Fix compiling the discovery CLI

* Fix code style

* Add Copyright headers to all files of syncthing-discovery

* Refactor the AddressRanker

* Fix timeout at the AddressRanker

* Refactor the DiscoveryHandler

* Use HttpUrlConnection in GlobalDiscoveryUtil

* Disable https verification in GlobalDiscoveryUtil

* Get the changes working

* Add newline

* De-hardcode announce server query url generation

* Only ignore specific global discovery exceptions

* Only catch IOException at LocalDiscoveryHandler.startListener()

* Only catch IOException at LocalDiscoveryUtil when processing the package
2018-11-06 12:13:44 +01:00
l-jonas 2b55bd9e76 Fix typo in the App which was at the Readme too 2018-10-29 07:45:14 +01:00
Licaon_Kter eb8360f276 Readme typos (#65) 2018-10-29 07:43:39 +01:00
31 changed files with 1019 additions and 394 deletions
+8 -13
View File
@@ -4,18 +4,18 @@
[![MPLv2 License](https://img.shields.io/badge/license-MPLv2-blue.svg?style=flat-square)](https://www.mozilla.org/MPL/2.0/)
This project is an Android app, that works as a client for a [Syncthing][1] share (accessing
Syncthing devices in the same way a client-server file sharing app access its proprietary server).
Syncthing devices in the same way a client-server file sharing app accesses its proprietary server).
This is a client-oriented implementation, designed to work online by downloading and
uploading files from an active device on the network (instead of synchronizing a local copy of
the entire repository). This is quite different from the way the [syncthing-android][2] works,
and its useful from those devices that cannot or wish not to download the entire repository (for
and it's useful for those devices that cannot or do not wish to download the entire repository (for
example, mobile devices with limited storage available, wishing to access a syncthing share).
This project is based on [syncthing-java][3], a java implementation of Syncthing protocols.
Due to the behaviour of this App and the [behaviour of the Syncthing Server](https://github.com/syncthing/syncthing/issues/5224),
you can't reconnct for some minutes if the App was killed (due to removing from the recent App list) or the connection was interrupted.
you can't reconnect for some minutes if the App was killed (due to removing from the recent App list) or the connection was interrupted.
This does not apply to local discovery connections.
[<img alt="Get it on F-Droid" src="https://f-droid.org/badge/get-it-on.png" height="80">](https://f-droid.org/packages/net.syncthing.lite/)
@@ -27,18 +27,13 @@ The project is translated on [Transifex](https://www.transifex.com/syncthing/syn
## Building
The project uses a standard Android build, and requires the Android SDK. The easiest option is if
you install [Android Studio][4] and import the project.
To compile with a development version of the [syncthing-java][3] library, you have to install it to
the local maven repository. To do this, clone the repo and run `gradle install` in the
syncthing-java project folder.
The project uses a standard Android build, and requires the Android SDK. The easiest option is to
install [Android Studio][3] and import the project.
## License
All code is licensed under the [MPLv2 License][5].
All code is licensed under the [MPLv2 License][4].
[1]: https://syncthing.net/
[2]: https://github.com/syncthing/syncthing-android
[3]: https://github.com/Nutomic/syncthing-java
[4]: https://developer.android.com/studio/index.html
[5]: LICENSE
[3]: https://developer.android.com/studio/index.html
[4]: LICENSE
+11 -2
View File
@@ -2,18 +2,27 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'com.github.ben-manes.versions'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'com.github.triplet.play'
android {
compileSdkVersion 27
buildToolsVersion "28.0.2"
dataBinding.enabled = true
playAccountConfigs {
defaultAccountConfig {
jsonFile = file(System.getenv("SYNCTHING_RELEASE_PLAY_ACCOUNT_CONFIG_FILE") ?: 'keys.json')
}
}
defaultConfig {
applicationId "net.syncthing.lite"
minSdkVersion 21
targetSdkVersion 26
versionCode 11
versionName "0.3.1"
versionCode 12
versionName "0.3.2"
multiDexEnabled true
playAccountConfig = playAccountConfigs.defaultAccountConfig
}
sourceSets {
main.java.srcDirs += "src/main/kotlin"
+23 -3
View File
@@ -1,9 +1,9 @@
<resources>
<string name="app_name">Syncthing Lite</string>
<string name="folder_list_empty_message">Keine Ordner verfügbar</string>
<string name="folder_list_empty_message">Kein Ordner verfügbar</string>
<string name="clear_local_cache_index_label">Lokalen Index und Cache löschen</string>
<string name="devices_list_view_empty_message">Keine Geräte verfügbar</string>
<string name="device_id_dialog_title">Geräte ID eingeben</string>
<string name="invalid_device_id">Fehler: Ungültige Geräte ID</string>
<string name="dialog_downloading_file">Datei %1$s wird heruntergeladen</string>
<string name="toast_file_download_failed">Datei konnte nicht heruntergeladen werden</string>
<string name="toast_open_file_failed">Keine kompatible app gefunden</string>
@@ -12,8 +12,11 @@
<string name="dialog_uploading_file">Datei %1$s wird hochgeladen</string>
<string name="clear_cache_and_index_title">Lokalen Cache und Index löschen?</string>
<string name="clear_cache_and_index_body">Gesamten lokalen Cache und Index löschen?</string>
<string name="index_update_progress_label">Index Aktualisierung für Ordner %1$s, %2$d %% synchronisiert</string>
<string name="loading_config_starting_syncthing_client">Konfiguartion wird geladen, Syncthing wird gestartet...</string>
<string name="last_modified_time">Zuletzt modifiziert: %1$s</string>
<string name="remove_device_title">Gerät entfernen: %1$s</string>
<string name="remove_device_title">Gerät %1$s entfernen?</string>
<string name="remove_device_message">Gerät %1$s von der Liste der bekannten Geräte entfernen?</string>
<string name="device_import_success">Gerät %1$s erfolgreich importiert</string>
<string name="device_already_known">Gerät ist bereits bekannt $1%s</string>
<string name="folders_label">Ordner</string>
@@ -21,4 +24,21 @@
<string name="folder_label_format">%1$s (%2$s)</string>
<string name="folder_content_info">%1$s, %2$d Dateien, %3$d Ordner</string>
<string name="file_info">%1$s, zuletzt modifiziert %2$s</string>
<string name="show_device_id">Geräte ID anzeigen</string>
<string name="device_id">Geräte ID</string>
<string name="device_id_copied">Geräte ID in den Zwischenspeicher kopiert</string>
<string name="share_device_id_chooser">Teile Geräte ID mit</string>
<string name="other_syncthing_instance_title">Eine andere Syncthing Instanz läuft bereits</string>
<string name="other_syncthing_instance_message">Lokale Auffindung wird nicht funktionieren. Stoppen Sie die andere Syncthing Instanz, um die lokale Auffindung zu ermöglichen.</string>
<string name="intro_page_one_title">Willkommen zu Syncthing Lite</string>
<string name="intro_page_one_description">Syncthing ersetzt proprietäre Sync- und Cloud-Services durch etwas Offenes, Vertrauenswürdiges und Dezentrales. Ihre Daten sind allein Ihre Daten, und Sie verdienen es zu wählen, wo sie gespeichert werden, ob sie an Dritte weitergegeben werden und wie sie über das Internet übertragen werden.</string>
<string name="intro_page_two_title">Ein Gerät hinzufügen</string>
<string name="intro_page_three_title">Ordner teilen</string>
<string name="intro_page_two_description">Eine Syncthing Geräte ID eingeben oder QR Code einer Geräte ID scannen.</string>
<string name="intro_page_three_description">Akzeptieren Sie nun das Gerät mit der ID %1$s und geben Sie einen Ordner mit ihm frei. Es kann einige Minuten dauern, bis sich die Geräte verbinden.</string>
<string name="settings">Einstellungen</string>
<string name="settings_app_version_title">App Version</string>
<string name="settings_local_device_name">Lokaler Geräte Namen</string>
<string name="settings_local_device_summary">Name, den das andere Gerät für dieses Gerät sehen wird</string>
<string name="device_id_dialog_title">Geräte ID eingeben</string>
</resources>
+52
View File
@@ -0,0 +1,52 @@
<resources>
<string name="app_name">Syncthing Lite</string>
<string name="folder_list_empty_message">Carpeta no disponible</string>
<string name="clear_local_cache_index_label">Limpia caché/índice local</string>
<string name="devices_list_view_empty_message">Dispositivos no disponibles</string>
<string name="invalid_device_id">Error: identificador de dispositivo inválido</string>
<string name="dialog_downloading_file">Descargando archivo%1$s</string>
<string name="toast_file_download_failed">Falló al descargar el archivo</string>
<string name="toast_open_file_failed">No se ha encontrado una app compatible</string>
<string name="toast_file_upload_failed">Error en la carga de archivos
</string>
<string name="toast_upload_complete">Carga de archivos completa
</string>
<string name="dialog_uploading_file">Cargando archivo %1$s</string>
<string name="clear_cache_and_index_title">¿Borrar la caché local y el índice?</string>
<string name="clear_cache_and_index_body">¿Borrar todos los datos de la caché local y los datos de índice?</string>
<string name="index_update_progress_label">Actualización del índice de las carpetas %1$s, %2$d%% sincronizado</string>
<string name="loading_config_starting_syncthing_client">Cargando configuración, iniciando el cliente de sincronización....</string>
<string name="last_modified_time">Última modificación: %1$s</string>
<string name="remove_device_title">¿Quitar dispositivo %1$s?</string>
<string name="remove_device_message">¿Quitar dispositivo %1$s de la lista de dispositivos conocidos?</string>
<string name="device_import_success">Dispositivo %1$simportado con éxito</string>
<string name="device_already_known">Dispositivo %1$s ya está presente</string>
<string name="folders_label">Carpetas</string>
<string name="devices_label">Dispositivos</string>
<string name="folder_label_format">%1$s (%2$s)</string>
<string name="folder_content_info">%1$s, %2$d archivos, %3$ddirectorios</string>
<string name="file_info">%1$s, modificado por última vez %2$s</string>
<string name="show_device_id">Mostrar ID de dispositivo </string>
<string name="device_id">ID de dispositivo</string>
<string name="device_id_copied">ID de dispositivo copiado al portapapeles</string>
<string name="share_device_id_chooser">Compartir ID de dispositivo con</string>
<string name="other_syncthing_instance_title">Otra instancia de Syncthing está siendo ejecutada</string>
<string name="other_syncthing_instance_message">El descubrimiento local no funcionará. Detenga la otra instancia de Syncthing para habilitar la detección local.</string>
<string name="intro_page_one_title">Bienvenido a Syncthing Lite</string>
<string name="intro_page_one_description">Syncthing reemplaza los servicios de sincronización y nube propietarios por algo abierto, fiable y descentralizado. Sus datos son sólo suyos y usted merece elegir dónde se almacenan, si se comparten con terceros y cómo se transmiten a través de Internet.</string>
<string name="intro_page_two_title">Añadir un dispositivo</string>
<string name="intro_page_three_title">Compartir tus carpetas</string>
<string name="intro_page_two_description">Introduce un ID de dispositivo de Syncthing o escanea un ID de dispositivo desde un código QR</string>
<string name="intro_page_three_description">Acepta ahora el dispositivo con ID %1$s, y comparte una carpeta con él. Pueden pasar unos minutos hasta que los dispositivos se conecten.</string>
<string name="settings">Configuración</string>
<string name="settings_app_version_title">Versión de la aplicación</string>
<string name="settings_local_device_name">Nombre del dispositivo local</string>
<string name="settings_local_device_summary">El nombre que otros dispositivos verán para este dispositivo</string>
<string name="settings_shutdown_delay_title">Retardo en el apagado</string>
<string name="settings_shutdown_delay_summary">Tiempo antes de apagar el cliente Syncthing después de su último uso</string>
<string name="device_id_dialog_title">Introducir la ID del dispositivo</string>
<string name="settings_shutdown_delay_10_seconds">10 segundos</string>
<string name="settings_shutdown_delay_30_seconds">30 segundos</string>
<string name="settings_shutdown_delay_1_minute">1 minuto</string>
<string name="settings_shutdown_delay_5_minutes">5 minutos</string>
</resources>
+28 -2
View File
@@ -3,7 +3,7 @@
<string name="folder_list_empty_message">Aucun dossier disponible</string>
<string name="clear_local_cache_index_label">Effacer le cache et l\'index local</string>
<string name="devices_list_view_empty_message">Aucun appareil disponible</string>
<string name="device_id_dialog_title">Entrer l\'ID de l\'appareil</string>
<string name="invalid_device_id">Erreur: ID de l\'appareil invalide</string>
<string name="dialog_downloading_file">Téléchargement du fichier %1$s</string>
<string name="toast_file_download_failed">Le téléchargement du fichier a échoué</string>
<string name="toast_open_file_failed">Aucune appli compatible trouvée</string>
@@ -12,8 +12,11 @@
<string name="dialog_uploading_file">Upload du fichier %1$s</string>
<string name="clear_cache_and_index_title">Effacer le cache local et l\'index?</string>
<string name="clear_cache_and_index_body">Effacer toutes les données du cache local et de l\'index ?</string>
<string name="index_update_progress_label">Mise à jour de l\'index pour le dossier %1$s, %2$d%% synchronisés</string>
<string name="loading_config_starting_syncthing_client">Chargement de la configuration, démarrage du client Syncthing...</string>
<string name="last_modified_time">Dernière modification : %1$s</string>
<string name="remove_device_title">Supprimer l\'appareil %1$s\?</string>
<string name="remove_device_message">Supprimer l\'appareil %1$s de la liste des appareil connus ?</string>
<string name="device_import_success">Appareil %1$s importé avec succès</string>
<string name="device_already_known">Appareil déjà connu %1$s</string>
<string name="folders_label">Dossiers</string>
@@ -21,4 +24,27 @@
<string name="folder_label_format">%1$s (%2$s)</string>
<string name="folder_content_info">%1$s, %2$d fichiers, %3$d dossiers</string>
<string name="file_info">%1$s, dernière modification %2$s</string>
</resources>
<string name="show_device_id">Afficher l\'ID de l\'appareil</string>
<string name="device_id">ID de l\'appareil</string>
<string name="device_id_copied">ID de l\'appareil copié dans le presse-papier</string>
<string name="share_device_id_chooser">Partager l\'ID de l\'appareil avec</string>
<string name="other_syncthing_instance_title">Une autre instance de Syncthing fonctionne</string>
<string name="other_syncthing_instance_message">La découverte locale ne fonctionnera pas. Arrêtez l\'autre instance de Syncthing pour activer la découverte locale.</string>
<string name="intro_page_one_title">Bienvenue dans Syncthing Lite</string>
<string name="intro_page_one_description">Syncthing remplace les services de synchronisation et de cloud propriétaires par quelque chose d\'ouvert, fiable et décentralisé. Vos données sont uniquement vos données et vous méritez de choisir où elles sont stockées, si elles sont partagées avec des tiers et comment elles sont transmises sur Internet.</string>
<string name="intro_page_two_title">Ajouter un appareil</string>
<string name="intro_page_three_title">Partager vos dossiers</string>
<string name="intro_page_two_description">Entrer l\'ID Syncthing de l\'appareil, ou scanner le QR code de l\'ID d\'un appareil.</string>
<string name="intro_page_three_description">Maintenant, acceptez l\'appareil avec l\'ID %1$s, et partagez un dossier avec lui. Cela peut prendre quelques minutes avant que les appareils ne se connectent.</string>
<string name="settings">Réglages</string>
<string name="settings_app_version_title">Version d\'application</string>
<string name="settings_local_device_name">Nom local de l\'appareil</string>
<string name="settings_local_device_summary">Le nom que les autres appareils verront pour cet appareil</string>
<string name="settings_shutdown_delay_title">Délai d\'arrêt</string>
<string name="settings_shutdown_delay_summary">Délai avant d\'arrêter le client Syncthing après sa dernière utilisation</string>
<string name="device_id_dialog_title">Entrer l\'ID de l\'appareil</string>
<string name="settings_shutdown_delay_10_seconds">10 secondes</string>
<string name="settings_shutdown_delay_30_seconds">30 secondes</string>
<string name="settings_shutdown_delay_1_minute">1 minute</string>
<string name="settings_shutdown_delay_5_minutes">5 minutes</string>
</resources>
+44
View File
@@ -0,0 +1,44 @@
<resources>
<string name="app_name">Syncthing Lite</string>
<string name="folder_list_empty_message">Nincs elérhető mappa</string>
<string name="clear_local_cache_index_label">Helyi gyorsítótár/index törlése</string>
<string name="devices_list_view_empty_message">Nincs elérhető eszköz</string>
<string name="invalid_device_id">Hiba: helytelen eszközazonosítő</string>
<string name="dialog_downloading_file">Fájl letöltése %1$s</string>
<string name="toast_file_download_failed">Hiba a fájl letöltése közben</string>
<string name="toast_open_file_failed">Nem található kompatibilis alkalmazás</string>
<string name="toast_file_upload_failed">Hiba fájl feltöltése közben</string>
<string name="toast_upload_complete">Feltöltés befejezve</string>
<string name="dialog_uploading_file">Fájl feltöltése: %1$s</string>
<string name="clear_cache_and_index_title">Törlöd a helyi gyorsítótárat és index-et?</string>
<string name="clear_cache_and_index_body">Törlöd az összes helyi gyorsítótár-at és index-et?</string>
<string name="index_update_progress_label">Index frissítés szinkronizálva a %1$s,%2$d,%% mappákhoz</string>
<string name="loading_config_starting_syncthing_client">Beállítások betöltése, Syncthing kliens indítása</string>
<string name="last_modified_time">Utolsó módosítás: %1$s</string>
<string name="remove_device_title">Törlöd a %1$s eszközt?</string>
<string name="remove_device_message">Törlöd a %1$s eszközt az ismertek listájáról?</string>
<string name="device_import_success">Eszköz sikeresen importálva: %1$s</string>
<string name="device_already_known">%1$s eszköz már hozzá van adva</string>
<string name="folders_label">Mappák</string>
<string name="devices_label">Eszközök</string>
<string name="folder_label_format">%1$s (%2$s)</string>
<string name="folder_content_info">%1$s, %2$d fájlok, %3$d mappák</string>
<string name="file_info">%1$s, utoljára módosítva %2$s</string>
<string name="show_device_id">Eszközazonosító megjelenítése</string>
<string name="device_id">Eszközazonosító</string>
<string name="device_id_copied">Eszközazonosító a vágólapra másolva</string>
<string name="share_device_id_chooser">Eszközazonosító megosztása</string>
<string name="other_syncthing_instance_title">Egy másik Syncthing folyamat fut</string>
<string name="other_syncthing_instance_message">Helyi felfedezés nem fog működni. Állítsd le a másik Syncthing folyamatot a helyi felfedezés bekapcsolásához.</string>
<string name="intro_page_one_title">Üdvözöllek a Syncthing Lite-ban</string>
<string name="intro_page_one_description">A Syncthing a zárt forrású szinkronizáló és felhő szolgáltatásokat egy nyílt, megbízható és decentralizált szoftverrel váltja fel. A te adatod csak a tiéd és te szabod meg, hogy hol tárolod, kivel osztod meg, és hogyan továbbítod az interneten.</string>
<string name="intro_page_two_title">Eszköz hozzáadása</string>
<string name="intro_page_three_title">Mappáid megosztása</string>
<string name="intro_page_two_description">Adj meg egy Syncthing eszközazonosítót, vagy olvasd be QR kódból</string>
<string name="intro_page_three_description">Fogadd el a(z) %1$s azonosítójú eszközt, és ossz meg vele egy mappát. Beletelhet néhány percbe mire az eszközök csatlakoznak.</string>
<string name="settings">Beállítások</string>
<string name="settings_app_version_title">Verzió</string>
<string name="settings_local_device_name">Helyi eszköz neve</string>
<string name="settings_local_device_summary">Név amit a többi eszköz fog látni</string>
<string name="device_id_dialog_title">Eszközazonosító megadása</string>
</resources>
+28 -3
View File
@@ -3,8 +3,8 @@
<string name="folder_list_empty_message">Nessuna cartella disponibile</string>
<string name="clear_local_cache_index_label">Cancella cache/indice</string>
<string name="devices_list_view_empty_message">Nessun dispositivo disponibile</string>
<string name="device_id_dialog_title">Inserisci ID Dispositivo</string>
<string name="dialog_downloading_file">Download del file %1$s</string>
<string name="invalid_device_id">Errore: ID dispositivo non valido</string>
<string name="dialog_downloading_file">Scaricamento del file %1$s</string>
<string name="toast_file_download_failed">Impossibile scaricare il file</string>
<string name="toast_open_file_failed">Nessuna applicazione compatibile trovata</string>
<string name="toast_file_upload_failed">Caricamento file fallito</string>
@@ -13,8 +13,10 @@
<string name="clear_cache_and_index_title">Cancellare la cache locale e l\'indice?</string>
<string name="clear_cache_and_index_body">Cancellare tutti i dati della cache locale e i dati dell\'indice?</string>
<string name="index_update_progress_label">Aggiornamento dell\'indice per la cartella %1$s, %2$d%% sincronizzato</string>
<string name="loading_config_starting_syncthing_client">Caricamento configurazione, avvio del client syncthing...</string>
<string name="last_modified_time">Ultima modifica: %1$s</string>
<string name="remove_device_title">Rimuovere il dispositivo %1$s\?</string>
<string name="remove_device_message">Rimuovere %1$s dalla lista dei dispositivi noti?</string>
<string name="device_import_success">Dispositivo %1$s importato con successo</string>
<string name="device_already_known">Dispositivo %1$s già presente</string>
<string name="folders_label">Cartelle</string>
@@ -22,4 +24,27 @@
<string name="folder_label_format">%1$s (%2$s)</string>
<string name="folder_content_info">%1$s, %2$d file, %3$d cartelle</string>
<string name="file_info">%1$s, ultima modifica %2$s</string>
</resources>
<string name="show_device_id">Mostra l\'ID del dispositivo</string>
<string name="device_id">ID dispositivo</string>
<string name="device_id_copied">ID dispositivo copiato negli appunti</string>
<string name="share_device_id_chooser">Condividi ID dispositivo con</string>
<string name="other_syncthing_instance_title">Un\'altra istanza di Syncthing è in esecuzione</string>
<string name="other_syncthing_instance_message">L\'individuazione locale non funzionerà. Arresta l\'altra istanza di Syncthing per abilitare l\'individuazione locale.</string>
<string name="intro_page_one_title">Benvenuto in Syncthing Lite</string>
<string name="intro_page_one_description">Syncthing sostituisce servizi proprietari di sincronizzazione e cloud con qualcosa di aperto, affidabile e decentralizzato. I tuoi dati sono solo tuoi e meriti di scegliere dove vengono immagazzinati, se sono condivisi con terze parti e come sono trasmessi attraverso Internet.</string>
<string name="intro_page_two_title">Aggiungi un dispositivo</string>
<string name="intro_page_three_title">Condividi le tue cartelle</string>
<string name="intro_page_two_description">Immetti un ID dispositivo Syncthing o esegui la scansione di un ID dispositivo da un codice QR</string>
<string name="intro_page_three_description">Ora accetta il dispositivo con ID %1$s, e condividi una cartella con esso. Potrebbero essere necessari alcuni minuti prima che i dispositivi si connettano.</string>
<string name="settings">Impostazioni</string>
<string name="settings_app_version_title">Versione dell\'app</string>
<string name="settings_local_device_name">Nome del dispositivo locale</string>
<string name="settings_local_device_summary">Il nome che altri dispositivi vedranno per questo dispositivo</string>
<string name="settings_shutdown_delay_title">Ritardo di chiusura</string>
<string name="settings_shutdown_delay_summary">Tempo prima della chiusura del client Syncthing dopo l\'ultimo utilizzo</string>
<string name="device_id_dialog_title">Inserisci ID Dispositivo</string>
<string name="settings_shutdown_delay_10_seconds">10 secondi</string>
<string name="settings_shutdown_delay_30_seconds">30 secondi</string>
<string name="settings_shutdown_delay_1_minute">1 minuto</string>
<string name="settings_shutdown_delay_5_minutes">5 minuti</string>
</resources>
+20 -1
View File
@@ -3,7 +3,7 @@
<string name="folder_list_empty_message">フォルダーがありません</string>
<string name="clear_local_cache_index_label">ローカルキャッシュ/索引をクリア</string>
<string name="devices_list_view_empty_message">デバイスがありません</string>
<string name="device_id_dialog_title">デバイス ID を入力</string>
<string name="invalid_device_id">エラー: デバイス ID が無効です</string>
<string name="dialog_downloading_file">ファイル %1$s のダウンロード中</string>
<string name="toast_file_download_failed">ファイルのダウンロードに失敗しました</string>
<string name="toast_open_file_failed">利用できるアプリが見つかりません</string>
@@ -13,8 +13,10 @@
<string name="clear_cache_and_index_title">ローカルキャッシュと索引をクリアしますか?</string>
<string name="clear_cache_and_index_body">すべてのローカルキャッシュデータと索引データをクリアしますか?</string>
<string name="index_update_progress_label">フォルダー %1$s の索引を更新しました。 %2$d%% 同期しました</string>
<string name="loading_config_starting_syncthing_client">設定の読み込み中、syncthing クライアントの開始中…</string>
<string name="last_modified_time">最終更新: %1$s</string>
<string name="remove_device_title">デバイス %1$sを削除しますか?</string>
<string name="remove_device_message">既存のデバイスのリストから %1$s を削除しますか?</string>
<string name="device_import_success">デバイス %1$s のインポートに成功しました</string>
<string name="device_already_known">デバイスは既に存在します %1$s</string>
<string name="folders_label">フォルダー</string>
@@ -22,4 +24,21 @@
<string name="folder_label_format">%1$s (%2$s)</string>
<string name="folder_content_info">%1$s, %2$d ファイル, %3$d ディレクトリー</string>
<string name="file_info">%1$s, 最終更新 %2$s</string>
<string name="show_device_id">デバイス ID を表示</string>
<string name="device_id">デバイス ID</string>
<string name="device_id_copied">デバイス ID をクリップボードにコピーしました</string>
<string name="share_device_id_chooser">次とデバイス ID を共有</string>
<string name="other_syncthing_instance_title">別の Syncthing インスタンスが実行中です</string>
<string name="other_syncthing_instance_message">ローカルの探索は動作しません。他の Syncthing インスタンスを停止して、ローカルの探索を有効にしてください。</string>
<string name="intro_page_one_title">Syncthing Lite へようこそ</string>
<string name="intro_page_one_description">Syncthing は、プロプライエタリな同期およびクラウドサービスを、オープンで信頼性があり、分散化されたものに置き換えます。 あなたのデータはあなただけのものであり、それが第三者と共有され、インターネット経由で送信される場合、保存される場所を選択する必要があります。</string>
<string name="intro_page_two_title">デバイスを追加</string>
<string name="intro_page_three_title">フォルダーを共有</string>
<string name="intro_page_two_description">Syncthing デバイス ID を入力、または QR コードからデバイス ID をスキャンしてください</string>
<string name="intro_page_three_description">ID %1$s のデバイスを承認して、フォルダーを共有しました。デバイスが接続されるまで数分かかることがあります。</string>
<string name="settings">設定</string>
<string name="settings_app_version_title">アプリバージョン</string>
<string name="settings_local_device_name">ローカルのデバイス名</string>
<string name="settings_local_device_summary">他のデバイスがこのデバイスを表示する名前</string>
<string name="device_id_dialog_title">デバイス ID を入力</string>
</resources>
@@ -0,0 +1,44 @@
<resources>
<string name="app_name">Syncthing Lite</string>
<string name="folder_list_empty_message">Geen map beschikbaar</string>
<string name="clear_local_cache_index_label">Lokaal cachegeheugen/index wissen</string>
<string name="devices_list_view_empty_message">Geen apparaten beschikbaar</string>
<string name="invalid_device_id">Fout: ongeldigen apparaats-ID</string>
<string name="dialog_downloading_file">Bestand %1$s wordt gedownload</string>
<string name="toast_file_download_failed">Download van bestand mislukt</string>
<string name="toast_open_file_failed">Gene compatibelen app gevonden</string>
<string name="toast_file_upload_failed">Uploaden van bestand mislukt</string>
<string name="toast_upload_complete">Uploaden van bestand voltooid</string>
<string name="dialog_uploading_file">Bestand %1$s wordt geüpload</string>
<string name="clear_cache_and_index_title">Lokaal cachegeheugen en index wissen?</string>
<string name="clear_cache_and_index_body">Alle lokale cache- en indexgegevens wissen?</string>
<string name="index_update_progress_label">Index voor map %1$s wordt bijgewerkt, %2$d%% gesynchroniseerd</string>
<string name="loading_config_starting_syncthing_client">Configuratie wordt geladen, Syncthing-cliënt wordt opgestart…</string>
<string name="last_modified_time">Laatst gewijzigd: %1$s</string>
<string name="remove_device_title">Apparaat %1$s verwijderen?</string>
<string name="remove_device_message">%1$s verwijderen uit de lijst van gekende apparaten?</string>
<string name="device_import_success">Apparaat %1$s geïmporteerd</string>
<string name="device_already_known">Apparaat %1$s reeds aanwezig</string>
<string name="folders_label">Mappen</string>
<string name="devices_label">Apparaten</string>
<string name="folder_label_format">%1$s (%2$s)</string>
<string name="folder_content_info">%1$s, %2$d bestanden, %3$d mappen</string>
<string name="file_info">%1$s, laatst gewijzigd %2$s</string>
<string name="show_device_id">Apparaats-ID tonen</string>
<string name="device_id">Apparaats-ID</string>
<string name="device_id_copied">Apparaats-ID gekopieerd naar klembord</string>
<string name="share_device_id_chooser">Apparaats-ID delen met</string>
<string name="other_syncthing_instance_title">Een andere Syncthing-instantie wordt reeds uitgevoerd</string>
<string name="other_syncthing_instance_message">Lokale ontdekking gaat niet werken. Stopt de andere Syncthing-instantie voor lokale ontdekking in te schakelen.</string>
<string name="intro_page_one_title">Welkom bij Syncthing Lite</string>
<string name="intro_page_one_description">Syncthing vervangt niet-vrije synchronisatie- en clouddiensten door iets opens, betrouwbaars en gedecentraliseerds. Uw gegevens behoren enkel u toe en gij bepaalt waar dat ze worden opgeslagen, of dat ze worden gedeeld met een derde partij en hoe dat ze over het internet worden verzonden.</string>
<string name="intro_page_two_title">Voegt een apparaat toe</string>
<string name="intro_page_three_title">Deelt uw mappen</string>
<string name="intro_page_two_description">Voert ne Syncthing-apparaats-ID in, of scant nen apparaats-ID van een QR-code</string>
<string name="intro_page_three_description">Aanvaard nu het apparaat met ID %1$s, en deelt er een map mee. Het kan enkele minuten duren vooraleer dat de apparaten verbinding maken.</string>
<string name="settings">Instellingen</string>
<string name="settings_app_version_title">Appversie</string>
<string name="settings_local_device_name">Naam van lokaal apparaat</string>
<string name="settings_local_device_summary">De naam die dat andere apparaten voor dit apparaat gaan zien</string>
<string name="device_id_dialog_title">Voert nen apparaats-ID in</string>
</resources>
@@ -0,0 +1,2 @@
<resources>
</resources>
+44
View File
@@ -0,0 +1,44 @@
<resources>
<string name="app_name">Syncthing Lite</string>
<string name="folder_list_empty_message">Geen map beschikbaar</string>
<string name="clear_local_cache_index_label">Lokale cache/index wissen</string>
<string name="devices_list_view_empty_message">Geen apparaten beschikbaar</string>
<string name="invalid_device_id">Fout: ongeldige apparaats-ID</string>
<string name="dialog_downloading_file">Bestand %1$s wordt gedownload</string>
<string name="toast_file_download_failed">Download van bestand mislukt</string>
<string name="toast_open_file_failed">Geen compatibele app gevonden</string>
<string name="toast_file_upload_failed">Uploaden van bestand mislukt</string>
<string name="toast_upload_complete">Uploaden van bestand voltooid</string>
<string name="dialog_uploading_file">Bestand %1$s wordt geüpload</string>
<string name="clear_cache_and_index_title">Lokale cache en index wissen?</string>
<string name="clear_cache_and_index_body">Alle lokale cache- en indexgegevens wissen?</string>
<string name="index_update_progress_label">Index voor map %1$s wordt bijgewerkt, %2$d%% gesynchroniseerd</string>
<string name="loading_config_starting_syncthing_client">Configuratie wordt geladen, Syncthing-cliënt wordt opgestart…</string>
<string name="last_modified_time">Laatst gewijzigd: %1$s</string>
<string name="remove_device_title">Apparaat %1$s verwijderen?</string>
<string name="remove_device_message">%1$s verwijderen uit de lijst van gekende apparaten?</string>
<string name="device_import_success">Apparaat %1$s geïmporteerd</string>
<string name="device_already_known">Apparaat %1$s reeds aanwezig</string>
<string name="folders_label">Mappen</string>
<string name="devices_label">Apparaten</string>
<string name="folder_label_format">%1$s (%2$s)</string>
<string name="folder_content_info">%1$s, %2$d bestanden, %3$d mappen</string>
<string name="file_info">%1$s, laatst gewijzigd %2$s</string>
<string name="show_device_id">Apparaats-ID tonen</string>
<string name="device_id">Apparaats-ID</string>
<string name="device_id_copied">Apparaats-ID gekopieerd naar klembord</string>
<string name="share_device_id_chooser">Apparaats-ID delen met</string>
<string name="other_syncthing_instance_title">Een andere Syncthing-instantie wordt reeds uitgevoerd</string>
<string name="other_syncthing_instance_message">Lokale ontdekking zal niet werken. Stop de andere Syncthing-instantie om lokale ontdekking in te schakelen.</string>
<string name="intro_page_one_title">Welkom bij Syncthing Lite</string>
<string name="intro_page_one_description">Syncthing vervangt niet-vrije synchronisatie- en clouddiensten door iets opens, betrouwbaars en gedecentraliseerds. Je gegevens behoren enkel jou toe en jij bepaalt waar ze worden opgeslagen, of ze gedeeld worden met een derde partij en hoe ze over het internet verstuurd worden.</string>
<string name="intro_page_two_title">Voeg een apparaat toe</string>
<string name="intro_page_three_title">Deel je mappen</string>
<string name="intro_page_two_description">Voer een Syncthing-apparaats-ID in, of scan een apparaats-ID van een QR-code</string>
<string name="intro_page_three_description">Accepteer nu het apparaat met ID %1$s, en deel er een map mee. Het kan enkele minuten duren voordat de apparaten verbinden.</string>
<string name="settings">Instellingen</string>
<string name="settings_app_version_title">Appversie</string>
<string name="settings_local_device_name">Naam van lokaal apparaat</string>
<string name="settings_local_device_summary">De naam die andere apparaten voor dit apparaat zullen zien</string>
<string name="device_id_dialog_title">Voer een apparaats-ID in</string>
</resources>
@@ -0,0 +1,2 @@
<resources>
</resources>
+32 -2
View File
@@ -3,7 +3,7 @@
<string name="folder_list_empty_message">Nici un director disponibil</string>
<string name="clear_local_cache_index_label">Curăță indexul/memoria locală</string>
<string name="devices_list_view_empty_message">Nici un dispozitiv disponibil</string>
<string name="device_id_dialog_title">Introduceți ID dispozitiv</string>
<string name="invalid_device_id">Eroare: ID dispozitiv invalid</string>
<string name="dialog_downloading_file">Se descarcă fișierul %1$s</string>
<string name="toast_file_download_failed">Descărcarea fișierului a eșuat</string>
<string name="toast_open_file_failed">Nu a fost găsită nici o aplicație compatibilă</string>
@@ -12,8 +12,11 @@
<string name="dialog_uploading_file">Se încarcă fișierul %1$s</string>
<string name="clear_cache_and_index_title">Se curăță memoria locală și indexul?</string>
<string name="clear_cache_and_index_body">Se curăță datele din memoria locală și datele indexului?</string>
<string name="index_update_progress_label">Actualizarea indexul pentru directorul %1$s, %2$d %% sincronizat</string>
<string name="loading_config_starting_syncthing_client">Încărcare setări, pornire client syncthing…</string>
<string name="last_modified_time">Modificat ultima dată pe: %1$s</string>
<string name="remove_device_title">Ștergere dispozitiv %1$s\?</string>
<string name="remove_device_message">Șterge %1$s din lista dispozitivelor cunoscute?</string>
<string name="device_import_success">Dispozitiv importat cu succes %1$s</string>
<string name="device_already_known">Dispozitiv deja prezent %1$s</string>
<string name="folders_label">Directoare</string>
@@ -21,4 +24,31 @@
<string name="folder_label_format">%1$s (%2$s)</string>
<string name="folder_content_info">%1$s, %2$d fișier(e), %3$d director(oare)</string>
<string name="file_info">%1$s, modificat ultima dată pe %2$s</string>
</resources>
<string name="show_device_id">Arată ID dispozitiv</string>
<string name="device_id">ID dispozitiv</string>
<string name="device_id_copied">ID dispozitiv copiat în memorie</string>
<string name="share_device_id_chooser">Partajează ID dispozitiv cu</string>
<string name="other_syncthing_instance_title">O altă instanță de Syncthing rulează</string>
<string name="other_syncthing_instance_message">Descoperire locală nu va funcționa. Opriți cealaltă instanță de Syncthing pentru a activa descoperirea locală.</string>
<string name="intro_page_one_title">Bine ați venit la Syncthing Lite</string>
<string name="intro_page_one_description">Syncthing înlocuiește serviciile proprietare de sincronizare și stocare
tip cloud cu ceva deschis, de încredere și descentralizat. Datele
dumneavoastră vă aparțin în totalitate și meritați să decideți unde vor
fi stocate, dacă vor fi partajate cu terțe entități precum și cum vor fi
trimise prin Internet.</string>
<string name="intro_page_two_title">Adaugă un dispozitiv</string>
<string name="intro_page_three_title">Partajați-vă directoarele</string>
<string name="intro_page_two_description">Introduceți ID-ul Syncthing al unui dispozitiv sau scanați ID-ul unui dispozitiv dintr-un cod QR</string>
<string name="intro_page_three_description">Acceptați acum dispozitivul cu ID-ul %1$s, și partajați un director cu el. S-ar putea să dureze câteva minute până când dispozitivele se vor conecta.</string>
<string name="settings">Setări</string>
<string name="settings_app_version_title">Versiune aplicație</string>
<string name="settings_local_device_name">Nume local dispozitiv</string>
<string name="settings_local_device_summary">Numele pe care celălalt dispozitiv îl va vedea pentru acest dispozitiv</string>
<string name="settings_shutdown_delay_title">Temporizare oprire</string>
<string name="settings_shutdown_delay_summary">După cât timp se va închide clientul Syncthing în funcție de ultima utilizare</string>
<string name="device_id_dialog_title">Introduceți ID dispozitiv</string>
<string name="settings_shutdown_delay_10_seconds">10 secunde</string>
<string name="settings_shutdown_delay_30_seconds">30 secunde</string>
<string name="settings_shutdown_delay_1_minute">1 minut</string>
<string name="settings_shutdown_delay_5_minutes">5 minute</string>
</resources>
+50
View File
@@ -0,0 +1,50 @@
<resources>
<string name="app_name">Syncthing Lite</string>
<string name="folder_list_empty_message">Ingen mapp tillgänglig</string>
<string name="clear_local_cache_index_label">Rensa lokala cache/index</string>
<string name="devices_list_view_empty_message">Inga enheter tillgängliga</string>
<string name="invalid_device_id">Fel: Ogiltigt enhets-ID</string>
<string name="dialog_downloading_file">Hämtar filen %1$s</string>
<string name="toast_file_download_failed">Misslyckades med att hämta filen</string>
<string name="toast_open_file_failed">Ingen kompatibel app hittades</string>
<string name="toast_file_upload_failed">Filöverföring misslyckades</string>
<string name="toast_upload_complete">Filöverföring färdig</string>
<string name="dialog_uploading_file">Överför filen %1$s</string>
<string name="clear_cache_and_index_title">Rensa cache och index lokalt?</string>
<string name="clear_cache_and_index_body">Rensa all cachedata och indexdata lokalt?</string>
<string name="index_update_progress_label">Index uppdatering för mappen %1$s, %2$d%% synkroniserad</string>
<string name="loading_config_starting_syncthing_client">Läser in konfiguration, starta synkroniserande klient...</string>
<string name="last_modified_time">Senast ändrad: %1$s</string>
<string name="remove_device_title">Ta bort enheten %1$s\?</string>
<string name="remove_device_message">Ta bort %1$s från listan över kända enheter?</string>
<string name="device_import_success">Importerad enheten %1$s</string>
<string name="device_already_known">Enhet som redan finns %1$s</string>
<string name="folders_label">Mappar</string>
<string name="devices_label">Enheter</string>
<string name="folder_label_format">%1$s (%2$s)</string>
<string name="folder_content_info">%1$s, %2$d filer, %3$d kataloger</string>
<string name="file_info">%1$s, senast ändrad %2$s</string>
<string name="show_device_id">Visa enhets-ID</string>
<string name="device_id">Enhets-ID</string>
<string name="device_id_copied">Enhets-ID kopierad till urklipp</string>
<string name="share_device_id_chooser">Dela enhets-ID med</string>
<string name="other_syncthing_instance_title">En annan Syncthing-instans körs</string>
<string name="other_syncthing_instance_message">Lokal upptäckt kommer inte att fungera. Stoppa den andra Syncthing-instansen för att möjliggöra lokal upptäckt.</string>
<string name="intro_page_one_title">Välkommen till Syncthing Lite</string>
<string name="intro_page_one_description">Syncthing ersätter proprietära synkroniserings- och molntjänster med något öppet, pålitligt och decentraliserat. Din data är endast din data och du förtjänar att välja var den lagras, om den delas med en tredjepart och hur den överförs över Internet.</string>
<string name="intro_page_two_title">Lägg till en enhet</string>
<string name="intro_page_three_title">Dela dina mappar</string>
<string name="intro_page_two_description">Ange ett Syncthing enhets-ID, eller skanna ett enhets-ID-nummer från en QR-kod</string>
<string name="intro_page_three_description">Acceptera nu enheten med ID %1$s och dela en mapp med den. Det kan ta några minuter tills enheterna ansluter.</string>
<string name="settings">Inställningar</string>
<string name="settings_app_version_title">Appversion</string>
<string name="settings_local_device_name">Lokala enhetens namn</string>
<string name="settings_local_device_summary">Namnet som andra enheter kommer att se för den här enheten</string>
<string name="settings_shutdown_delay_title">Avstängningsfördröjning</string>
<string name="settings_shutdown_delay_summary">Tid innan du stänger av Syncthing-klienten efter den senaste användningen</string>
<string name="device_id_dialog_title">Ange enhets-ID</string>
<string name="settings_shutdown_delay_10_seconds">10 sekunder</string>
<string name="settings_shutdown_delay_30_seconds">30 sekunder</string>
<string name="settings_shutdown_delay_1_minute">1 minut</string>
<string name="settings_shutdown_delay_5_minutes">5 minuter</string>
</resources>
+2
View File
@@ -0,0 +1,2 @@
<resources>
</resources>
@@ -0,0 +1,35 @@
<resources>
<string name="app_name">Syncthing Lite</string>
<string name="folder_list_empty_message">没有可用的文件夹</string>
<string name="clear_local_cache_index_label">清除本地缓存/索引</string>
<string name="devices_list_view_empty_message">没有可用的设备</string>
<string name="invalid_device_id">无效的设备 ID</string>
<string name="dialog_downloading_file">正下载文件 %1$s</string>
<string name="toast_file_download_failed">下载文件失败</string>
<string name="toast_open_file_failed">没有找到兼容的程序</string>
<string name="toast_file_upload_failed">上传文件失败</string>
<string name="toast_upload_complete">文件上传完成</string>
<string name="dialog_uploading_file">正上传文件 %1$s</string>
<string name="clear_cache_and_index_title">确定清除本地缓存和索引?</string>
<string name="clear_cache_and_index_body">确定清除全部本地缓存数据和索引数据?</string>
<string name="loading_config_starting_syncthing_client">载入配置,正在启动 syncthing 客户端</string>
<string name="remove_device_title">移除设备: %1$s</string>
<string name="device_import_success">成功导入的设备: %1$s</string>
<string name="device_already_known">已经存在的设备: %1$s</string>
<string name="folders_label">文件夹</string>
<string name="devices_label">设备</string>
<string name="show_device_id">显示设备 ID</string>
<string name="device_id">设备 ID</string>
<string name="device_id_copied">设备 ID 已复制到剪贴板</string>
<string name="share_device_id_chooser">分享设备 ID 于</string>
<string name="other_syncthing_instance_title">另一个 Syncthing 实例正在运行</string>
<string name="intro_page_one_title">欢迎使用 Syncthing Lite</string>
<string name="intro_page_two_title">添加一个设备</string>
<string name="intro_page_three_title">分享您的文件夹</string>
<string name="intro_page_two_description">输入一个 Syncthing 设备 ID,或者通过 QR 码扫描一个设备 ID</string>
<string name="settings">设定</string>
<string name="settings_app_version_title">应用版本</string>
<string name="settings_local_device_name">本地设备名称</string>
<string name="settings_local_device_summary">其他设备将会看到这台设备的名字</string>
<string name="device_id_dialog_title">输入设备 ID</string>
</resources>
+1 -1
View File
@@ -49,7 +49,7 @@
<string name="settings_shutdown_delay_5_minutes">5 minutes</string>
<string name="dialog_warning_reconnect_problem">
Due to the behaviour of this App and the behaviour of the Syncthing Server,
you can\'t reconnct for some minutes if the App was killed (due to removing from the recent App list)
you can\'t reconnect for some minutes if the App was killed (due to removing from the recent App list)
or the connection was interrupted.
This does not apply to local discovery connections.
</string>
+1
View File
@@ -17,6 +17,7 @@ buildscript {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.github.ben-manes:gradle-versions-plugin:0.17.0'
classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.6'
classpath 'com.github.triplet.gradle:play-publisher:1.2.0'
}
}
@@ -13,7 +13,6 @@
*/
package net.syncthing.java.core.beans
import org.slf4j.LoggerFactory
import java.net.InetAddress
import java.net.InetSocketAddress
import java.net.UnknownHostException
@@ -28,8 +27,11 @@ class DeviceAddress private constructor(val deviceId: String, private val instan
val score = score ?: Integer.MAX_VALUE
private val lastModified = lastModified ?: Date()
@Deprecated(message = "should use deviceIdObject instead")
fun deviceId() = DeviceId(deviceId)
val deviceIdObject: DeviceId by lazy { DeviceId(deviceId) }
@Throws(UnknownHostException::class)
private fun getInetAddress(): InetAddress = InetAddress.getByName(address.replaceFirst("^[^:]+://".toRegex(), "").replaceFirst("(:[0-9]+)?(/.*)?$".toRegex(), ""))
+1
View File
@@ -8,6 +8,7 @@ dependencies {
compile "commons-cli:commons-cli:1.4"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "com.google.protobuf:protobuf-lite:$protobuf_lite_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.30.2'
}
run {
@@ -1,5 +1,6 @@
/*
/*
* Copyright (C) 2016 Davide Imbriaco
* Copyright (C) 2018 Jonas Lochmann
*
* This Java file is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -13,66 +14,63 @@
*/
package net.syncthing.java.discovery
import kotlinx.coroutines.experimental.CancellationException
import kotlinx.coroutines.experimental.runBlocking
import kotlinx.coroutines.experimental.selects.select
import kotlinx.coroutines.experimental.withTimeout
import net.syncthing.java.core.beans.DeviceAddress
import net.syncthing.java.core.beans.DeviceId
import org.slf4j.LoggerFactory
import java.util.*
import java.io.Closeable
class DeviceAddressSupplier(private val discoveryHandler: DiscoveryHandler) : Iterable<DeviceAddress?> {
class DeviceAddressSupplier(private val peerDevices: Set<DeviceId>, private val devicesAddressesManager: DevicesAddressesManager) : Iterable<DeviceAddress?>, Closeable {
private val deviceAddressListStreams = peerDevices.map { deviceId ->
devicesAddressesManager.getDeviceAddressManager(deviceId).streamCurrentDeviceAddresses()
}
private val logger = LoggerFactory.getLogger(javaClass)
private val deviceAddressQueue = PriorityQueue<DeviceAddress>(11, compareBy { it.score })
private val queueLock = Object()
private fun getDeviceAddress(): DeviceAddress? {
synchronized(queueLock) {
return deviceAddressQueue.poll()
}
}
internal fun onNewDeviceAddressAcquired(address: DeviceAddress) {
if (address.isWorking()) {
synchronized(queueLock) {
deviceAddressQueue.add(address)
queueLock.notify()
private suspend fun getDeviceAddress(): DeviceAddress? {
return select {
deviceAddressListStreams.forEach { stream ->
stream.onReceive { it }
}
}
}
@Throws(InterruptedException::class)
fun getDeviceAddressOrWait(): DeviceAddress? = getDeviceAddressOrWait(5000)
init {
synchronized(queueLock) {
deviceAddressQueue.addAll(discoveryHandler.getAllWorkingDeviceAddresses())// note: slight risk of duplicate address loading
}
suspend fun getDeviceAddressOrWait(timeout: Long) = withTimeout(timeout) {
getDeviceAddress()
}
@Throws(InterruptedException::class)
private fun getDeviceAddressOrWait(timeout: Long): DeviceAddress? {
synchronized(queueLock) {
if (deviceAddressQueue.isEmpty()) {
queueLock.wait(timeout)
}
return getDeviceAddress()
}
suspend fun getDeviceAddressOrWait() = getDeviceAddressOrWait(5000L)
override fun close() {
deviceAddressListStreams.forEach { it.cancel() }
}
@Deprecated(message = "iterator is blocking")
override fun iterator(): Iterator<DeviceAddress?> {
return object : Iterator<DeviceAddress?> {
private var hasNext: Boolean? = null
private var next: DeviceAddress? = null
override fun hasNext(): Boolean {
if (hasNext == null) {
try {
next = getDeviceAddressOrWait()
} catch (ex: InterruptedException) {
next = runBlocking {
getDeviceAddressOrWait()
}
} catch (ex: CancellationException) {
logger.warn("", ex)
}
hasNext = next != null
}
if (hasNext == false) {
close()
}
return hasNext!!
}
@@ -0,0 +1,68 @@
/*
* Copyright (C) 2018 Jonas Lochmann
*
* This Java file is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.syncthing.java.discovery
import kotlinx.coroutines.experimental.channels.Channel
import kotlinx.coroutines.experimental.channels.ReceiveChannel
import net.syncthing.java.core.beans.DeviceAddress
import net.syncthing.java.core.beans.DeviceId
class DeviceAddressesManager (val deviceId: DeviceId) {
private val lock = Object()
private val deviceAddressesCache = mutableListOf<DeviceAddress>()
private val listeners = mutableListOf<(DeviceAddress) -> Unit>()
fun putAddress(address: DeviceAddress) {
if (address.deviceIdObject != deviceId) {
throw IllegalArgumentException()
}
synchronized(lock) {
deviceAddressesCache.add(address)
listeners.forEach { it(address) }
}
}
private fun addListener(listener: (DeviceAddress) -> Unit) {
synchronized(lock) {
listeners.add(listener)
}
}
private fun removeListener(listener: (DeviceAddress) -> Unit) {
synchronized(lock) {
listeners.remove(listener)
}
}
// this creates a copy of the set
fun getCurrentDeviceAddresses() = synchronized(lock) {
deviceAddressesCache.toList()
}
fun streamCurrentDeviceAddresses(): ReceiveChannel<DeviceAddress> = Channel<DeviceAddress>(capacity = Channel.UNLIMITED).apply {
val listener: (DeviceAddress) -> Unit = {
offer(it)
}
invokeOnClose {
removeListener(listener)
}
synchronized(lock) {
addListener(listener)
deviceAddressesCache.forEach { listener(it) }
}
}
}
@@ -0,0 +1,34 @@
/*
* Copyright (C) 2018 Jonas Lochmann
*
* This Java file is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.syncthing.java.discovery
import net.syncthing.java.core.beans.DeviceId
class DevicesAddressesManager {
private val data = mutableMapOf<DeviceId, DeviceAddressesManager>()
fun getDeviceAddressManager(deviceId: DeviceId) = synchronized(data) {
val item = data[deviceId]
if (item != null) {
item
} else {
val newItem = DeviceAddressesManager(deviceId)
data[deviceId] = newItem
newItem
}
}
}
@@ -1,4 +1,4 @@
/*
/*
* Copyright (C) 2016 Davide Imbriaco
* Copyright (C) 2018 Jonas Lochmann
*
@@ -14,91 +14,98 @@
*/
package net.syncthing.java.discovery
import kotlinx.coroutines.experimental.GlobalScope
import kotlinx.coroutines.experimental.launch
import net.syncthing.java.core.beans.DeviceAddress
import net.syncthing.java.core.beans.DeviceId
import net.syncthing.java.core.configuration.Configuration
import net.syncthing.java.core.utils.awaitTerminationSafe
import net.syncthing.java.core.utils.submitLogging
import net.syncthing.java.discovery.protocol.GlobalDiscoveryHandler
import net.syncthing.java.discovery.protocol.LocalDiscoveryHandler
import net.syncthing.java.discovery.utils.AddressRanker
import org.apache.commons.lang3.tuple.Pair
import org.slf4j.LoggerFactory
import java.io.Closeable
import java.util.*
import java.util.concurrent.Executors
class DiscoveryHandler(private val configuration: Configuration) : Closeable {
private val logger = LoggerFactory.getLogger(javaClass)
private val globalDiscoveryHandler = GlobalDiscoveryHandler(configuration)
private val localDiscoveryHandler = LocalDiscoveryHandler(configuration, { _, deviceAddresses ->
private val localDiscoveryHandler = LocalDiscoveryHandler(configuration, { message ->
logger.info("received device address list from local discovery")
processDeviceAddressBg(deviceAddresses)
GlobalScope.launch {
processDeviceAddressBg(message.addresses)
}
}, { deviceId ->
onMessageFromUnknownDeviceListeners.forEach { listener -> listener(deviceId) }
})
private val executorService = Executors.newCachedThreadPool()
private val deviceAddressMap = Collections.synchronizedMap(hashMapOf<Pair<DeviceId, String>, DeviceAddress>())
private val deviceAddressSupplier = DeviceAddressSupplier(this)
private val devicesAddressesManager = DevicesAddressesManager()
private var isClosed = false
private val onMessageFromUnknownDeviceListeners = Collections.synchronizedSet(HashSet<(DeviceId) -> Unit>())
private var shouldLoadFromGlobal = true
private var shouldStartLocalDiscovery = true
fun getAllWorkingDeviceAddresses() = deviceAddressMap.values.filter { it.isWorking() }
private fun doGlobalDiscoveryIfNotYetDone() {
// TODO: timeout for reload
// TODO: retry if connectivity changed
private fun updateAddressesBg() {
if (shouldLoadFromGlobal) {
shouldLoadFromGlobal = false
GlobalScope.launch {
processDeviceAddressBg(globalDiscoveryHandler.query(configuration.peerIds))
}
}
}
private fun initLocalDiscoveryIfNotYetDone() {
if (shouldStartLocalDiscovery) {
shouldStartLocalDiscovery = false
localDiscoveryHandler.startListener()
localDiscoveryHandler.sendAnnounceMessage()
}
if (shouldLoadFromGlobal) {
shouldLoadFromGlobal = false //TODO timeout for reload
executorService.submitLogging {
for (deviceId in configuration.peerIds) {
globalDiscoveryHandler.query(deviceId, this::processDeviceAddressBg)
}
}
}
}
private fun processDeviceAddressBg(deviceAddresses: Iterable<DeviceAddress>) {
private suspend fun processDeviceAddressBg(deviceAddresses: Iterable<DeviceAddress>) {
if (isClosed) {
logger.debug("discarding device addresses, discovery handler already closed")
} else {
executorService.submitLogging {
val list = deviceAddresses.toList()
val peers = configuration.peerIds
//do not process address already processed
list.filter { deviceAddress ->
!peers.contains(deviceAddress.deviceId()) || deviceAddressMap.containsKey(Pair.of(DeviceId(deviceAddress.deviceId), deviceAddress.address))
}
AddressRanker.pingAddresses(list)
.forEach { putDeviceAddress(it) }
val list = deviceAddresses.toList()
val peers = configuration.peerIds
//do not process address already processed
list.filter { deviceAddress ->
!peers.contains(deviceAddress.deviceIdObject)
}
AddressRanker.pingAddresses(list)
.forEach { putDeviceAddress(it) }
}
}
private fun putDeviceAddress(deviceAddress: DeviceAddress) {
deviceAddressMap[Pair.of(DeviceId(deviceAddress.deviceId), deviceAddress.address)] = deviceAddress
deviceAddressSupplier.onNewDeviceAddressAcquired(deviceAddress)
devicesAddressesManager.getDeviceAddressManager(
deviceId = deviceAddress.deviceIdObject
).putAddress(deviceAddress)
}
fun newDeviceAddressSupplier(): DeviceAddressSupplier {
updateAddressesBg()
return deviceAddressSupplier
if (isClosed) {
throw IllegalStateException()
}
doGlobalDiscoveryIfNotYetDone()
initLocalDiscoveryIfNotYetDone()
return DeviceAddressSupplier(
peerDevices = configuration.peerIds,
devicesAddressesManager = devicesAddressesManager
)
}
override fun close() {
if (!isClosed) {
isClosed = true
localDiscoveryHandler.close()
globalDiscoveryHandler.close()
executorService.shutdown()
executorService.awaitTerminationSafe()
}
}
@@ -16,14 +16,12 @@ package net.syncthing.java.discovery
import net.syncthing.java.core.beans.DeviceAddress
import net.syncthing.java.core.beans.DeviceId
import net.syncthing.java.core.configuration.Configuration
import net.syncthing.java.core.security.KeystoreHandler
import net.syncthing.java.discovery.protocol.GlobalDiscoveryHandler
import net.syncthing.java.discovery.protocol.LocalDiscoveryHandler
import org.apache.commons.cli.DefaultParser
import org.apache.commons.cli.HelpFormatter
import org.apache.commons.cli.Option
import org.apache.commons.cli.Options
import org.apache.commons.io.FileUtils
import java.io.File
import java.util.concurrent.CountDownLatch
@@ -85,10 +83,10 @@ class Main {
private fun queryLocalDiscovery(configuration: Configuration, deviceId: DeviceId): Collection<DeviceAddress> {
val lock = Object()
val discoveredAddresses = mutableListOf<DeviceAddress>()
val handler = LocalDiscoveryHandler(configuration, { discoveredDeviceId, deviceAddresses ->
val handler = LocalDiscoveryHandler(configuration, { message ->
synchronized(lock) {
if (discoveredDeviceId == deviceId) {
discoveredAddresses.addAll(deviceAddresses)
if (message.deviceId == deviceId) {
discoveredAddresses.addAll(message.addresses)
lock.notify()
}
}
@@ -0,0 +1,54 @@
/*
* Copyright (C) 2016 Davide Imbriaco
* Copyright (C) 2018 Jonas Lochmann
*
* This Java file is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.syncthing.java.discovery.protocol
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken
import java.util.*
data class AnnouncementMessage(val addresses: List<String>) {
companion object {
private const val ADDRESSES = "addresses"
fun parse(reader: JsonReader): AnnouncementMessage {
var addresses = listOf<String>()
reader.beginObject()
while (reader.hasNext()) {
when (reader.nextName()) {
ADDRESSES -> {
val newAddresses = ArrayList<String>()
if (reader.peek() == JsonToken.NULL) {
reader.skipValue()
} else {
reader.beginArray()
while (reader.hasNext()) {
newAddresses.add(reader.nextString())
}
reader.endArray()
}
addresses = Collections.unmodifiableList(newAddresses)
}
else -> reader.skipValue()
}
}
reader.endObject()
return AnnouncementMessage(addresses)
}
}
}
@@ -1,4 +1,4 @@
/*
/*
* Copyright (C) 2016 Davide Imbriaco
* Copyright (C) 2018 Jonas Lochmann
*
@@ -14,125 +14,86 @@
*/
package net.syncthing.java.discovery.protocol
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken
import kotlinx.coroutines.experimental.GlobalScope
import kotlinx.coroutines.experimental.async
import kotlinx.coroutines.experimental.coroutineScope
import kotlinx.coroutines.experimental.launch
import net.syncthing.java.core.beans.DeviceAddress
import net.syncthing.java.core.beans.DeviceId
import net.syncthing.java.core.configuration.Configuration
import net.syncthing.java.discovery.utils.AddressRanker
import org.apache.http.HttpStatus
import org.apache.http.client.methods.HttpGet
import org.apache.http.conn.ssl.SSLConnectionSocketFactory
import org.apache.http.conn.ssl.SSLContextBuilder
import org.apache.http.conn.ssl.TrustSelfSignedStrategy
import org.apache.http.impl.client.HttpClients
import org.apache.http.util.EntityUtils
import org.slf4j.LoggerFactory
import java.io.Closeable
import java.io.IOException
import java.io.StringReader
import java.security.KeyManagementException
import java.security.KeyStoreException
import java.security.NoSuchAlgorithmException
import java.util.*
internal class GlobalDiscoveryHandler(private val configuration: Configuration) : Closeable {
internal class GlobalDiscoveryHandler(private val configuration: Configuration) {
private val logger = LoggerFactory.getLogger(javaClass)
fun query(deviceId: DeviceId, callback: (List<DeviceAddress>) -> Unit) {
val addresses = pickAnnounceServers()
.map {
try {
queryAnnounceServer(it, deviceId)
} catch (e: IOException) {
logger.warn("Failed to query $it", e)
listOf<DeviceAddress>()
}
}
.flatten()
callback(addresses)
}
private fun pickAnnounceServers(): List<String> {
val list = AddressRanker
.pingAddresses(configuration.discoveryServers.map { DeviceAddress(it, "tcp://$it:443") })
return list.map { it.deviceId }
}
@Throws(IOException::class)
private fun queryAnnounceServer(server: String, deviceId: DeviceId): List<DeviceAddress> {
@Deprecated(message = "coroutine version should be used instead of callback")
fun query(deviceId: DeviceId, callback: (List<DeviceAddress>) -> Unit) = GlobalScope.launch {
try {
logger.debug("querying server {} for device id {}", server, deviceId)
val httpClient = HttpClients.custom()
.setSSLSocketFactory(SSLConnectionSocketFactory(SSLContextBuilder().loadTrustMaterial(null, TrustSelfSignedStrategy()).build(), SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER))
.build()
val httpGet = HttpGet("https://$server/v2/?device=${deviceId.deviceId}")
return httpClient.execute<List<DeviceAddress>>(httpGet) { response ->
when (response.statusLine.statusCode) {
HttpStatus.SC_NOT_FOUND -> {
logger.debug("device not found: {}", deviceId)
return@execute emptyList()
}
HttpStatus.SC_OK -> {
val announcementMessage = AnnouncementMessage.parse(
JsonReader(
StringReader(
EntityUtils.toString(response.entity)
)
)
)
return@execute (announcementMessage.addresses)
.map { DeviceAddress(deviceId.deviceId, it) }
}
else -> {
throw IOException("http error ${response.statusLine}, response ${EntityUtils.toString(response.entity)}")
}
}
}
} catch (e: Exception) {
when (e) {
is IOException, is NoSuchAlgorithmException, is KeyStoreException, is KeyManagementException ->
throw IOException(e)
else -> throw e
}
callback(query(deviceId))
} catch (ex: Exception) {
callback(emptyList())
}
}
override fun close() {}
suspend fun query(deviceIds: Collection<DeviceId>): List<DeviceAddress> {
val discoveryServers = pickAnnounceServers()
private data class AnnouncementMessage(val addresses: List<String>) {
companion object {
private const val ADDRESSES = "addresses"
return coroutineScope {
deviceIds
.distinct()
.map { deviceId ->
async {
queryAnnounceServers(
servers = discoveryServers,
deviceId = deviceId
)
}
}
.map { it.await() }
.flatten()
}
}
fun parse(reader: JsonReader): AnnouncementMessage {
var addresses = listOf<String>()
suspend fun query(deviceId: DeviceId) = queryAnnounceServers(
servers = pickAnnounceServers(),
deviceId = deviceId
)
reader.beginObject()
while (reader.hasNext()) {
when (reader.nextName()) {
ADDRESSES -> {
val newAddresses = ArrayList<String>()
suspend fun pickAnnounceServers() = AddressRanker
.pingAddresses(configuration.discoveryServers.map { DeviceAddress(it, "tcp://$it:443") })
.map { it.deviceId }
if (reader.peek() == JsonToken.NULL) {
reader.skipValue()
} else {
reader.beginArray()
while (reader.hasNext()) {
newAddresses.add(reader.nextString())
}
reader.endArray()
suspend fun queryAnnounceServers(servers: List<String>, deviceId: DeviceId) = coroutineScope {
servers
.map { server ->
async {
try {
queryAnnounceServer(server, deviceId)
} catch (ex: Exception) {
logger.warn("Failed to query $server for $deviceId", ex)
when (ex) {
is IOException -> { /* ignore */ }
is DeviceNotFoundException -> { /* ignore */ }
is TooManyRequestsException -> { /* ignore */ }
else -> throw ex
}
addresses = Collections.unmodifiableList(newAddresses)
emptyList<DeviceAddress>()
}
else -> reader.skipValue()
}
}
reader.endObject()
.map { it.await() }
.flatten()
// .distinct() is not required because the device addresses contain the used discovery server
}
return AnnouncementMessage(addresses)
}
}
companion object {
suspend fun queryAnnounceServer(server: String, deviceId: DeviceId) =
GlobalDiscoveryUtil
.queryAnnounceServer(server, deviceId)
.addresses.map { DeviceAddress(deviceId.deviceId, it) }
}
}
@@ -0,0 +1,79 @@
/*
* Copyright (C) 2016 Davide Imbriaco
* Copyright (C) 2018 Jonas Lochmann
*
* This Java file is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.syncthing.java.discovery.protocol
import com.google.gson.stream.JsonReader
import kotlinx.coroutines.experimental.Dispatchers
import kotlinx.coroutines.experimental.withContext
import net.syncthing.java.core.beans.DeviceId
import java.io.BufferedInputStream
import java.io.IOException
import java.io.InputStreamReader
import java.net.HttpURLConnection
import java.net.URL
import java.security.SecureRandom
import java.security.cert.X509Certificate
import javax.net.ssl.HostnameVerifier
import javax.net.ssl.HttpsURLConnection
import javax.net.ssl.SSLContext
import javax.net.ssl.X509TrustManager
object GlobalDiscoveryUtil {
private fun queryAnnounceServerUrl(server: String, deviceId: DeviceId) =
"https://$server/v2/?device=${deviceId.deviceId}"
suspend fun queryAnnounceServer(server: String, deviceId: DeviceId): AnnouncementMessage {
return withContext(Dispatchers.IO) {
val url = URL(queryAnnounceServerUrl(server, deviceId))
val connection = (url.openConnection() as HttpsURLConnection).apply {
hostnameVerifier = HostnameVerifier { _, _ -> true }
sslSocketFactory = SSLContext.getInstance("SSL").apply {
init(null, arrayOf(object: X509TrustManager {
override fun checkClientTrusted(p0: Array<out X509Certificate>?, p1: String?) {
// check nothing
}
override fun checkServerTrusted(p0: Array<out X509Certificate>?, p1: String?) {
// check nothing
}
override fun getAcceptedIssuers(): Array<X509Certificate> = emptyArray()
}), SecureRandom())
}.socketFactory
}
try {
connection.connect()
when (connection.responseCode) {
HttpURLConnection.HTTP_NOT_FOUND -> throw DeviceNotFoundException()
429 -> throw TooManyRequestsException()
HttpURLConnection.HTTP_OK -> {
JsonReader(InputStreamReader(BufferedInputStream(connection.inputStream))).use { reader ->
AnnouncementMessage.parse(reader)
}
}
else -> throw IOException("http error ${connection.responseCode}: ${connection.responseMessage}")
}
} finally {
connection.disconnect()
}
}
}
}
class DeviceNotFoundException: RuntimeException()
class TooManyRequestsException: RuntimeException()
// TODO: handle too many requests -> stop sending requests for some time
@@ -1,4 +1,4 @@
/*
/*
* Copyright (C) 2016 Davide Imbriaco
* Copyright (C) 2018 Jonas Lochmann
*
@@ -14,164 +14,55 @@
*/
package net.syncthing.java.discovery.protocol
import com.google.protobuf.ByteString
import com.google.protobuf.InvalidProtocolBufferException
import net.syncthing.java.core.beans.DeviceAddress
import kotlinx.coroutines.experimental.GlobalScope
import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.channels.consumeEach
import kotlinx.coroutines.experimental.launch
import net.syncthing.java.core.beans.DeviceId
import net.syncthing.java.core.configuration.Configuration
import net.syncthing.java.core.events.DeviceAddressReceivedEvent
import net.syncthing.java.core.utils.NetworkUtils
import net.syncthing.java.core.utils.submitLogging
import net.syncthing.java.discovery.protocol.LocalDiscoveryProtos.Announce
import org.apache.commons.io.IOUtils
import org.slf4j.LoggerFactory
import java.io.ByteArrayOutputStream
import java.io.Closeable
import java.io.DataOutputStream
import java.io.IOException
import java.net.DatagramPacket
import java.net.DatagramSocket
import java.net.InetAddress
import java.net.NetworkInterface
import java.nio.ByteBuffer
import java.util.concurrent.Executors
internal class LocalDiscoveryHandler(private val configuration: Configuration,
private val onMessageReceivedListener: (DeviceId, List<DeviceAddress>) -> Unit,
private val onMessageReceivedListener: (LocalDiscoveryMessage) -> Unit,
private val onMessageFromUnknownDeviceListener: (DeviceId) -> Unit = {}) : Closeable {
companion object {
private const val MAGIC = 0x2EA7D90B
private const val LISTENING_PORT = 21027
private const val INCOMING_BUFFER_SIZE = 1024
}
private val logger = LoggerFactory.getLogger(javaClass)
private val listeningExecutor = Executors.newSingleThreadScheduledExecutor()
private val processingExecutor = Executors.newCachedThreadPool()
private var datagramSocket: DatagramSocket? = null
private val job = Job()
fun sendAnnounceMessage() {
processingExecutor.submitLogging {
try {
val out = ByteArrayOutputStream()
DataOutputStream(out).writeInt(MAGIC)
Announce.newBuilder()
.setId(ByteString.copyFrom(configuration.localDeviceId.toHashData()))
.setInstanceId(configuration.instanceId)
.build().writeTo(out)
val data = out.toByteArray()
val networkInterfaces = NetworkInterface.getNetworkInterfaces()
while (networkInterfaces.hasMoreElements()) {
val networkInterface = networkInterfaces.nextElement()
for (interfaceAddress in networkInterface.interfaceAddresses) {
val broadcastAddress = interfaceAddress.broadcast
logger.trace("interface = {} address = {} broadcast = {}", networkInterface, interfaceAddress, broadcastAddress)
if (broadcastAddress != null) {
logger.debug("sending broadcast announce on {}", broadcastAddress)
DatagramSocket().use { broadcastSocket ->
broadcastSocket.broadcast = true
val datagramPacket = DatagramPacket(
data, data.size, broadcastAddress, LISTENING_PORT)
broadcastSocket.send(datagramPacket)
}
}
}
}
} catch (e: IOException) {
logger.warn("Failed to send local announce message", e)
}
GlobalScope.launch {
LocalDiscoveryUtil.sendAnnounceMessage(
ownDeviceId = configuration.localDeviceId,
instanceId = configuration.instanceId
)
}
}
fun startListener() {
if (datagramSocket == null || datagramSocket!!.isClosed) {
GlobalScope.launch (job) {
try {
datagramSocket = DatagramSocket(LISTENING_PORT, InetAddress.getByName("0.0.0.0"))
logger.info("Opened udp socket {}", datagramSocket!!.localSocketAddress)
} catch (e: IOException) {
logger.warn("Failed to open listening socket on port $LISTENING_PORT, ${e.message}")
return
}
LocalDiscoveryUtil.listenForAnnounceMessages().consumeEach { message ->
if (message.deviceId == configuration.localDeviceId) {
// ignore announcement received from ourselves.
} else if (!configuration.peerIds.contains(message.deviceId)) {
logger.trace("Received local announce from ${message.deviceId} which is not a peer, ignoring")
}
onMessageFromUnknownDeviceListener(message.deviceId)
} else {
logger.debug("received local announce from device id = {}", message.deviceId)
listeningExecutor.submitLogging(object : Runnable {
override fun run() {
try {
val datagramPacket = DatagramPacket(ByteArray(INCOMING_BUFFER_SIZE), INCOMING_BUFFER_SIZE)
logger.trace("waiting for message on socket addr = {}", datagramSocket!!.localSocketAddress)
datagramSocket!!.receive(datagramPacket)
processingExecutor.submitLogging { handleReceivedDatagram(datagramPacket) }
listeningExecutor.submitLogging(this)
} catch (e: IOException) {
if (e.message == "Socket closed") {
// Ignore exception on socket close.
return
onMessageReceivedListener(message)
}
logger.warn("Error receiving datagram", e)
close()
}
} catch (ex: IOException) {
logger.warn("Failed to listen for announcement messages", ex)
}
})
}
private fun handleReceivedDatagram(datagramPacket: DatagramPacket) {
try {
val sourceAddress = datagramPacket.address.hostAddress
val byteBuffer = ByteBuffer.wrap(
datagramPacket.data, datagramPacket.offset, datagramPacket.length)
val magic = byteBuffer.int
NetworkUtils.assertProtocol(magic == MAGIC, {"magic mismatch, expected $MAGIC, got $magic"})
val announce = Announce.parseFrom(ByteString.copyFrom(byteBuffer))
val deviceId = DeviceId.fromHashData(announce.id.toByteArray())
// Ignore announcement received from ourselves.
if (deviceId == configuration.localDeviceId)
return
if (!configuration.peerIds.contains(deviceId)) {
logger.trace("Received local announce from $deviceId which is not a peer, ignoring")
onMessageFromUnknownDeviceListener(deviceId)
return
}
logger.debug("received local announce from device id = {}", deviceId)
val addressesList = announce.addressesList ?: listOf<String>()
val deviceAddresses = addressesList.map { address ->
// When interpreting addresses with an unspecified address, e.g.,
// tcp://0.0.0.0:22000 or tcp://:42424, the source address of the
// discovery announcement is to be used.
DeviceAddress.Builder()
.setAddress(address.replaceFirst("tcp://(0.0.0.0|):".toRegex(), "tcp://$sourceAddress:"))
.setDeviceId(deviceId.deviceId)
.setInstanceId(announce.instanceId)
.setProducer(DeviceAddress.AddressProducer.LOCAL_DISCOVERY)
.build()
}
onMessageReceivedListener(deviceId, deviceAddresses)
} catch (ex: InvalidProtocolBufferException) {
logger.warn("error processing datagram", ex)
}
}
override fun close() {
processingExecutor.shutdown()
listeningExecutor.shutdown()
if (datagramSocket != null) {
IOUtils.closeQuietly(datagramSocket)
}
}
abstract inner class MessageReceivedEvent : DeviceAddressReceivedEvent {
abstract fun deviceId(): DeviceId
abstract override fun getDeviceAddresses(): List<DeviceAddress>
job.cancel()
}
}
@@ -0,0 +1,143 @@
/*
* Copyright (C) 2016 Davide Imbriaco
* Copyright (C) 2018 Jonas Lochmann
*
* This Java file is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.syncthing.java.discovery.protocol
import com.google.protobuf.ByteString
import kotlinx.coroutines.experimental.Dispatchers
import kotlinx.coroutines.experimental.GlobalScope
import kotlinx.coroutines.experimental.channels.ReceiveChannel
import kotlinx.coroutines.experimental.channels.produce
import kotlinx.coroutines.experimental.withContext
import net.syncthing.java.core.beans.DeviceAddress
import net.syncthing.java.core.beans.DeviceId
import net.syncthing.java.core.utils.NetworkUtils
import org.slf4j.LoggerFactory
import java.io.ByteArrayOutputStream
import java.io.DataOutputStream
import java.io.IOException
import java.net.*
import java.nio.ByteBuffer
object LocalDiscoveryUtil {
private const val LISTENING_PORT = 21027
private const val MAGIC = 0x2EA7D90B
private const val INCOMING_BUFFER_SIZE = 1024
private val logger = LoggerFactory.getLogger(javaClass)
suspend fun listenForAnnounceMessages(): ReceiveChannel<LocalDiscoveryMessage> = GlobalScope.produce {
DatagramSocket(LISTENING_PORT, InetAddress.getByName("0.0.0.0")).use { datagramSocket ->
invokeOnClose {
datagramSocket.close()
}
withContext(Dispatchers.IO) {
val datagramPacket = DatagramPacket(ByteArray(INCOMING_BUFFER_SIZE), INCOMING_BUFFER_SIZE)
while (!isClosedForSend) {
try {
datagramSocket.receive(datagramPacket)
} catch (ex: SocketException) {
if (datagramSocket.isClosed) {
// if the socket was closed by the invokeOnClose, then ignore it
return@withContext
} else {
// otherwise it's more serious and it is rethrown
throw ex
}
}
try {
val sourceAddress = datagramPacket.address.hostAddress
val byteBuffer = ByteBuffer.wrap(datagramPacket.data, datagramPacket.offset, datagramPacket.length)
val magic = byteBuffer.int
NetworkUtils.assertProtocol(magic == MAGIC) {
"magic mismatch, expected ${MAGIC}, got $magic"
}
val announce = LocalDiscoveryProtos.Announce.parseFrom(ByteString.copyFrom(byteBuffer))
val deviceId = DeviceId.fromHashData(announce.id.toByteArray())
val deviceAddresses = (announce.addressesList ?: emptyList()).map { address ->
// When interpreting addresses with an unspecified address, e.g.,
// tcp://0.0.0.0:22000 or tcp://:42424, the source address of the
// discovery announcement is to be used.
DeviceAddress.Builder()
.setAddress(address.replaceFirst("tcp://(0.0.0.0|):".toRegex(), "tcp://$sourceAddress:"))
.setDeviceId(deviceId.deviceId)
.setInstanceId(announce.instanceId)
.setProducer(DeviceAddress.AddressProducer.LOCAL_DISCOVERY)
.build()
}
val message = LocalDiscoveryMessage(
deviceId = deviceId,
addresses = deviceAddresses
)
send(message)
} catch (ex: IOException) {
logger.warn("error during handling received package", ex)
}
}
}
}
}
fun sendAnnounceMessage(ownDeviceId: DeviceId, instanceId: Long) {
val discoveryMessage = ByteArrayOutputStream().apply {
DataOutputStream(this).writeInt(MAGIC)
LocalDiscoveryProtos.Announce.newBuilder()
.setId(ByteString.copyFrom(ownDeviceId.toHashData()))
.setInstanceId(instanceId)
.build()
.writeTo(this)
}.toByteArray()
for (networkInterface in NetworkInterface.getNetworkInterfaces()) {
for (interfaceAddress in networkInterface.interfaceAddresses) {
val broadcastAddress = interfaceAddress.broadcast
logger.trace("interface = {} address = {} broadcast = {}", networkInterface, interfaceAddress, broadcastAddress)
if (broadcastAddress != null) {
logger.debug("sending broadcast announce on {}", broadcastAddress)
DatagramSocket().use { broadcastSocket ->
broadcastSocket.broadcast = true
broadcastSocket.send(DatagramPacket(
discoveryMessage,
discoveryMessage.size,
broadcastAddress,
LISTENING_PORT))
}
}
}
}
}
}
data class LocalDiscoveryMessage(val deviceId: DeviceId, val addresses: List<DeviceAddress>) {
init {
addresses.forEach { address ->
if (address.deviceIdObject != deviceId) {
throw IllegalArgumentException()
}
}
}
}
@@ -1,5 +1,6 @@
/*
/*
* Copyright (C) 2016 Davide Imbriaco
* Copyright (C) 2018 Jonas Lochmann
*
* This Java file is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -13,69 +14,65 @@
*/
package net.syncthing.java.discovery.utils
import kotlinx.coroutines.experimental.*
import net.syncthing.java.core.beans.DeviceAddress
import net.syncthing.java.core.beans.DeviceAddress.AddressType
import net.syncthing.java.core.utils.submitLogging
import org.slf4j.LoggerFactory
import java.io.Closeable
import java.io.IOException
import java.net.Socket
import java.util.concurrent.*
internal class AddressRanker private constructor(private val sourceAddresses: List<DeviceAddress>) : Closeable {
companion object {
private const val TCP_CONNECTION_TIMEOUT = 5000
private val BASE_SCORE_MAP = mapOf(
AddressType.TCP to 0,
AddressType.RELAY to 2000,
AddressType.HTTP_RELAY to 1000 * 2000,
AddressType.HTTPS_RELAY to 1000 * 2000)
private val ACCEPTED_ADDRESS_TYPES = BASE_SCORE_MAP.keys
fun pingAddresses(list: List<DeviceAddress>): List<DeviceAddress> {
AddressRanker(list).use { addressRanker ->
return addressRanker.testAndRankAndWait()
}
}
}
object AddressRanker {
private const val TCP_CONNECTION_TIMEOUT = 5000
private val BASE_SCORE_MAP = mapOf(
AddressType.TCP to 0,
AddressType.RELAY to 2000,
AddressType.HTTP_RELAY to 1000 * 2000,
AddressType.HTTPS_RELAY to 1000 * 2000
)
private val ACCEPTED_ADDRESS_TYPES = BASE_SCORE_MAP.keys
private val logger = LoggerFactory.getLogger(javaClass)
private val executorService = Executors.newCachedThreadPool()
private fun addHttpRelays(list: List<DeviceAddress>): List<DeviceAddress> {
val httpRelays = list
.filter { address ->
address.getType() == AddressType.RELAY && address.containsUriParamValue("httpUrl")
}
.map { address ->
val httpUrl = address.getUriParam("httpUrl")
address.copyBuilder().setAddress("relay-" + httpUrl!!).build()
}
return httpRelays + list
}
private fun testAndRankAndWait(): List<DeviceAddress> {
return addHttpRelays(sourceAddresses)
suspend fun pingAddresses(sourceAddresses: List<DeviceAddress>) = coroutineScope {
addHttpRelays(sourceAddresses)
.filter { ACCEPTED_ADDRESS_TYPES.contains(it.getType()) }
.map { executorService.submitLogging<DeviceAddress?> { pingAddresses(it) } }
.mapNotNull { future ->
try {
future.get((TCP_CONNECTION_TIMEOUT * 2).toLong(), TimeUnit.MILLISECONDS)
} catch (e: ExecutionException) {
logger.warn("Failed to ping device", e)
null
} catch (e: InterruptedException) {
logger.warn("Failed to ping device", e)
null
.toList() // the following should happen parallel
.map {
async {
try {
withTimeout(TCP_CONNECTION_TIMEOUT * 2L) {
// this nested async ensures that cancelling/ the timeout has got an effect without delay
GlobalScope.async (Dispatchers.IO) {
pingAddressSync(it)
}.await()
}
} catch (ex: Exception) {
logger.warn("Failed to ping device", ex)
null
}
}
}
.map { it.await() }
.filterNotNull()
.sortedBy { it.score }
}
private fun pingAddresses(deviceAddress: DeviceAddress): DeviceAddress? {
private fun getHttpRelays(list: List<DeviceAddress>) = list
.asSequence()
.filter { address ->
address.getType() == AddressType.RELAY && address.containsUriParamValue("httpUrl")
}
.map { address ->
val httpUrl = address.getUriParam("httpUrl")
address.copyBuilder().setAddress("relay-" + httpUrl!!).build()
}
private fun addHttpRelays(list: List<DeviceAddress>) = getHttpRelays(list) + list
private fun pingAddressSync(deviceAddress: DeviceAddress): DeviceAddress? {
val startTime = System.currentTimeMillis()
try {
Socket().use { socket ->
socket.soTimeout = TCP_CONNECTION_TIMEOUT
@@ -85,18 +82,10 @@ internal class AddressRanker private constructor(private val sourceAddresses: Li
logger.debug("address unreacheable = $deviceAddress, ${ex.message}")
return null
}
val ping = (System.currentTimeMillis() - startTime).toInt()
val ping = (System.currentTimeMillis() - startTime).toInt()
val baseScore = BASE_SCORE_MAP[deviceAddress.getType()] ?: 0
return deviceAddress.copyBuilder().setScore(ping + baseScore).build()
}
override fun close() {
executorService.shutdown()
try {
executorService.awaitTermination(2, TimeUnit.SECONDS)
} catch (ex: InterruptedException) {
logger.warn("", ex)
}
}
}