Improve various details

This commit is contained in:
Martchus 2016-09-01 16:34:30 +02:00
parent 4737268e20
commit 2360b2a482
30 changed files with 1351 additions and 406 deletions

View File

@ -19,11 +19,15 @@ set(HEADER_FILES
data/syncthingconnection.h
data/syncthingdirectorymodel.h
data/syncthingdevicemodel.h
data/syncthingconfig.h
data/utils.h
)
set(SRC_FILES
data/syncthingconnection.cpp
data/syncthingdirectorymodel.cpp
data/syncthingdevicemodel.cpp
data/syncthingconfig.cpp
data/utils.cpp
)
set(WIDGETS_HEADER_FILES
@ -86,6 +90,7 @@ set(REQUIRED_ICONS
network-card
window-close
edit-copy
edit-paste
preferences-other
view-barcode
folder-open
@ -98,6 +103,13 @@ set(REQUIRED_ICONS
network-server
folder-sync
internet-web-browser
network-connect
system-run
system-search
preferences-desktop
preferences-desktop-notification
preferences-desktop-icons
preferences-desktop-locale
)
# find c++utilities

View File

@ -2,14 +2,27 @@
Qt 5-based tray application for [Syncthing](https://github.com/syncthing/syncthing)
* Still under development
* Designed to work under any desktop environment with tray icon support
* Tested under Plasma 5 and Openbox/qt5ct/Tint2
* Could be shown as regular window if no tray icon support is available
* Doesn't require desktop environment specific libraries
* Provides quick access to most frequently used features but does not intend to replace the official web UI
* Check state of directories and devices
* Check current traffic statistics
* Display further details about direcoties and devices, like last file, last
scan, ...
* Trigger re-scan of a specific directory
* Open a directory with the default file browser
* Pause/resume devices
* Shows Syncthing notifications
* Does *not* allow configuring Syncthing itself (currently I do not intend to add this feature as it could cause more harm than good when not implemented correctly)
* Provides quick access to the official web UI
* Utilizes either Qt WebKit or Qt WebEngine
* Can be built without web view support as well (then the web UI is opened in the regular browser)
* Still under development; the following features are planned
* Connect to multiple instances of Syncthing at a time
* Add option to conveniently add the tray to the applications launched when the desktop environment starts
* Add option to launch Syncthing when the tray is started and log stdout/stderr (would make sense for me under Windows, otherwise starting Syncthing via systemd is more preferable of course)
## Screenshots
### Under Openbox/Tint2
@ -36,3 +49,9 @@ The following Qt 5 modules are requried: core network gui widgets webenginewidge
#### Select Qt modules for WebView
* If Qt WebKitWidgets is installed on the system, the tray will link against it. Otherwise it will link against Qt WebEngineWidgets.
* To force usage of Qt WebKit/Qt WebEngine or to disable both add `-DWEBVIEW_PROVIDER=webkit/webengine/none` to the CMake arguments.
BTW: I still prefer the deprecated Qt WebKit because
* I currently don't know how to allow a particular self-signed certificate in Qt WebEngine. (Currently any self-signed certificate is accepted!)
* Qt WebEngine can not be built with mingw-w64.
* Qt WebEngine is more buggy in my experience.
* security issues are not a concern because no other website than the Syncthing web UI is shown.

View File

@ -1,79 +1,87 @@
#include "./settings.h"
#include "../gui/trayicon.h"
#include "../gui/traywidget.h"
#include "resources/config.h"
#include <c++utilities/application/argumentparser.h>
#include <c++utilities/application/commandlineutils.h>
#include <c++utilities/application/failure.h>
#include <qtutilities/resources/qtconfigarguments.h>
#include <qtutilities/resources/resources.h>
#include <qtutilities/resources/importplugin.h>
#include <qtutilities/settingsdialog/qtsettings.h>
#include <QApplication>
#include <QMessageBox>
#include <iostream>
using namespace std;
using namespace ApplicationUtilities;
using namespace QtGui;
int main(int argc, char *argv[])
{
SET_APPLICATION_INFO;
// setup argument parser
ArgumentParser parser;
HelpArgument helpArg(parser);
// Qt configuration arguments
QT_CONFIG_ARGUMENTS qtConfigArgs;
Argument windowedArg("windowed", 'w', "shows the UI in a regular window");
windowedArg.setCombinable(true);
qtConfigArgs.qtWidgetsGuiArg().addSubArgument(&windowedArg);
parser.setMainArguments({&qtConfigArgs.qtWidgetsGuiArg(), &helpArg});
try {
parser.parseArgs(argc, argv);
if(qtConfigArgs.qtWidgetsGuiArg().isPresent()) {
SET_QT_APPLICATION_INFO;
QApplication application(argc, argv);
Settings::restore();
Settings::qtSettings().apply();
qtConfigArgs.applySettings(true);
LOAD_QT_TRANSLATIONS;
QtUtilitiesResources::init();
int res;
#ifndef QT_NO_SYSTEMTRAYICON
if(QSystemTrayIcon::isSystemTrayAvailable()) {
if(windowedArg.isPresent()) {
TrayWidget trayWidget;
trayWidget.show();
res = application.exec();
} else {
application.setQuitOnLastWindowClosed(false);
TrayIcon trayIcon;
trayIcon.show();
res = application.exec();
}
} else {
QMessageBox::critical(nullptr, QApplication::applicationName(), QApplication::translate("main", "The system tray is (currently) not available."));
res = -1;
}
#else
QMessageBox::critical(nullptr, QApplication::applicationName(), QApplication::translate("main", "The Qt libraries have not been built with tray icon support."));
res = -2;
#endif
Settings::save();
QtUtilitiesResources::cleanup();
return res;
}
} catch(const Failure &ex) {
CMD_UTILS_START_CONSOLE;
cout << "Unable to parse arguments. " << ex.what() << "\nSee --help for available commands." << endl;
}
return 0;
}
#include "./settings.h"
#include "../gui/trayicon.h"
#include "../gui/traywidget.h"
#include "resources/config.h"
#include <c++utilities/application/argumentparser.h>
#include <c++utilities/application/commandlineutils.h>
#include <c++utilities/application/failure.h>
#include <qtutilities/resources/qtconfigarguments.h>
#include <qtutilities/resources/resources.h>
#include <qtutilities/resources/importplugin.h>
#include <qtutilities/settingsdialog/qtsettings.h>
#include <QApplication>
#include <QMessageBox>
#include <iostream>
using namespace std;
using namespace ApplicationUtilities;
using namespace QtGui;
int main(int argc, char *argv[])
{
SET_APPLICATION_INFO;
// setup argument parser
ArgumentParser parser;
HelpArgument helpArg(parser);
// Qt configuration arguments
QT_CONFIG_ARGUMENTS qtConfigArgs;
Argument windowedArg("windowed", 'w', "opens the tray menu as a regular window");
windowedArg.setCombinable(true);
qtConfigArgs.qtWidgetsGuiArg().addSubArgument(&windowedArg);
parser.setMainArguments({&qtConfigArgs.qtWidgetsGuiArg(), &helpArg});
try {
parser.parseArgs(argc, argv);
if(qtConfigArgs.qtWidgetsGuiArg().isPresent()) {
SET_QT_APPLICATION_INFO;
QApplication application(argc, argv);
Settings::restore();
Settings::qtSettings().apply();
qtConfigArgs.applySettings(true);
LOAD_QT_TRANSLATIONS;
QtUtilitiesResources::init();
int res;
if(windowedArg.isPresent()) {
TrayWidget trayWidget;
trayWidget.show();
res = application.exec();
} else {
#ifndef QT_NO_SYSTEMTRAYICON
if(QSystemTrayIcon::isSystemTrayAvailable()) {
application.setQuitOnLastWindowClosed(false);
TrayIcon trayIcon;
trayIcon.show();
if(Settings::firstLaunch()) {
trayIcon.trayMenu().widget()->showSettingsDialog();
QMessageBox msgBox;
msgBox.setIcon(QMessageBox::Information);
msgBox.setText(QCoreApplication::translate("main", "You must configure how to connect to Syncthing when using Syncthing Tray the first time."));
msgBox.setInformativeText(QCoreApplication::translate("main", "Note that the settings dialog allows importing URL, credentials and API-key from the local Syncthing configuration."));
msgBox.exec();
}
res = application.exec();
} else {
QMessageBox::critical(nullptr, QApplication::applicationName(), QApplication::translate("main", "The system tray is (currently) not available. You could open the tray menu as a regular window using the -w flag, though."));
res = -1;
}
#else
QMessageBox::critical(nullptr, QApplication::applicationName(), QApplication::translate("main", "The Qt libraries have not been built with tray icon support. You could open the tray menu as a regular window using the -w flag, though."));
res = -2;
#endif
}
Settings::save();
QtUtilitiesResources::cleanup();
return res;
}
} catch(const Failure &ex) {
CMD_UTILS_START_CONSOLE;
cout << "Unable to parse arguments. " << ex.what() << "\nSee --help for available commands." << endl;
}
return 0;
}

View File

@ -1,4 +1,5 @@
#include "./settings.h"
#include <qtutilities/settingsdialog/qtsettings.h>
#include <QString>
@ -10,7 +11,13 @@ using namespace Media;
namespace Settings {
// tray
bool &firstLaunch()
{
static bool v = false;
return v;
}
// connection
QString &syncthingUrl()
{
static QString v;
@ -36,12 +43,14 @@ QByteArray &apiKey()
static QByteArray v;
return v;
}
// notifications
bool &notifyOnDisconnect()
{
static bool v = true;
return v;
}
bool &notifyOnErrors()
bool &notifyOnInternalErrors()
{
static bool v = true;
return v;
@ -56,11 +65,20 @@ bool &showSyncthingNotifications()
static bool v = true;
return v;
}
// appearance
bool &showTraffic()
{
static bool v = true;
return v;
}
QSize &trayMenuSize()
{
static QSize v(350, 300);
return v;
}
// autostart/launcher
bool &launchSynchting()
{
static bool v = false;
@ -108,16 +126,18 @@ void restore()
QSettings settings(QSettings::IniFormat, QSettings::UserScope, QApplication::organizationName(), QApplication::applicationName());
settings.beginGroup(QStringLiteral("tray"));
firstLaunch() = !settings.contains(QStringLiteral("syncthingUrl"));
syncthingUrl() = settings.value(QStringLiteral("syncthingUrl"), QStringLiteral("http://localhost:8080/")).toString();
authEnabled() = settings.value(QStringLiteral("authEnabled"), false).toBool();
userName() = settings.value(QStringLiteral("userName")).toString();
password() = settings.value(QStringLiteral("password")).toString();
apiKey() = settings.value(QStringLiteral("apiKey")).toByteArray();
notifyOnDisconnect() = settings.value(QStringLiteral("notifyOnDisconnect"), true).toBool();
notifyOnErrors() = settings.value(QStringLiteral("notifyOnErrors"), true).toBool();
notifyOnInternalErrors() = settings.value(QStringLiteral("notifyOnErrors"), true).toBool();
notifyOnSyncComplete() = settings.value(QStringLiteral("notifyOnSyncComplete"), true).toBool();
showSyncthingNotifications() = settings.value(QStringLiteral("showSyncthingNotifications"), true).toBool();
showTraffic() = settings.value(QStringLiteral("showTraffic"), true).toBool();
trayMenuSize() = settings.value(QStringLiteral("trayMenuSize"), trayMenuSize()).toSize();
launchSynchting() = settings.value(QStringLiteral("launchSynchting"), false).toBool();
syncthingCommand() = settings.value(QStringLiteral("syncthingCommand"), QStringLiteral("syncthing")).toString();
settings.endGroup();
@ -145,10 +165,11 @@ void save()
settings.setValue(QStringLiteral("password"), password());
settings.setValue(QStringLiteral("apiKey"), apiKey());
settings.setValue(QStringLiteral("notifyOnDisconnect"), notifyOnDisconnect());
settings.setValue(QStringLiteral("notifyOnErrors"), notifyOnErrors());
settings.setValue(QStringLiteral("notifyOnErrors"), notifyOnInternalErrors());
settings.setValue(QStringLiteral("notifyOnSyncComplete"), notifyOnSyncComplete());
settings.setValue(QStringLiteral("showSyncthingNotifications"), showSyncthingNotifications());
settings.setValue(QStringLiteral("showTraffic"), showTraffic());
settings.setValue(QStringLiteral("trayMenuSize"), trayMenuSize());
settings.setValue(QStringLiteral("launchSynchting"), launchSynchting());
settings.setValue(QStringLiteral("syncthingCommand"), syncthingCommand());
settings.endGroup();

View File

@ -7,6 +7,7 @@
QT_FORWARD_DECLARE_CLASS(QByteArray)
QT_FORWARD_DECLARE_CLASS(QString)
QT_FORWARD_DECLARE_CLASS(QSize)
namespace Media {
enum class TagUsage;
@ -19,21 +20,30 @@ class QtSettings;
namespace Settings {
bool &firstLaunch();
// connection
QString &syncthingUrl();
bool &authEnabled();
QString &userName();
QString &password();
QByteArray &apiKey();
// notifications
bool &notifyOnDisconnect();
bool &notifyOnErrors();
bool &notifyOnInternalErrors();
bool &notifyOnSyncComplete();
bool &showSyncthingNotifications();
bool &showTraffic();
// apprearance
bool &showTraffic();
QSize &trayMenuSize();
// autostart/launcher
bool &launchSynchting();
QString &syncthingCommand();
// web view
#if defined(SYNCTHINGTRAY_USE_WEBENGINE) || defined(SYNCTHINGTRAY_USE_WEBKIT)
bool &webViewDisabled();
double &webViewZoomFactor();

70
data/syncthingconfig.cpp Normal file
View File

@ -0,0 +1,70 @@
#include "syncthingconfig.h"
#include <QStandardPaths>
#include <QXmlStreamReader>
#include <QFile>
namespace Data {
/*!
* \struct SyncthingConfig
* \brief The SyncthingConfig struct holds the configuration of Syncthing itself read from config.xml in the Syncthing home directory.
* \remarks Only a few fields are required since most of the Syncthing config can be accessed via SyncthingConnection class.
*/
QString SyncthingConfig::locateConfigFile()
{
QString path = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, QStringLiteral("syncthing/config.xml"));
if(path.isEmpty()) {
path = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, QStringLiteral("Syncthing/config.xml"));
}
return path;
}
QString SyncthingConfig::locateHttpsCertificate()
{
QString path = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, QStringLiteral("syncthing/https-cert.pem"));
if(path.isEmpty()) {
path = QStandardPaths::locate(QStandardPaths::GenericConfigLocation, QStringLiteral("Syncthing/https-cert.pem"));
}
return path;
}
bool SyncthingConfig::restore(const QString &configFilePath)
{
QFile configFile(configFilePath);
if(!configFile.open(QFile::ReadOnly)) {
return false;
}
QXmlStreamReader xmlReader(&configFile);
bool ok = false;
#include <qtutilities/misc/xmlparsermacros.h>
children {
// only version 16 supported, try to parse other versions anyways since the changes might not affect
// the few parts read here
version = attribute("version").toString();
children {
iftag("gui") {
ok = true;
guiEnabled = attributeFlag("enabled");
guiEnforcesSecureConnection = attributeFlag("tls");
children {
iftag("address") {
guiAddress = text;
} eliftag("user") {
guiUser = text;
} eliftag("password") {
guiPasswordHash = text;
} eliftag("apikey") {
guiApiKey = text;
} else_skip
}
} else_skip
}
}
#include <qtutilities/misc/undefxmlparsermacros.h>
return ok;
}
} // namespace Data

26
data/syncthingconfig.h Normal file
View File

@ -0,0 +1,26 @@
#ifndef DATA_SYNCTHINGCONFIG_H
#define DATA_SYNCTHINGCONFIG_H
#include <QString>
namespace Data {
struct SyncthingConfig
{
QString version;
bool guiEnabled = false;
bool guiEnforcesSecureConnection = false;
QString guiAddress;
QString guiUser;
QString guiPasswordHash;
QString guiApiKey;
static QString locateConfigFile();
static QString locateHttpsCertificate();
bool restore(const QString &configFilePath);
};
} // namespace Data
#endif // DATA_SYNCTHINGCONFIG_H

View File

@ -1,4 +1,5 @@
#include "./syncthingconnection.h"
#include "./syncthingconfig.h"
#include <c++utilities/conversion/conversionexception.h>
@ -13,6 +14,7 @@
#include <QAuthenticator>
#include <QStringBuilder>
#include <QTimer>
#include <QHostAddress>
#include <utility>
@ -56,6 +58,13 @@ bool SyncthingDir::assignStatus(const QString &statusStr)
newStatus = DirStatus::Unknown;
}
if(newStatus != status) {
switch(status) {
case DirStatus::Scanning:
lastScanTime = DateTime::now();
break;
default:
;
}
status = newStatus;
return true;
}
@ -67,6 +76,8 @@ bool SyncthingDir::assignStatus(const QString &statusStr)
* \brief The SyncthingConnection class allows Qt applications to access Syncthing.
*/
QList<QSslError> SyncthingConnection::m_expectedCertificateErrors;
/*!
* \brief Constructs a new instance ready to connect. To establish the connection, call connect().
*/
@ -84,10 +95,12 @@ SyncthingConnection::SyncthingConnection(const QString &syncthingUrl, const QByt
m_totalOutgoingRate(0),
m_configReply(nullptr),
m_statusReply(nullptr),
m_connectionsReply(nullptr),
m_eventsReply(nullptr),
m_unreadNotifications(false),
m_hasConfig(false),
m_hasStatus(false)
m_hasStatus(false),
m_lastFileDeleted(false)
{}
/*!
@ -226,17 +239,21 @@ QNetworkRequest SyncthingConnection::prepareRequest(const QString &path, const Q
/*!
* \brief Requests asynchronously data using the rest API.
*/
inline QNetworkReply *SyncthingConnection::requestData(const QString &path, const QUrlQuery &query, bool rest)
QNetworkReply *SyncthingConnection::requestData(const QString &path, const QUrlQuery &query, bool rest)
{
return networkAccessManager().get(prepareRequest(path, query, rest));
auto *reply = networkAccessManager().get(prepareRequest(path, query, rest));
reply->ignoreSslErrors(m_expectedCertificateErrors);
return reply;
}
/*!
* \brief Posts asynchronously data using the rest API.
*/
inline QNetworkReply *SyncthingConnection::postData(const QString &path, const QUrlQuery &query, const QByteArray &data)
QNetworkReply *SyncthingConnection::postData(const QString &path, const QUrlQuery &query, const QByteArray &data)
{
return networkAccessManager().post(prepareRequest(path, query), data);
auto *reply = networkAccessManager().post(prepareRequest(path, query), data);
reply->ignoreSslErrors(m_expectedCertificateErrors);
return reply;
}
SyncthingDir *SyncthingConnection::findDirInfo(const QString &dir, int &row)
@ -270,6 +287,8 @@ void SyncthingConnection::continueConnecting()
{
if(m_keepPolling && m_hasConfig && m_hasStatus) {
requestConnections();
requestDirStatistics();
requestDeviceStatistics();
// since config and status could be read successfully, let's poll for events
m_lastEventId = 0;
requestEvents();
@ -318,13 +337,29 @@ void SyncthingConnection::requestStatus()
/*!
* \brief Requests current connections asynchronously.
*
* The signal devStatusChanged() is emitted for each device where the connection status has changed updated; error() is emitted in the error case.
* The signal devStatusChanged() is emitted for each device where the connection status has changed; error() is emitted in the error case.
*/
void SyncthingConnection::requestConnections()
{
QObject::connect(m_connectionsReply = requestData(QStringLiteral("system/connections"), QUrlQuery()), &QNetworkReply::finished, this, &SyncthingConnection::readConnections);
}
/*!
* \brief Requests directory statistics asynchronously.
*/
void SyncthingConnection::requestDirStatistics()
{
QObject::connect(requestData(QStringLiteral("stats/folder"), QUrlQuery()), &QNetworkReply::finished, this, &SyncthingConnection::readDirStatistics);
}
/*!
* \brief Requests device statistics asynchronously.
*/
void SyncthingConnection::requestDeviceStatistics()
{
QObject::connect(requestData(QStringLiteral("stats/device"), QUrlQuery()), &QNetworkReply::finished, this, &SyncthingConnection::readDeviceStatistics);
}
/*!
* \brief Requests the Syncthing events (since the last successful call) asynchronously.
*
@ -399,6 +434,37 @@ void SyncthingConnection::requestLog(std::function<void (const std::vector<Synct
});
}
/*!
* \brief Locates and loads the (self-signed) certificate used by the Syncthing GUI.
*/
void SyncthingConnection::loadSelfSignedCertificate()
{
// only possible if the Syncthing instance is running on the local machine
const QString host(QUrl(syncthingUrl()).host());
if(host.compare(QLatin1String("localhost"), Qt::CaseInsensitive) != 0 && !QHostAddress().isLoopback()) {
return;
}
// ensure current exceptions for self-signed certificates are cleared
m_expectedCertificateErrors.clear();
// find cert
const QString certPath = !m_configDir.isEmpty() ? (m_configDir + QStringLiteral("/https-cert.pem")) : SyncthingConfig::locateHttpsCertificate();
if(certPath.isEmpty()) {
emit error(tr("Unable to locate certificate used by Syncthing GUI."));
return;
}
// add exception
const QList<QSslCertificate> cert = QSslCertificate::fromPath(certPath);
if(cert.isEmpty()) {
emit error(tr("Unable to load certificate used by Syncthing GUI."));
return;
}
m_expectedCertificateErrors << QSslError(QSslError::UnableToGetLocalIssuerCertificate, cert.at(0));
m_expectedCertificateErrors << QSslError(QSslError::UnableToVerifyFirstCertificate, cert.at(0));
m_expectedCertificateErrors << QSslError(QSslError::SelfSignedCertificate, cert.at(0));
m_expectedCertificateErrors << QSslError(QSslError::HostNameMismatch, cert.at(0));
}
/*!
* \brief Reads results of requestConfig().
*/
@ -556,7 +622,7 @@ void SyncthingConnection::readConnections()
double transferTime;
if(!m_lastConnectionsUpdate.isNull() && ((transferTime = (DateTime::gmtNow() - m_lastConnectionsUpdate).totalSeconds()) != 0.0)) {
m_totalIncomingRate = (totalIncomingTraffic - m_totalIncomingTraffic) * 0.008 / transferTime,
m_totalOutgoingRate = (totalOutgoingTraffic - m_totalOutgoingTraffic) * 0.008 / transferTime;
m_totalOutgoingRate = (totalOutgoingTraffic - m_totalOutgoingTraffic) * 0.008 / transferTime;
} else {
m_totalIncomingRate = m_totalOutgoingRate = 0.0;
}
@ -611,6 +677,108 @@ void SyncthingConnection::readConnections()
}
}
/*!
* \brief Reads results of requestDirStatistics().
* \remarks TODO
*/
void SyncthingConnection::readDirStatistics()
{
auto *reply = static_cast<QNetworkReply *>(sender());
reply->deleteLater();
switch(reply->error()) {
case QNetworkReply::NoError: {
QJsonParseError jsonError;
const QJsonDocument replyDoc = QJsonDocument::fromJson(reply->readAll(), &jsonError);
if(jsonError.error == QJsonParseError::NoError) {
const QJsonObject replyObj(replyDoc.object());
int index = 0;
for(SyncthingDir &dirInfo : m_dirs) {
const QJsonObject dirObj(replyObj.value(dirInfo.id).toObject());
if(!dirObj.isEmpty()) {
bool mod = false;
try {
dirInfo.lastScanTime = DateTime::fromIsoStringLocal(dirObj.value(QStringLiteral("lastScan")).toString().toUtf8().data());
mod = true;
} catch(const ConversionException &) {
dirInfo.lastScanTime = DateTime();
}
const QJsonObject lastFileObj(dirObj.value(QStringLiteral("lastFile")).toObject());
if(!lastFileObj.isEmpty()) {
dirInfo.lastFileName = lastFileObj.value(QStringLiteral("filename")).toString();
mod = true;
if(!dirInfo.lastFileName.isEmpty()) {
dirInfo.lastFileDeleted = lastFileObj.value(QStringLiteral("deleted")).toBool(false);
try {
dirInfo.lastFileTime = DateTime::fromIsoStringLocal(lastFileObj.value(QStringLiteral("at")).toString().toUtf8().data());
if(dirInfo.lastFileTime > m_lastFileTime) {
m_lastFileTime = dirInfo.lastFileTime,
m_lastFileName = dirInfo.lastFileName,
m_lastFileDeleted = dirInfo.lastFileDeleted;
}
} catch(const ConversionException &) {
dirInfo.lastFileTime = DateTime();
}
}
}
if(mod) {
emit dirStatusChanged(dirInfo, index);
}
}
++index;
}
} else {
emit error(tr("Unable to parse directory statistics: ") + jsonError.errorString());
}
} case QNetworkReply::OperationCanceledError:
return; // intended, not an error
default:
emit error(tr("Unable to request directory statistics: ") + reply->errorString());
}
}
/*!
* \brief Reads results of requestDeviceStatistics().
* \remarks TODO
*/
void SyncthingConnection::readDeviceStatistics()
{
auto *reply = static_cast<QNetworkReply *>(sender());
reply->deleteLater();
switch(reply->error()) {
case QNetworkReply::NoError: {
QJsonParseError jsonError;
const QJsonDocument replyDoc = QJsonDocument::fromJson(reply->readAll(), &jsonError);
if(jsonError.error == QJsonParseError::NoError) {
const QJsonObject replyObj(replyDoc.object());
int index = 0;
for(SyncthingDev &devInfo : m_devs) {
const QJsonObject devObj(replyObj.value(devInfo.id).toObject());
if(!devObj.isEmpty()) {
try {
devInfo.lastSeen = DateTime::fromIsoStringLocal(devObj.value(QStringLiteral("lastSeen")).toString().toUtf8().data());
emit devStatusChanged(devInfo, index);
} catch(const ConversionException &) {
devInfo.lastSeen = DateTime();
}
}
++index;
}
// since there seems no event for this data, just request every minute, FIXME: make interval configurable
if(m_keepPolling) {
QTimer::singleShot(60000, Qt::VeryCoarseTimer, this, SLOT(requestConnections()));
}
} else {
emit error(tr("Unable to parse device statistics: ") + jsonError.errorString());
}
} case QNetworkReply::OperationCanceledError:
return; // intended, not an error
default:
emit error(tr("Unable to request device statistics: ") + reply->errorString());
}
}
/*!
* \brief Reads results of requestEvents().
*/
@ -635,7 +803,7 @@ void SyncthingConnection::readEvents()
m_lastEventId = event.value(QStringLiteral("id")).toInt(m_lastEventId);
DateTime eventTime;
try {
eventTime = DateTime::fromIsoString(event.value(QStringLiteral("time")).toString().toLocal8Bit().data()).first;
eventTime = DateTime::fromIsoStringGmt(event.value(QStringLiteral("time")).toString().toLocal8Bit().data());
} catch(const ConversionException &) {
// ignore conversion error
}
@ -651,6 +819,10 @@ void SyncthingConnection::readEvents()
readDirEvent(eventType, eventData);
} else if(eventType.startsWith(QLatin1String("Device"))) {
readDeviceEvent(eventTime, eventType, eventData);
} else if(eventType == QLatin1String("ItemStarted")) {
readItemStarted(eventTime, eventData);
} else if(eventType == QLatin1String("ItemFinished")) {
readItemFinished(eventTime, eventData);
}
}
} else {
@ -727,8 +899,13 @@ void SyncthingConnection::readStatusChangedEvent(const QJsonObject &eventData)
* \remarks TODO
*/
void SyncthingConnection::readDownloadProgressEvent(const QJsonObject &eventData)
{}
{
VAR_UNUSED(eventData)
}
/*!
* \brief Reads results of requestEvents().
*/
void SyncthingConnection::readDirEvent(const QString &eventType, const QJsonObject &eventData)
{
const QString dir(eventData.value(QStringLiteral("folder")).toString());
@ -743,6 +920,7 @@ void SyncthingConnection::readDirEvent(const QString &eventType, const QJsonObje
const QJsonObject error(errorVal.toObject());
if(!error.isEmpty()) {
dirInfo->errors.emplace_back(error.value(QStringLiteral("error")).toString(), error.value(QStringLiteral("path")).toString());
dirInfo->status = DirStatus::OutOfSync;
emit newNotification(dirInfo->errors.back().message);
}
}
@ -774,9 +952,9 @@ void SyncthingConnection::readDirEvent(const QString &eventType, const QJsonObje
}
} else if(eventType == QLatin1String("FolderScanProgress")) {
// FIXME: for some reason current is always 0
int current = eventData.value(QStringLiteral("current")).toInt(0);
int total = eventData.value(QStringLiteral("total")).toInt(0);
int rate = eventData.value(QStringLiteral("rate")).toInt(0);
int current = eventData.value(QStringLiteral("current")).toInt(0),
total = eventData.value(QStringLiteral("total")).toInt(0),
rate = eventData.value(QStringLiteral("rate")).toInt(0);
if(current > 0 && total > 0) {
dirInfo->progressPercentage = current * 100 / total;
dirInfo->progressRate = rate;
@ -830,6 +1008,46 @@ void SyncthingConnection::readDeviceEvent(DateTime eventTime, const QString &eve
}
}
/*!
* \brief Reads results of requestEvents().
*/
void SyncthingConnection::readItemStarted(DateTime eventTime, const QJsonObject &eventData)
{
VAR_UNUSED(eventTime)
VAR_UNUSED(eventData)
}
/*!
* \brief Reads results of requestEvents().
*/
void SyncthingConnection::readItemFinished(DateTime eventTime, const QJsonObject &eventData)
{
const QString dir(eventData.value(QStringLiteral("folder")).toString());
if(!dir.isEmpty()) {
int index;
if(SyncthingDir *dirInfo = findDirInfo(dir, index)) {
const QString error(eventData.value(QStringLiteral("error")).toString()),
item(eventData.value(QStringLiteral("item")).toString());
if(error.isEmpty()) {
if(dirInfo->lastFileTime.isNull() || eventTime < dirInfo->lastFileTime) {
dirInfo->lastFileTime = eventTime,
dirInfo->lastFileName = item,
dirInfo->lastFileDeleted = (eventData.value(QStringLiteral("action")) != QLatin1String("delete"));
if(eventTime > m_lastFileTime) {
m_lastFileTime = dirInfo->lastFileTime,
m_lastFileName = dirInfo->lastFileName,
m_lastFileDeleted = dirInfo->lastFileDeleted;
}
emit dirStatusChanged(*dirInfo, index);
}
} else {
dirInfo->errors.emplace_back(error, item);
emit newNotification(error);
}
}
}
}
/*!
* \brief Reads results of rescan().
*/
@ -841,7 +1059,7 @@ void SyncthingConnection::readRescan()
case QNetworkReply::NoError:
break;
default:
emit error(tr("Unable to request rescan: ") + reply->errorString());
emit error(tr("Unable to request rescan: ") + reply->errorString());
}
}
@ -856,7 +1074,7 @@ void SyncthingConnection::readPauseResume()
case QNetworkReply::NoError:
break;
default:
emit error(tr("Unable to request pause/resume: ") + reply->errorString());
emit error(tr("Unable to request pause/resume: ") + reply->errorString());
}
}

View File

@ -4,6 +4,8 @@
#include <c++utilities/chrono/datetime.h>
#include <QObject>
#include <QList>
#include <QSslError>
#include <functional>
#include <vector>
@ -59,13 +61,17 @@ struct SyncthingDir
bool autoNormalize = false;
int rescanInterval = 0;
int minDiskFreePercentage = 0;
DirStatus status = DirStatus::Unknown;
DirStatus status = DirStatus::Idle;
int progressPercentage = 0;
int progressRate = 0;
std::vector<DirErrors> errors;
int globalBytes = 0, globalDeleted = 0, globalFiles = 0;
int localBytes = 0, localDeleted = 0, localFiles = 0;
int neededByted = 0, neededFiles = 0;
ChronoUtilities::DateTime lastScanTime;
ChronoUtilities::DateTime lastFileTime;
QString lastFileName;
bool lastFileDeleted = false;
bool assignStatus(const QString &statusStr);
};
@ -98,6 +104,7 @@ struct SyncthingDev
QString connectionAddress;
QString connectionType;
QString clientVersion;
ChronoUtilities::DateTime lastSeen;
};
struct SyncthingLogEntry
@ -141,8 +148,10 @@ public:
const std::vector<SyncthingDev> &devInfo() const;
void requestQrCode(const QString &text, std::function<void (const QPixmap &)> callback);
void requestLog(std::function<void (const std::vector<SyncthingLogEntry> &)> callback);
static const QList<QSslError> &expectedCertificateErrors();
public Q_SLOTS:
void loadSelfSignedCertificate();
void connect();
void disconnect();
void reconnect();
@ -226,6 +235,8 @@ private Q_SLOTS:
void requestConfig();
void requestStatus();
void requestConnections();
void requestDirStatistics();
void requestDeviceStatistics();
void requestEvents();
void abortAllRequests();
@ -234,12 +245,16 @@ private Q_SLOTS:
void readDevs(const QJsonArray &devs);
void readStatus();
void readConnections();
void readDirStatistics();
void readDeviceStatistics();
void readEvents();
void readStartingEvent(const QJsonObject &eventData);
void readStatusChangedEvent(const QJsonObject &eventData);
void readDownloadProgressEvent(const QJsonObject &eventData);
void readDirEvent(const QString &eventType, const QJsonObject &eventData);
void readDeviceEvent(ChronoUtilities::DateTime eventTime, const QString &eventType, const QJsonObject &eventData);
void readItemStarted(ChronoUtilities::DateTime eventTime, const QJsonObject &eventData);
void readItemFinished(ChronoUtilities::DateTime eventTime, const QJsonObject &eventData);
void readRescan();
void readPauseResume();
@ -277,6 +292,10 @@ private:
std::vector<SyncthingDir> m_dirs;
std::vector<SyncthingDev> m_devs;
ChronoUtilities::DateTime m_lastConnectionsUpdate;
ChronoUtilities::DateTime m_lastFileTime;
QString m_lastFileName;
bool m_lastFileDeleted;
static QList<QSslError> m_expectedCertificateErrors;
};
/*!
@ -419,6 +438,15 @@ inline const std::vector<SyncthingDev> &SyncthingConnection::devInfo() const
return m_devs;
}
/*!
* \brief Returns a list of all expected certificate errors.
* \remarks This list is shared by all instances and updated via loadSelfSignedCertificate().
*/
inline const QList<QSslError> &SyncthingConnection::expectedCertificateErrors()
{
return m_expectedCertificateErrors;
}
}
#endif // SYNCTHINGCONNECTION_H

View File

@ -1,5 +1,8 @@
#include "./syncthingdevicemodel.h"
#include "./syncthingconnection.h"
#include "./utils.h"
using namespace ChronoUtilities;
namespace Data {
@ -83,9 +86,10 @@ QVariant SyncthingDeviceModel::data(const QModelIndex &index, int role) const
switch(index.row()) {
case 0: return tr("ID");
case 1: return tr("Addresses");
case 2: return tr("Compression");
case 3: return tr("Certificate");
case 4: return tr("Introducer");
case 2: return tr("Last seen");
case 3: return tr("Compression");
case 4: return tr("Certificate");
case 5: return tr("Introducer");
}
break;
case 1: // attribute values
@ -93,13 +97,44 @@ QVariant SyncthingDeviceModel::data(const QModelIndex &index, int role) const
switch(index.row()) {
case 0: return dev.id;
case 1: return dev.addresses.join(QStringLiteral(", "));
case 2: return dev.compression;
case 3: return dev.certName.isEmpty() ? tr("none") : dev.certName;
case 4: return dev.introducer ? tr("yes") : tr("no");
case 2: return dev.lastSeen.isNull() ? tr("unknown or own device") : QString::fromLatin1(dev.lastSeen.toString(DateTimeOutputFormat::DateAndTime, true).data());
case 3: return dev.compression;
case 4: return dev.certName.isEmpty() ? tr("none") : dev.certName;
case 5: return dev.introducer ? tr("yes") : tr("no");
}
break;
}
break;
case Qt::ForegroundRole:
switch(index.column()) {
case 1:
const SyncthingDev &dev = m_devs[index.parent().row()];
switch(index.row()) {
case 2:
if(dev.lastSeen.isNull()) {
return QColor(Qt::gray);
}
break;
case 4:
if(dev.certName.isEmpty()) {
return QColor(Qt::gray);
}
break;
}
}
break;
case Qt::ToolTipRole:
switch(index.column()) {
case 1:
switch(index.row()) {
case 2:
const SyncthingDev &dev = m_devs[index.parent().row()];
if(!dev.lastSeen.isNull()) {
return agoString(dev.lastSeen);
}
break;
}
}
default:
;
}
@ -196,7 +231,7 @@ int SyncthingDeviceModel::rowCount(const QModelIndex &parent) const
if(!parent.isValid()) {
return m_devs.size();
} else if(!parent.parent().isValid()) {
return 5;
return 6;
} else {
return 0;
}

View File

@ -1,5 +1,8 @@
#include "./syncthingdirectorymodel.h"
#include "./syncthingconnection.h"
#include "./utils.h"
using namespace ChronoUtilities;
namespace Data {
@ -86,6 +89,8 @@ QVariant SyncthingDirectoryModel::data(const QModelIndex &index, int role) const
case 2: return tr("Devices");
case 3: return tr("Read-only");
case 4: return tr("Rescan interval");
case 5: return tr("Last scan");
case 6: return tr("Last file");
}
break;
case 1: // attribute values
@ -96,10 +101,53 @@ QVariant SyncthingDirectoryModel::data(const QModelIndex &index, int role) const
case 2: return dir.devices.join(QStringLiteral(", "));
case 3: return dir.readOnly ? tr("yes") : tr("no");
case 4: return QStringLiteral("%1 s").arg(dir.rescanInterval);
case 5: return dir.lastScanTime.isNull() ? tr("unknown") : QString::fromLatin1(dir.lastScanTime.toString(DateTimeOutputFormat::DateAndTime, true).data());
case 6: return dir.lastFileName.isEmpty() ? tr("unknown") : dir.lastFileName;
}
break;
}
break;
case Qt::ForegroundRole:
switch(index.column()) {
case 1:
const SyncthingDir &dir = m_dirs[index.parent().row()];
switch(index.row()) {
case 5:
if(dir.lastScanTime.isNull()) {
return QColor(Qt::gray);
}
break;
case 6:
if(dir.lastFileName.isEmpty()) {
return QColor(Qt::gray);
} else if(dir.lastFileDeleted) {
return QColor(Qt::red);
}
break;
}
}
break;
case Qt::ToolTipRole:
switch(index.column()) {
case 1:
const SyncthingDir &dir = m_dirs[index.parent().row()];
switch(index.row()) {
case 5:
if(!dir.lastScanTime.isNull()) {
return agoString(dir.lastScanTime);
}
break;
case 6:
if(!dir.lastFileTime.isNull()) {
if(dir.lastFileDeleted) {
return tr("Deleted at %1").arg(QString::fromLatin1(dir.lastFileTime.toString(DateTimeOutputFormat::DateAndTime, true).data()));
} else {
return tr("Updated at %1").arg(QString::fromLatin1(dir.lastFileTime.toString(DateTimeOutputFormat::DateAndTime, true).data()));
}
}
break;
}
}
default:
;
}
@ -177,7 +225,7 @@ int SyncthingDirectoryModel::rowCount(const QModelIndex &parent) const
if(!parent.isValid()) {
return m_dirs.size();
} else if(!parent.parent().isValid()) {
return 5;
return 7;
} else {
return 0;
}

22
data/utils.cpp Normal file
View File

@ -0,0 +1,22 @@
#include "utils.h"
#include <c++utilities/chrono/datetime.h>
#include <QString>
#include <QCoreApplication>
using namespace ChronoUtilities;
namespace Data {
QString agoString(DateTime dateTime)
{
const TimeSpan delta(DateTime::now() - dateTime);
if(!delta.isNegative() && static_cast<uint64>(delta.totalTicks()) > (TimeSpan::ticksPerMinute / 4uL)) {
return QCoreApplication::translate("Data::Utils", "%1 ago").arg(QString::fromLatin1(delta.toString(TimeSpanOutputFormat::WithMeasures, true).data()));
} else {
return QCoreApplication::translate("Data::Utils", "right now");
}
}
}

18
data/utils.h Normal file
View File

@ -0,0 +1,18 @@
#ifndef DATA_UTILS_H
#define DATA_UTILS_H
#include <QtGlobal>
QT_FORWARD_DECLARE_CLASS(QString)
namespace ChronoUtilities {
class DateTime;
}
namespace Data {
QString agoString(ChronoUtilities::DateTime dateTime);
}
#endif // DATA_UTILS_H

View File

@ -2,29 +2,85 @@
<ui version="4.0">
<class>QtGui::AppearanceOptionPage</class>
<widget class="QWidget" name="QtGui::AppearanceOptionPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>283</width>
<height>74</height>
</rect>
</property>
<property name="windowTitle">
<string>Appearance</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QCheckBox" name="showTrafficCheckBox">
<property name="windowIcon">
<iconset theme="preferences-desktop"/>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="sizeLabel">
<property name="text">
<string>Show traffic</string>
<string>Menu size</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
<item row="2" column="0">
<widget class="QLabel" name="guiElementsLabel">
<property name="text">
<string>Optional GUI elements</string>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="showTrafficCheckBox">
<property name="text">
<string>Traffic statistics</string>
</property>
</spacer>
</widget>
</item>
<item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QSpinBox" name="widthSpinBox">
<property name="minimum">
<number>150</number>
</property>
<property name="maximum">
<number>1000</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="sizeTimesLabel">
<property name="text">
<string>x</string>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="heightSpinBox">
<property name="minimum">
<number>150</number>
</property>
<property name="maximum">
<number>1000</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="sizePxLabel">
<property name="text">
<string>px</string>
</property>
<property name="textFormat">
<enum>Qt::PlainText</enum>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>

View File

@ -2,9 +2,20 @@
<ui version="4.0">
<class>QtGui::AutostartOptionPage</class>
<widget class="QWidget" name="QtGui::AutostartOptionPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>575</width>
<height>52</height>
</rect>
</property>
<property name="windowTitle">
<string>Autostart</string>
</property>
<property name="windowIcon">
<iconset theme="system-run"/>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">

View File

@ -2,17 +2,12 @@
<ui version="4.0">
<class>QtGui::ConnectionOptionPage</class>
<widget class="QWidget" name="QtGui::ConnectionOptionPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>433</width>
<height>267</height>
</rect>
</property>
<property name="windowTitle">
<string>Connection</string>
</property>
<property name="windowIcon">
<iconset theme="network-connect"/>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="urlLabel">
@ -71,28 +66,21 @@
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="10" column="1">
<item row="11" column="1">
<widget class="QLabel" name="statusLabel">
<property name="text">
<string>disconnected</string>
</property>
</widget>
</item>
<item row="10" column="0">
<item row="11" column="0">
<widget class="QLabel" name="statusTextLabel">
<property name="text">
<string>Status</string>
</property>
</widget>
</item>
<item row="11" column="1">
<item row="12" column="1">
<widget class="QPushButton" name="connectPushButton">
<property name="text">
<string>Apply connection settings and try to reconnect</string>
@ -113,6 +101,33 @@
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QPushButton" name="insertFromConfigFilePushButton">
<property name="styleSheet">
<string notr="true">font-weight: bold;</string>
</property>
<property name="text">
<string>Insert values from local Syncthing configuration</string>
</property>
<property name="icon">
<iconset theme="edit-paste">
<normaloff>.</normaloff>.</iconset>
</property>
</widget>
</item>
<item row="10" column="1">
<widget class="Line" name="line">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>

View File

@ -2,43 +2,46 @@
<ui version="4.0">
<class>QtGui::NotificationsOptionPage</class>
<widget class="QWidget" name="QtGui::NotificationsOptionPage">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>175</width>
<height>156</height>
</rect>
</property>
<property name="windowTitle">
<string>Notifications</string>
</property>
<property name="windowIcon">
<iconset theme="preferences-desktop-notification">
<normaloff>.</normaloff>.</iconset>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="notifyOnLabel">
<property name="text">
<string>Notify on</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="notifyOnDisconnectCheckBox">
<property name="text">
<string>Notify on disconnect</string>
<string>disconnect</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="notifyOnErrorsCheckBox">
<property name="text">
<string>Notify on internal errors</string>
<string>internal errors</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="showSyncthingNotificationsCheckBox">
<property name="text">
<string>Notify on Syncthing errors</string>
<string>errors/notifications from Syncthing</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="notifyOnSyncCompleteCheckBox">
<property name="text">
<string>Notify on sync complete</string>
<string>sync complete</string>
</property>
</widget>
</item>

View File

@ -2,6 +2,7 @@
#include "../application/settings.h"
#include "../data/syncthingconnection.h"
#include "../data/syncthingconfig.h"
#include "ui_connectionoptionpage.h"
#include "ui_notificationsoptionpage.h"
@ -9,6 +10,8 @@
#include "ui_autostartoptionpage.h"
#include "ui_webviewoptionpage.h"
#include "resources/config.h"
#include <tagparser/mediafileinfo.h>
#include <tagparser/backuphelper.h>
@ -16,6 +19,10 @@
#include <qtutilities/settingsdialog/optioncategorymodel.h>
#include <qtutilities/settingsdialog/qtsettings.h>
#include <QFileDialog>
#include <QMessageBox>
#include <QHostAddress>
#include <functional>
using namespace std;
@ -40,12 +47,49 @@ QWidget *ConnectionOptionPage::setupWidget()
updateConnectionStatus();
QObject::connect(m_connection, &SyncthingConnection::statusChanged, bind(&ConnectionOptionPage::updateConnectionStatus, this));
QObject::connect(ui()->connectPushButton, &QPushButton::clicked, bind(&ConnectionOptionPage::applyAndReconnect, this));
QObject::connect(ui()->insertFromConfigFilePushButton, &QPushButton::clicked, bind(&ConnectionOptionPage::insertFromConfigFile, this));
return w;
}
void ConnectionOptionPage::insertFromConfigFile()
{
if(hasBeenShown()) {
QString configFile = SyncthingConfig::locateConfigFile();
if(configFile.isEmpty()) {
// allow user to select config file manually if it could not be located
configFile = QFileDialog::getOpenFileName(widget(), QCoreApplication::translate("QtGui::ConnectionOptionPage", "Select Syncthing config file") + QStringLiteral(" - " APP_NAME));
}
if(configFile.isEmpty()) {
return;
}
SyncthingConfig config;
if(!config.restore(configFile)) {
QMessageBox::critical(widget(), widget()->windowTitle() + QStringLiteral(" - " APP_NAME), QCoreApplication::translate("QtGui::ConnectionOptionPage", "Unable to parse the Syncthing config file."));
return;
}
if(!config.guiAddress.isEmpty()) {
ui()->urlLineEdit->selectAll();
ui()->urlLineEdit->insert(((config.guiEnforcesSecureConnection || !QHostAddress(config.guiAddress.mid(0, config.guiAddress.indexOf(QChar(':')))).isLoopback()) ? QStringLiteral("https://") : QStringLiteral("http://")) + config.guiAddress);
}
if(!config.guiUser.isEmpty() || !config.guiPasswordHash.isEmpty()) {
ui()->authCheckBox->setChecked(true);
ui()->userNameLineEdit->selectAll();
ui()->userNameLineEdit->insert(config.guiUser);
} else {
ui()->authCheckBox->setChecked(false);
}
if(!config.guiApiKey.isEmpty()) {
ui()->apiKeyLineEdit->selectAll();
ui()->apiKeyLineEdit->insert(config.guiApiKey);
}
}
}
void ConnectionOptionPage::updateConnectionStatus()
{
ui()->statusLabel->setText(m_connection->statusText());
if(hasBeenShown()) {
ui()->statusLabel->setText(m_connection->statusText());
}
}
bool ConnectionOptionPage::apply()
@ -97,7 +141,7 @@ bool NotificationsOptionPage::apply()
{
if(hasBeenShown()) {
notifyOnDisconnect() = ui()->notifyOnDisconnectCheckBox->isChecked();
notifyOnErrors() = ui()->notifyOnErrorsCheckBox->isChecked();
notifyOnInternalErrors() = ui()->notifyOnErrorsCheckBox->isChecked();
notifyOnSyncComplete() = ui()->notifyOnSyncCompleteCheckBox->isChecked();
showSyncthingNotifications() = ui()->showSyncthingNotificationsCheckBox->isChecked();
}
@ -108,7 +152,7 @@ void NotificationsOptionPage::reset()
{
if(hasBeenShown()) {
ui()->notifyOnDisconnectCheckBox->setChecked(notifyOnDisconnect());
ui()->notifyOnErrorsCheckBox->setChecked(notifyOnErrors());
ui()->notifyOnErrorsCheckBox->setChecked(notifyOnInternalErrors());
ui()->notifyOnSyncCompleteCheckBox->setChecked(notifyOnSyncComplete());
ui()->showSyncthingNotificationsCheckBox->setChecked(showSyncthingNotifications());
}
@ -125,6 +169,8 @@ AppearanceOptionPage::~AppearanceOptionPage()
bool AppearanceOptionPage::apply()
{
if(hasBeenShown()) {
trayMenuSize().setWidth(ui()->widthSpinBox->value());
trayMenuSize().setHeight(ui()->heightSpinBox->value());
showTraffic() = ui()->showTrafficCheckBox->isChecked();
}
return true;
@ -133,6 +179,8 @@ bool AppearanceOptionPage::apply()
void AppearanceOptionPage::reset()
{
if(hasBeenShown()) {
ui()->widthSpinBox->setValue(trayMenuSize().width());
ui()->heightSpinBox->setValue(trayMenuSize().height());
ui()->showTrafficCheckBox->setChecked(showTraffic());
}
}

View File

@ -23,6 +23,7 @@ public:
ConnectionOptionPage(Data::SyncthingConnection *connection, QWidget *parentWidget = nullptr);
private:
DECLARE_SETUP_WIDGETS
void insertFromConfigFile();
void updateConnectionStatus();
void applyAndReconnect();
Data::SyncthingConnection *m_connection;

View File

@ -43,7 +43,7 @@ TrayIcon::TrayIcon(QObject *parent) :
// connect signals and slots
SyncthingConnection *connection = &(m_trayMenu.widget()->connection());
connect(this, &TrayIcon::activated, this, &TrayIcon::handleActivated);
connect(connection, &SyncthingConnection::error, this, &TrayIcon::showSyncthingError);
connect(connection, &SyncthingConnection::error, this, &TrayIcon::showInternalError);
connect(connection, &SyncthingConnection::newNotification, this, &TrayIcon::showSyncthingNotification);
connect(connection, &SyncthingConnection::statusChanged, this, &TrayIcon::updateStatusIconAndText);
}
@ -72,17 +72,17 @@ void TrayIcon::handleActivated(QSystemTrayIcon::ActivationReason reason)
}
}
void TrayIcon::showSyncthingError(const QString &errorMsg)
void TrayIcon::showInternalError(const QString &errorMsg)
{
if(Settings::notifyOnErrors()) {
showMessage(tr("Syncthing error"), errorMsg, QSystemTrayIcon::Critical);
if(Settings::notifyOnInternalErrors()) {
showMessage(tr("Error"), errorMsg, QSystemTrayIcon::Critical);
}
}
void TrayIcon::showSyncthingNotification(const QString &message)
{
if(Settings::showSyncthingNotifications()) {
showMessage(tr("Syncthing notification"), message, QSystemTrayIcon::Information);
showMessage(tr("Syncthing notification"), message, QSystemTrayIcon::Warning);
}
}

View File

@ -22,10 +22,11 @@ class TrayIcon : public QSystemTrayIcon
public:
TrayIcon(QObject *parent = nullptr);
TrayMenu &trayMenu();
private slots:
void handleActivated(QSystemTrayIcon::ActivationReason reason);
void showSyncthingError(const QString &errorMsg);
void showInternalError(const QString &errorMsg);
void showSyncthingNotification(const QString &message);
void updateStatusIconAndText(Data::SyncthingStatus status);
@ -43,6 +44,11 @@ private:
Data::SyncthingStatus m_status;
};
inline TrayMenu &TrayIcon::trayMenu()
{
return m_trayMenu;
}
}
#endif // TRAY_ICON_H

View File

@ -1,6 +1,8 @@
#include "./traymenu.h"
#include "./traywidget.h"
#include "../application/settings.h"
#include <QHBoxLayout>
namespace QtGui {
@ -17,7 +19,7 @@ TrayMenu::TrayMenu(QWidget *parent) :
QSize TrayMenu::sizeHint() const
{
return QSize(350, 300);
return Settings::trayMenuSize();
}
}

View File

@ -95,6 +95,7 @@ TrayWidget::TrayWidget(TrayMenu *parent) :
connect(m_ui->settingsPushButton, &QPushButton::clicked, this, &TrayWidget::showSettingsDialog);
connect(&m_connection, &SyncthingConnection::statusChanged, this, &TrayWidget::updateStatusButton);
connect(&m_connection, &SyncthingConnection::trafficChanged, this, &TrayWidget::updateTraffic);
connect(&m_connection, &SyncthingConnection::newNotification, this, &TrayWidget::handleNewNotification);
connect(m_ui->dirsTreeView, &DirView::openDir, this, &TrayWidget::openDir);
connect(m_ui->dirsTreeView, &DirView::scanDir, this, &TrayWidget::scanDir);
connect(m_ui->devsTreeView, &DevView::pauseResumeDev, this, &TrayWidget::pauseResumeDev);
@ -109,7 +110,7 @@ void TrayWidget::showSettingsDialog()
{
if(!m_settingsDlg) {
m_settingsDlg = new SettingsDialog(&m_connection, this);
m_settingsDlg->setWindowTitle(tr("Settings - Syncthing tray"));
m_settingsDlg->setWindowTitle(tr("Settings") + QStringLiteral(" - " APP_NAME));
connect(m_settingsDlg, &SettingsDialog::applied, this, &TrayWidget::applySettings);
#ifndef SYNCTHINGTRAY_NO_WEBVIEW
if(m_webViewDlg) {
@ -128,8 +129,8 @@ void TrayWidget::showSettingsDialog()
void TrayWidget::showAboutDialog()
{
if(!m_aboutDlg) {
m_aboutDlg = new AboutDialog(this, QString(), QStringLiteral(APP_AUTHOR "\nfallback icons from KDE/Breeze project\nSyncthing icons from Syncthing project"), QString(), QString(), tr("Tray application for Syncthing"), QImage(QStringLiteral(":/icons/hicolor/scalable/app/syncthingtray.svg")));
m_aboutDlg->setWindowTitle(tr("About - Syncthing Tray"));
m_aboutDlg = new AboutDialog(this, QString(), QStringLiteral(APP_AUTHOR "\nfallback icons from KDE/Breeze project\nSyncthing icons from Syncthing project"), QString(), QString(), QStringLiteral(APP_DESCRIPTION), QImage(QStringLiteral(":/icons/hicolor/scalable/app/syncthingtray.svg")));
m_aboutDlg->setWindowTitle(tr("About") + QStringLiteral(" - " APP_NAME));
m_aboutDlg->setWindowIcon(QIcon(QStringLiteral(":/icons/hicolor/scalable/app/syncthingtray.svg")));
}
m_aboutDlg->show();
@ -167,7 +168,7 @@ void TrayWidget::showWebUi()
void TrayWidget::showOwnDeviceId()
{
auto *dlg = new QDialog(this);
dlg->setWindowTitle(tr("Own device ID - Syncthing Tray"));
dlg->setWindowTitle(tr("Own device ID") + QStringLiteral(" - " APP_NAME));
dlg->setWindowIcon(QIcon(QStringLiteral(":/icons/hicolor/scalable/app/syncthingtray.svg")));
dlg->setAttribute(Qt::WA_DeleteOnClose);
dlg->setBackgroundRole(QPalette::Background);
@ -201,7 +202,7 @@ void TrayWidget::showOwnDeviceId()
void TrayWidget::showLog()
{
auto *dlg = new QDialog(this);
dlg->setWindowTitle(tr("Log - Syncthing"));
dlg->setWindowTitle(tr("Log") + QStringLiteral(" - " APP_NAME));
dlg->setWindowIcon(QIcon(QStringLiteral(":/icons/hicolor/scalable/app/syncthingtray.svg")));
dlg->setAttribute(Qt::WA_DeleteOnClose);
auto *layout = new QVBoxLayout(dlg);
@ -255,6 +256,7 @@ void TrayWidget::applySettings()
} else {
m_connection.setCredentials(QString(), QString());
}
m_connection.loadSelfSignedCertificate();
m_connection.reconnect();
m_ui->trafficFrame->setVisible(Settings::showTraffic());
if(Settings::showTraffic()) {
@ -268,7 +270,7 @@ void TrayWidget::openDir(const QModelIndex &dirIndex)
if(QDir(dir->path).exists()) {
DesktopUtils::openLocalFileOrDir(dir->path);
} else {
QMessageBox::warning(this, QCoreApplication::applicationName(), tr("The directly <i>%1</i> does not exist on the local machine.").arg(dir->path));
QMessageBox::warning(this, QCoreApplication::applicationName(), tr("The directory <i>%1</i> does not exist on the local machine.").arg(dir->path));
}
}
}
@ -335,4 +337,9 @@ void TrayWidget::handleWebViewDeleted()
m_webViewDlg = nullptr;
}
void TrayWidget::handleNewNotification(const QString &msg)
{
// FIXME
}
}

View File

@ -55,6 +55,7 @@ private slots:
void changeStatus();
void updateTraffic();
void handleWebViewDeleted();
void handleNewNotification(const QString &msg);
private:
TrayMenu *m_menu;

View File

@ -13,12 +13,16 @@
#if defined(SYNCTHINGTRAY_USE_WEBENGINE)
# include <QWebEngineSettings>
# include <QWebEngineView>
# include <QWebEngineCertificateError>
#elif defined(SYNCTHINGTRAY_USE_WEBKIT)
# include <QWebSettings>
# include <QWebView>
# include <QWebFrame>
# include <QSslError>
#endif
using namespace Data;
namespace QtGui {
WebPage::WebPage(WEB_VIEW_PROVIDER *view) :
@ -32,6 +36,7 @@ WebPage::WebPage(WEB_VIEW_PROVIDER *view) :
settings()->setAttribute(QWebSettings::JavascriptCanOpenWindows, true);
setNetworkAccessManager(&Data::networkAccessManager());
connect(&Data::networkAccessManager(), &QNetworkAccessManager::authenticationRequired, this, static_cast<void(WebPage::*)(QNetworkReply *, QAuthenticator *)>(&WebPage::supplyCredentials));
connect(&Data::networkAccessManager(), &QNetworkAccessManager::sslErrors, this, static_cast<void(WebPage::*)(QNetworkReply *, const QList<QSslError> &errors)>(&WebPage::handleSslErrors));
#endif
if(!m_view) {
// delegate to external browser if no view is assigned
@ -50,9 +55,24 @@ WEB_PAGE_PROVIDER *WebPage::createWindow(WEB_PAGE_PROVIDER::WebWindowType type)
return new WebPage;
}
#ifdef SYNCTHINGTRAY_USE_WEBENGINE
bool WebPage::certificateError(const QWebEngineCertificateError &certificateError)
{
switch(certificateError.error()) {
case QWebEngineCertificateError::CertificateCommonNameInvalid:
case QWebEngineCertificateError::CertificateAuthorityInvalid:
// FIXME: only ignore the error if the used certificate matches the certificate
// known to be used by the Syncthing GUI
return true;
default:
return false;
}
}
#endif
void WebPage::delegateToExternalBrowser(const QUrl &url)
{
openUrlExternal(url);
QDesktopServices::openUrl(url);
// this page and the associated view are useless
m_view->deleteLater();
deleteLater();
@ -78,10 +98,14 @@ void WebPage::supplyCredentials(QAuthenticator *authenticator)
}
}
void WebPage::openUrlExternal(const QUrl &url)
#ifdef SYNCTHINGTRAY_USE_WEBKIT
void WebPage::handleSslErrors(QNetworkReply *reply, const QList<QSslError> &errors)
{
QDesktopServices::openUrl(url);
if(reply->request().url().host() == m_view->url().host()) {
reply->ignoreSslErrors(SyncthingConnection::expectedCertificateErrors());
}
}
#endif
}

View File

@ -13,6 +13,7 @@
QT_FORWARD_DECLARE_CLASS(WEB_VIEW_PROVIDER)
QT_FORWARD_DECLARE_CLASS(QAuthenticator)
QT_FORWARD_DECLARE_CLASS(QNetworkReply)
QT_FORWARD_DECLARE_CLASS(QSslError)
namespace QtGui {
@ -22,17 +23,20 @@ class WebPage : public WEB_PAGE_PROVIDER
public:
WebPage(WEB_VIEW_PROVIDER *view = nullptr);
public slots:
void openUrlExternal(const QUrl &url);
protected:
WEB_PAGE_PROVIDER *createWindow(WebWindowType type);
#ifdef SYNCTHINGTRAY_USE_WEBENGINE
bool certificateError(const QWebEngineCertificateError &certificateError);
#endif
private slots:
void delegateToExternalBrowser(const QUrl &url);
void supplyCredentials(const QUrl &requestUrl, QAuthenticator *authenticator);
void supplyCredentials(QNetworkReply *reply, QAuthenticator *authenticator);
void supplyCredentials(QAuthenticator *authenticator);
#ifdef SYNCTHINGTRAY_USE_WEBKIT
void handleSslErrors(QNetworkReply *, const QList<QSslError> &errors);
#endif
private:
WEB_VIEW_PROVIDER *m_view;

View File

@ -3,7 +3,6 @@
#include "./webpage.h"
#include "../application/settings.h"
#include "../data/syncthingconnection.h"
#include <qtutilities/misc/dialogutils.h>
@ -29,6 +28,7 @@ WebViewDialog::WebViewDialog(QWidget *parent) :
m_view->setPage(new WebPage(m_view));
applySettings();
if(Settings::webViewGeometry().isEmpty()) {
@ -60,4 +60,4 @@ void QtGui::WebViewDialog::closeEvent(QCloseEvent *event)
}
#endif
#endif // SYNCTHINGTRAY_NO_WEBVIEW

View File

@ -29,5 +29,5 @@ private:
}
#endif
#endif // SYNCTHINGTRAY_NO_WEBVIEW
#endif // WEBVIEW_DIALOG_H

View File

@ -4,89 +4,119 @@
<context>
<name>Data::SyncthingConnection</name>
<message>
<location filename="../data/syncthingconnection.cpp" line="108"/>
<location filename="../data/syncthingconnection.cpp" line="120"/>
<source>disconnected</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="110"/>
<location filename="../data/syncthingconnection.cpp" line="122"/>
<source>connected</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="112"/>
<location filename="../data/syncthingconnection.cpp" line="124"/>
<source>connected, notifications available</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="114"/>
<location filename="../data/syncthingconnection.cpp" line="126"/>
<source>connected, paused</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="116"/>
<location filename="../data/syncthingconnection.cpp" line="128"/>
<source>connected, synchronizing</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="118"/>
<location filename="../data/syncthingconnection.cpp" line="130"/>
<source>unknown</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="393"/>
<location filename="../data/syncthingconnection.cpp" line="427"/>
<source>Unable to parse Syncthing log: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="397"/>
<location filename="../data/syncthingconnection.cpp" line="431"/>
<source>Unable to request system log: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="425"/>
<location filename="../data/syncthingconnection.cpp" line="525"/>
<location filename="../data/syncthingconnection.cpp" line="452"/>
<source>Unable to locate certificate used by Syncthing GUI.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="458"/>
<source>Unable to load certificate used by Syncthing GUI.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="490"/>
<location filename="../data/syncthingconnection.cpp" line="590"/>
<source>Unable to parse Syncthing config: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="430"/>
<location filename="../data/syncthingconnection.cpp" line="530"/>
<location filename="../data/syncthingconnection.cpp" line="495"/>
<location filename="../data/syncthingconnection.cpp" line="595"/>
<source>Unable to request Syncthing config: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="605"/>
<location filename="../data/syncthingconnection.cpp" line="670"/>
<source>Unable to parse connections: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="610"/>
<location filename="../data/syncthingconnection.cpp" line="675"/>
<source>Unable to request connections: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="657"/>
<location filename="../data/syncthingconnection.cpp" line="725"/>
<source>Unable to parse directory statistics: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="730"/>
<source>Unable to request directory statistics: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="767"/>
<source>Unable to parse device statistics: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="772"/>
<source>Unable to request device statistics: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="823"/>
<source>Unable to parse Syncthing events: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="679"/>
<location filename="../data/syncthingconnection.cpp" line="845"/>
<source>Unable to request Syncthing events: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="844"/>
<location filename="../data/syncthingconnection.cpp" line="1051"/>
<source>Unable to request rescan: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="859"/>
<location filename="../data/syncthingconnection.cpp" line="1066"/>
<source>Unable to request pause/resume: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="361"/>
<location filename="../data/syncthingconnection.cpp" line="395"/>
<source>Unable to request QR-Code: </source>
<translation type="unfinished"></translation>
</message>
@ -94,93 +124,103 @@
<context>
<name>Data::SyncthingDeviceModel</name>
<message>
<location filename="../data/syncthingdevicemodel.cpp" line="58"/>
<location filename="../data/syncthingdevicemodel.cpp" line="84"/>
<location filename="../data/syncthingdevicemodel.cpp" line="61"/>
<location filename="../data/syncthingdevicemodel.cpp" line="87"/>
<source>ID</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdevicemodel.cpp" line="59"/>
<location filename="../data/syncthingdevicemodel.cpp" line="62"/>
<source>Status</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdevicemodel.cpp" line="85"/>
<location filename="../data/syncthingdevicemodel.cpp" line="88"/>
<source>Addresses</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdevicemodel.cpp" line="86"/>
<location filename="../data/syncthingdevicemodel.cpp" line="89"/>
<source>Last seen</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdevicemodel.cpp" line="90"/>
<source>Compression</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdevicemodel.cpp" line="87"/>
<location filename="../data/syncthingdevicemodel.cpp" line="91"/>
<source>Certificate</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdevicemodel.cpp" line="88"/>
<location filename="../data/syncthingdevicemodel.cpp" line="92"/>
<source>Introducer</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdevicemodel.cpp" line="97"/>
<location filename="../data/syncthingdevicemodel.cpp" line="102"/>
<source>none</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdevicemodel.cpp" line="98"/>
<location filename="../data/syncthingdevicemodel.cpp" line="103"/>
<source>yes</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdevicemodel.cpp" line="98"/>
<location filename="../data/syncthingdevicemodel.cpp" line="103"/>
<source>no</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdevicemodel.cpp" line="120"/>
<location filename="../data/syncthingdevicemodel.cpp" line="155"/>
<source>Unknown status</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdevicemodel.cpp" line="122"/>
<location filename="../data/syncthingdevicemodel.cpp" line="157"/>
<source>Idle</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdevicemodel.cpp" line="123"/>
<location filename="../data/syncthingdevicemodel.cpp" line="158"/>
<source>Disconnected</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdevicemodel.cpp" line="124"/>
<location filename="../data/syncthingdevicemodel.cpp" line="159"/>
<source>Synchronizing (%1 %)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdevicemodel.cpp" line="124"/>
<location filename="../data/syncthingdevicemodel.cpp" line="159"/>
<source>Synchronizing</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdevicemodel.cpp" line="121"/>
<location filename="../data/syncthingdevicemodel.cpp" line="156"/>
<source>Own device</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdevicemodel.cpp" line="117"/>
<location filename="../data/syncthingdevicemodel.cpp" line="100"/>
<source>unknown or own device</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdevicemodel.cpp" line="152"/>
<source>Paused</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdevicemodel.cpp" line="125"/>
<location filename="../data/syncthingdevicemodel.cpp" line="160"/>
<source>Out of sync</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdevicemodel.cpp" line="126"/>
<location filename="../data/syncthingdevicemodel.cpp" line="161"/>
<source>Rejected</source>
<translation type="unfinished"></translation>
</message>
@ -188,109 +228,168 @@
<context>
<name>Data::SyncthingDirectoryModel</name>
<message>
<location filename="../data/syncthingdirectorymodel.cpp" line="58"/>
<location filename="../data/syncthingdirectorymodel.cpp" line="84"/>
<location filename="../data/syncthingdirectorymodel.cpp" line="61"/>
<location filename="../data/syncthingdirectorymodel.cpp" line="87"/>
<source>ID</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdirectorymodel.cpp" line="59"/>
<location filename="../data/syncthingdirectorymodel.cpp" line="62"/>
<source>Status</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdirectorymodel.cpp" line="85"/>
<location filename="../data/syncthingdirectorymodel.cpp" line="88"/>
<source>Path</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdirectorymodel.cpp" line="86"/>
<location filename="../data/syncthingdirectorymodel.cpp" line="89"/>
<source>Devices</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdirectorymodel.cpp" line="88"/>
<location filename="../data/syncthingdirectorymodel.cpp" line="91"/>
<source>Rescan interval</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdirectorymodel.cpp" line="97"/>
<location filename="../data/syncthingdirectorymodel.cpp" line="92"/>
<source>Last scan</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdirectorymodel.cpp" line="93"/>
<source>Last file</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdirectorymodel.cpp" line="102"/>
<source>yes</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdirectorymodel.cpp" line="97"/>
<location filename="../data/syncthingdirectorymodel.cpp" line="102"/>
<source>no</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdirectorymodel.cpp" line="118"/>
<location filename="../data/syncthingdirectorymodel.cpp" line="104"/>
<location filename="../data/syncthingdirectorymodel.cpp" line="105"/>
<source>unknown</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdirectorymodel.cpp" line="143"/>
<source>Deleted at %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdirectorymodel.cpp" line="145"/>
<source>Updated at %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdirectorymodel.cpp" line="166"/>
<source>Idle</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdirectorymodel.cpp" line="119"/>
<location filename="../data/syncthingdirectorymodel.cpp" line="167"/>
<source>Scanning (%1 %)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdirectorymodel.cpp" line="120"/>
<location filename="../data/syncthingdirectorymodel.cpp" line="168"/>
<source>Synchronizing (%1 %)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdirectorymodel.cpp" line="122"/>
<location filename="../data/syncthingdirectorymodel.cpp" line="170"/>
<source>Out of sync</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdirectorymodel.cpp" line="119"/>
<location filename="../data/syncthingdirectorymodel.cpp" line="167"/>
<source>Scanning</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdirectorymodel.cpp" line="87"/>
<location filename="../data/syncthingdirectorymodel.cpp" line="90"/>
<source>Read-only</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdirectorymodel.cpp" line="117"/>
<location filename="../data/syncthingdirectorymodel.cpp" line="165"/>
<source>Unknown status</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdirectorymodel.cpp" line="120"/>
<location filename="../data/syncthingdirectorymodel.cpp" line="168"/>
<source>Synchronizing</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdirectorymodel.cpp" line="121"/>
<location filename="../data/syncthingdirectorymodel.cpp" line="169"/>
<source>Paused</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Data::Utils</name>
<message>
<location filename="../data/utils.cpp" line="16"/>
<source>%1 ago</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/utils.cpp" line="18"/>
<source>right now</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>QtGui::AppearanceOptionPage</name>
<message>
<location filename="../gui/appearanceoptionpage.ui" line="6"/>
<location filename="../gui/appearanceoptionpage.ui" line="14"/>
<source>Appearance</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/appearanceoptionpage.ui" line="12"/>
<source>Show traffic</source>
<location filename="../gui/appearanceoptionpage.ui" line="23"/>
<source>Menu size</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/appearanceoptionpage.ui" line="30"/>
<source>Optional GUI elements</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/appearanceoptionpage.ui" line="37"/>
<source>Traffic statistics</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/appearanceoptionpage.ui" line="56"/>
<source>x</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/appearanceoptionpage.ui" line="76"/>
<source>px</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>QtGui::AutostartOptionPage</name>
<message>
<location filename="../gui/autostartoptionpage.ui" line="6"/>
<location filename="../gui/autostartoptionpage.ui" line="14"/>
<source>Autostart</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/autostartoptionpage.ui" line="12"/>
<location filename="../gui/autostartoptionpage.ui" line="23"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Not implemented yet&lt;/span&gt;&lt;/p&gt;&lt;p&gt;This will allow launching the tray when the desktop environment starts and to launch Syncthing when the tray is started.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"></translation>
</message>
@ -298,50 +397,65 @@
<context>
<name>QtGui::ConnectionOptionPage</name>
<message>
<location filename="../gui/connectionoptionpage.ui" line="14"/>
<location filename="../gui/connectionoptionpage.ui" line="6"/>
<source>Connection</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/connectionoptionpage.ui" line="20"/>
<location filename="../gui/connectionoptionpage.ui" line="15"/>
<source>Syncthing URL</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/connectionoptionpage.ui" line="30"/>
<location filename="../gui/connectionoptionpage.ui" line="25"/>
<source>Authentication</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/connectionoptionpage.ui" line="43"/>
<location filename="../gui/connectionoptionpage.ui" line="38"/>
<source>User</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/connectionoptionpage.ui" line="84"/>
<location filename="../gui/connectionoptionpage.ui" line="72"/>
<source>disconnected</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/connectionoptionpage.ui" line="91"/>
<location filename="../gui/connectionoptionpage.ui" line="79"/>
<source>Status</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/connectionoptionpage.ui" line="98"/>
<location filename="../gui/connectionoptionpage.ui" line="86"/>
<source>Apply connection settings and try to reconnect</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/connectionoptionpage.ui" line="112"/>
<location filename="../gui/connectionoptionpage.ui" line="100"/>
<source>API key</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/connectionoptionpage.ui" line="53"/>
<location filename="../gui/connectionoptionpage.ui" line="110"/>
<source>Insert values from local Syncthing configuration</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/connectionoptionpage.ui" line="48"/>
<source>Password</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/settingsdialog.cpp" line="60"/>
<source>Select Syncthing config file</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/settingsdialog.cpp" line="67"/>
<source>Unable to parse the Syncthing config file.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>QtGui::DevView</name>
@ -387,35 +501,40 @@
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/notificationsoptionpage.ui" line="20"/>
<source>Notify on disconnect</source>
<location filename="../gui/notificationsoptionpage.ui" line="23"/>
<source>Notify on</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/notificationsoptionpage.ui" line="27"/>
<source>Notify on internal errors</source>
<location filename="../gui/notificationsoptionpage.ui" line="30"/>
<source>disconnect</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/notificationsoptionpage.ui" line="34"/>
<source>Notify on Syncthing errors</source>
<location filename="../gui/notificationsoptionpage.ui" line="37"/>
<source>internal errors</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/notificationsoptionpage.ui" line="41"/>
<source>Notify on sync complete</source>
<location filename="../gui/notificationsoptionpage.ui" line="44"/>
<source>Syncthing errors</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/notificationsoptionpage.ui" line="51"/>
<source>sync complete</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>QtGui::SettingsDialog</name>
<message>
<location filename="../gui/settingsdialog.cpp" line="211"/>
<location filename="../gui/settingsdialog.cpp" line="259"/>
<source>Tray</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/settingsdialog.cpp" line="219"/>
<location filename="../gui/settingsdialog.cpp" line="267"/>
<source>Web view</source>
<translation type="unfinished"></translation>
</message>
@ -524,8 +643,8 @@
<message>
<location filename="../gui/traywidget.ui" line="230"/>
<location filename="../gui/traywidget.ui" line="250"/>
<location filename="../gui/traywidget.cpp" line="321"/>
<location filename="../gui/traywidget.cpp" line="328"/>
<location filename="../gui/traywidget.cpp" line="322"/>
<location filename="../gui/traywidget.cpp" line="329"/>
<source>unknown</source>
<translation type="unfinished"></translation>
</message>
@ -551,11 +670,13 @@
</message>
<message>
<location filename="../gui/traywidget.ui" line="80"/>
<location filename="../gui/traywidget.cpp" line="132"/>
<source>About</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/traywidget.ui" line="134"/>
<location filename="../gui/traywidget.cpp" line="112"/>
<source>Settings</source>
<translation type="unfinished"></translation>
</message>
@ -569,16 +690,6 @@
<source>View own device ID</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/traywidget.cpp" line="112"/>
<source>Settings - Syncthing tray</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/traywidget.cpp" line="131"/>
<source>Tray application for Syncthing</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/traywidget.cpp" line="81"/>
<source>Rescan all directories</source>
@ -589,16 +700,6 @@
<source>Show Syncthing log</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/traywidget.cpp" line="132"/>
<source>About - Syncthing Tray</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/traywidget.cpp" line="170"/>
<source>Own device ID - Syncthing Tray</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/traywidget.cpp" line="180"/>
<source>device ID is unknown</source>
@ -609,11 +710,6 @@
<source>Copy to clipboard</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/traywidget.cpp" line="204"/>
<source>Log - Syncthing</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/traywidget.cpp" line="231"/>
<source>Not connected to Syncthing, click to connect</source>
@ -640,7 +736,17 @@
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/traywidget.cpp" line="271"/>
<location filename="../gui/traywidget.cpp" line="170"/>
<source>Own device ID</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/traywidget.cpp" line="204"/>
<source>Log</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/traywidget.cpp" line="272"/>
<source>The directly &lt;i&gt;%1&lt;/i&gt; does not exist on the local machine.</source>
<translation type="unfinished"></translation>
</message>
@ -648,7 +754,7 @@
<context>
<name>QtGui::WebViewDialog</name>
<message>
<location filename="../gui/webviewdialog.cpp" line="26"/>
<location filename="../gui/webviewdialog.cpp" line="25"/>
<source>Syncthing</source>
<translation type="unfinished"></translation>
</message>
@ -657,7 +763,7 @@
<name>QtGui::WebViewOptionPage</name>
<message>
<location filename="../gui/webviewoptionpage.ui" line="14"/>
<location filename="../gui/settingsdialog.cpp" line="173"/>
<location filename="../gui/settingsdialog.cpp" line="221"/>
<source>General</source>
<translation type="unfinished"></translation>
</message>
@ -687,7 +793,7 @@
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/settingsdialog.cpp" line="175"/>
<location filename="../gui/settingsdialog.cpp" line="223"/>
<source>Syncthing Tray has not been built with vieb view support utilizing either Qt WebKit or Qt WebEngine.
The Web UI will be opened in the default web browser instead.</source>
<translation type="unfinished"></translation>
@ -696,13 +802,23 @@ The Web UI will be opened in the default web browser instead.</source>
<context>
<name>main</name>
<message>
<location filename="../application/main.cpp" line="61"/>
<source>The system tray is (currently) not available.</source>
<location filename="../application/main.cpp" line="62"/>
<source>You must configure how to connect to Syncthing when using Syncthing Tray the first time.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../application/main.cpp" line="65"/>
<source>The Qt libraries have not been built with tray icon support.</source>
<location filename="../application/main.cpp" line="63"/>
<source>Note that the settings dialog allows importing URL, credentials and API-key from the local Syncthing configuration.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../application/main.cpp" line="68"/>
<source>The system tray is (currently) not available. You could open the tray menu as a regular window using the -w flag, though.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../application/main.cpp" line="72"/>
<source>The Qt libraries have not been built with tray icon support. You could open the tray menu as a regular window using the -w flag, though.</source>
<translation type="unfinished"></translation>
</message>
</context>

View File

@ -4,89 +4,119 @@
<context>
<name>Data::SyncthingConnection</name>
<message>
<location filename="../data/syncthingconnection.cpp" line="108"/>
<location filename="../data/syncthingconnection.cpp" line="120"/>
<source>disconnected</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="110"/>
<location filename="../data/syncthingconnection.cpp" line="122"/>
<source>connected</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="112"/>
<location filename="../data/syncthingconnection.cpp" line="124"/>
<source>connected, notifications available</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="114"/>
<location filename="../data/syncthingconnection.cpp" line="126"/>
<source>connected, paused</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="116"/>
<location filename="../data/syncthingconnection.cpp" line="128"/>
<source>connected, synchronizing</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="118"/>
<location filename="../data/syncthingconnection.cpp" line="130"/>
<source>unknown</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="393"/>
<location filename="../data/syncthingconnection.cpp" line="427"/>
<source>Unable to parse Syncthing log: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="397"/>
<location filename="../data/syncthingconnection.cpp" line="431"/>
<source>Unable to request system log: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="425"/>
<location filename="../data/syncthingconnection.cpp" line="525"/>
<location filename="../data/syncthingconnection.cpp" line="452"/>
<source>Unable to locate certificate used by Syncthing GUI.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="458"/>
<source>Unable to load certificate used by Syncthing GUI.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="490"/>
<location filename="../data/syncthingconnection.cpp" line="590"/>
<source>Unable to parse Syncthing config: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="430"/>
<location filename="../data/syncthingconnection.cpp" line="530"/>
<location filename="../data/syncthingconnection.cpp" line="495"/>
<location filename="../data/syncthingconnection.cpp" line="595"/>
<source>Unable to request Syncthing config: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="605"/>
<location filename="../data/syncthingconnection.cpp" line="670"/>
<source>Unable to parse connections: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="610"/>
<location filename="../data/syncthingconnection.cpp" line="675"/>
<source>Unable to request connections: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="657"/>
<location filename="../data/syncthingconnection.cpp" line="725"/>
<source>Unable to parse directory statistics: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="730"/>
<source>Unable to request directory statistics: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="767"/>
<source>Unable to parse device statistics: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="772"/>
<source>Unable to request device statistics: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="823"/>
<source>Unable to parse Syncthing events: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="679"/>
<location filename="../data/syncthingconnection.cpp" line="845"/>
<source>Unable to request Syncthing events: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="844"/>
<location filename="../data/syncthingconnection.cpp" line="1051"/>
<source>Unable to request rescan: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="859"/>
<location filename="../data/syncthingconnection.cpp" line="1066"/>
<source>Unable to request pause/resume: </source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingconnection.cpp" line="361"/>
<location filename="../data/syncthingconnection.cpp" line="395"/>
<source>Unable to request QR-Code: </source>
<translation type="unfinished"></translation>
</message>
@ -94,93 +124,103 @@
<context>
<name>Data::SyncthingDeviceModel</name>
<message>
<location filename="../data/syncthingdevicemodel.cpp" line="58"/>
<location filename="../data/syncthingdevicemodel.cpp" line="84"/>
<location filename="../data/syncthingdevicemodel.cpp" line="61"/>
<location filename="../data/syncthingdevicemodel.cpp" line="87"/>
<source>ID</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdevicemodel.cpp" line="59"/>
<location filename="../data/syncthingdevicemodel.cpp" line="62"/>
<source>Status</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdevicemodel.cpp" line="85"/>
<location filename="../data/syncthingdevicemodel.cpp" line="88"/>
<source>Addresses</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdevicemodel.cpp" line="86"/>
<location filename="../data/syncthingdevicemodel.cpp" line="89"/>
<source>Last seen</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdevicemodel.cpp" line="90"/>
<source>Compression</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdevicemodel.cpp" line="87"/>
<location filename="../data/syncthingdevicemodel.cpp" line="91"/>
<source>Certificate</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdevicemodel.cpp" line="88"/>
<location filename="../data/syncthingdevicemodel.cpp" line="92"/>
<source>Introducer</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdevicemodel.cpp" line="97"/>
<location filename="../data/syncthingdevicemodel.cpp" line="102"/>
<source>none</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdevicemodel.cpp" line="98"/>
<location filename="../data/syncthingdevicemodel.cpp" line="103"/>
<source>yes</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdevicemodel.cpp" line="98"/>
<location filename="../data/syncthingdevicemodel.cpp" line="103"/>
<source>no</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdevicemodel.cpp" line="120"/>
<location filename="../data/syncthingdevicemodel.cpp" line="155"/>
<source>Unknown status</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdevicemodel.cpp" line="122"/>
<location filename="../data/syncthingdevicemodel.cpp" line="157"/>
<source>Idle</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdevicemodel.cpp" line="123"/>
<location filename="../data/syncthingdevicemodel.cpp" line="158"/>
<source>Disconnected</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdevicemodel.cpp" line="124"/>
<location filename="../data/syncthingdevicemodel.cpp" line="159"/>
<source>Synchronizing (%1 %)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdevicemodel.cpp" line="124"/>
<location filename="../data/syncthingdevicemodel.cpp" line="159"/>
<source>Synchronizing</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdevicemodel.cpp" line="121"/>
<location filename="../data/syncthingdevicemodel.cpp" line="156"/>
<source>Own device</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdevicemodel.cpp" line="117"/>
<location filename="../data/syncthingdevicemodel.cpp" line="100"/>
<source>unknown or own device</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdevicemodel.cpp" line="152"/>
<source>Paused</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdevicemodel.cpp" line="125"/>
<location filename="../data/syncthingdevicemodel.cpp" line="160"/>
<source>Out of sync</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdevicemodel.cpp" line="126"/>
<location filename="../data/syncthingdevicemodel.cpp" line="161"/>
<source>Rejected</source>
<translation type="unfinished"></translation>
</message>
@ -188,109 +228,168 @@
<context>
<name>Data::SyncthingDirectoryModel</name>
<message>
<location filename="../data/syncthingdirectorymodel.cpp" line="58"/>
<location filename="../data/syncthingdirectorymodel.cpp" line="84"/>
<location filename="../data/syncthingdirectorymodel.cpp" line="61"/>
<location filename="../data/syncthingdirectorymodel.cpp" line="87"/>
<source>ID</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdirectorymodel.cpp" line="59"/>
<location filename="../data/syncthingdirectorymodel.cpp" line="62"/>
<source>Status</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdirectorymodel.cpp" line="85"/>
<location filename="../data/syncthingdirectorymodel.cpp" line="88"/>
<source>Path</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdirectorymodel.cpp" line="86"/>
<location filename="../data/syncthingdirectorymodel.cpp" line="89"/>
<source>Devices</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdirectorymodel.cpp" line="88"/>
<location filename="../data/syncthingdirectorymodel.cpp" line="91"/>
<source>Rescan interval</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdirectorymodel.cpp" line="97"/>
<location filename="../data/syncthingdirectorymodel.cpp" line="92"/>
<source>Last scan</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdirectorymodel.cpp" line="93"/>
<source>Last file</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdirectorymodel.cpp" line="102"/>
<source>yes</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdirectorymodel.cpp" line="97"/>
<location filename="../data/syncthingdirectorymodel.cpp" line="102"/>
<source>no</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdirectorymodel.cpp" line="118"/>
<location filename="../data/syncthingdirectorymodel.cpp" line="104"/>
<location filename="../data/syncthingdirectorymodel.cpp" line="105"/>
<source>unknown</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdirectorymodel.cpp" line="143"/>
<source>Deleted at %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdirectorymodel.cpp" line="145"/>
<source>Updated at %1</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdirectorymodel.cpp" line="166"/>
<source>Idle</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdirectorymodel.cpp" line="119"/>
<location filename="../data/syncthingdirectorymodel.cpp" line="167"/>
<source>Scanning (%1 %)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdirectorymodel.cpp" line="120"/>
<location filename="../data/syncthingdirectorymodel.cpp" line="168"/>
<source>Synchronizing (%1 %)</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdirectorymodel.cpp" line="122"/>
<location filename="../data/syncthingdirectorymodel.cpp" line="170"/>
<source>Out of sync</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdirectorymodel.cpp" line="119"/>
<location filename="../data/syncthingdirectorymodel.cpp" line="167"/>
<source>Scanning</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdirectorymodel.cpp" line="87"/>
<location filename="../data/syncthingdirectorymodel.cpp" line="90"/>
<source>Read-only</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdirectorymodel.cpp" line="117"/>
<location filename="../data/syncthingdirectorymodel.cpp" line="165"/>
<source>Unknown status</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdirectorymodel.cpp" line="120"/>
<location filename="../data/syncthingdirectorymodel.cpp" line="168"/>
<source>Synchronizing</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/syncthingdirectorymodel.cpp" line="121"/>
<location filename="../data/syncthingdirectorymodel.cpp" line="169"/>
<source>Paused</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>Data::Utils</name>
<message>
<location filename="../data/utils.cpp" line="16"/>
<source>%1 ago</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../data/utils.cpp" line="18"/>
<source>right now</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>QtGui::AppearanceOptionPage</name>
<message>
<location filename="../gui/appearanceoptionpage.ui" line="6"/>
<location filename="../gui/appearanceoptionpage.ui" line="14"/>
<source>Appearance</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/appearanceoptionpage.ui" line="12"/>
<source>Show traffic</source>
<location filename="../gui/appearanceoptionpage.ui" line="23"/>
<source>Menu size</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/appearanceoptionpage.ui" line="30"/>
<source>Optional GUI elements</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/appearanceoptionpage.ui" line="37"/>
<source>Traffic statistics</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/appearanceoptionpage.ui" line="56"/>
<source>x</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/appearanceoptionpage.ui" line="76"/>
<source>px</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>QtGui::AutostartOptionPage</name>
<message>
<location filename="../gui/autostartoptionpage.ui" line="6"/>
<location filename="../gui/autostartoptionpage.ui" line="14"/>
<source>Autostart</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/autostartoptionpage.ui" line="12"/>
<location filename="../gui/autostartoptionpage.ui" line="23"/>
<source>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Not implemented yet&lt;/span&gt;&lt;/p&gt;&lt;p&gt;This will allow launching the tray when the desktop environment starts and to launch Syncthing when the tray is started.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="unfinished"></translation>
</message>
@ -298,50 +397,65 @@
<context>
<name>QtGui::ConnectionOptionPage</name>
<message>
<location filename="../gui/connectionoptionpage.ui" line="14"/>
<location filename="../gui/connectionoptionpage.ui" line="6"/>
<source>Connection</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/connectionoptionpage.ui" line="20"/>
<location filename="../gui/connectionoptionpage.ui" line="15"/>
<source>Syncthing URL</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/connectionoptionpage.ui" line="30"/>
<location filename="../gui/connectionoptionpage.ui" line="25"/>
<source>Authentication</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/connectionoptionpage.ui" line="43"/>
<location filename="../gui/connectionoptionpage.ui" line="38"/>
<source>User</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/connectionoptionpage.ui" line="84"/>
<location filename="../gui/connectionoptionpage.ui" line="72"/>
<source>disconnected</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/connectionoptionpage.ui" line="91"/>
<location filename="../gui/connectionoptionpage.ui" line="79"/>
<source>Status</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/connectionoptionpage.ui" line="98"/>
<location filename="../gui/connectionoptionpage.ui" line="86"/>
<source>Apply connection settings and try to reconnect</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/connectionoptionpage.ui" line="112"/>
<location filename="../gui/connectionoptionpage.ui" line="100"/>
<source>API key</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/connectionoptionpage.ui" line="53"/>
<location filename="../gui/connectionoptionpage.ui" line="110"/>
<source>Insert values from local Syncthing configuration</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/connectionoptionpage.ui" line="48"/>
<source>Password</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/settingsdialog.cpp" line="60"/>
<source>Select Syncthing config file</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/settingsdialog.cpp" line="67"/>
<source>Unable to parse the Syncthing config file.</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>QtGui::DevView</name>
@ -387,35 +501,40 @@
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/notificationsoptionpage.ui" line="20"/>
<source>Notify on disconnect</source>
<location filename="../gui/notificationsoptionpage.ui" line="23"/>
<source>Notify on</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/notificationsoptionpage.ui" line="27"/>
<source>Notify on internal errors</source>
<location filename="../gui/notificationsoptionpage.ui" line="30"/>
<source>disconnect</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/notificationsoptionpage.ui" line="34"/>
<source>Notify on Syncthing errors</source>
<location filename="../gui/notificationsoptionpage.ui" line="37"/>
<source>internal errors</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/notificationsoptionpage.ui" line="41"/>
<source>Notify on sync complete</source>
<location filename="../gui/notificationsoptionpage.ui" line="44"/>
<source>Syncthing errors</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/notificationsoptionpage.ui" line="51"/>
<source>sync complete</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>QtGui::SettingsDialog</name>
<message>
<location filename="../gui/settingsdialog.cpp" line="211"/>
<location filename="../gui/settingsdialog.cpp" line="259"/>
<source>Tray</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/settingsdialog.cpp" line="219"/>
<location filename="../gui/settingsdialog.cpp" line="267"/>
<source>Web view</source>
<translation type="unfinished"></translation>
</message>
@ -524,8 +643,8 @@
<message>
<location filename="../gui/traywidget.ui" line="230"/>
<location filename="../gui/traywidget.ui" line="250"/>
<location filename="../gui/traywidget.cpp" line="321"/>
<location filename="../gui/traywidget.cpp" line="328"/>
<location filename="../gui/traywidget.cpp" line="322"/>
<location filename="../gui/traywidget.cpp" line="329"/>
<source>unknown</source>
<translation type="unfinished"></translation>
</message>
@ -551,11 +670,13 @@
</message>
<message>
<location filename="../gui/traywidget.ui" line="80"/>
<location filename="../gui/traywidget.cpp" line="132"/>
<source>About</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/traywidget.ui" line="134"/>
<location filename="../gui/traywidget.cpp" line="112"/>
<source>Settings</source>
<translation type="unfinished"></translation>
</message>
@ -569,16 +690,6 @@
<source>View own device ID</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/traywidget.cpp" line="112"/>
<source>Settings - Syncthing tray</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/traywidget.cpp" line="131"/>
<source>Tray application for Syncthing</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/traywidget.cpp" line="81"/>
<source>Rescan all directories</source>
@ -589,16 +700,6 @@
<source>Show Syncthing log</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/traywidget.cpp" line="132"/>
<source>About - Syncthing Tray</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/traywidget.cpp" line="170"/>
<source>Own device ID - Syncthing Tray</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/traywidget.cpp" line="180"/>
<source>device ID is unknown</source>
@ -609,11 +710,6 @@
<source>Copy to clipboard</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/traywidget.cpp" line="204"/>
<source>Log - Syncthing</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/traywidget.cpp" line="231"/>
<source>Not connected to Syncthing, click to connect</source>
@ -640,7 +736,17 @@
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/traywidget.cpp" line="271"/>
<location filename="../gui/traywidget.cpp" line="170"/>
<source>Own device ID</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/traywidget.cpp" line="204"/>
<source>Log</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/traywidget.cpp" line="272"/>
<source>The directly &lt;i&gt;%1&lt;/i&gt; does not exist on the local machine.</source>
<translation type="unfinished"></translation>
</message>
@ -648,7 +754,7 @@
<context>
<name>QtGui::WebViewDialog</name>
<message>
<location filename="../gui/webviewdialog.cpp" line="26"/>
<location filename="../gui/webviewdialog.cpp" line="25"/>
<source>Syncthing</source>
<translation type="unfinished"></translation>
</message>
@ -657,7 +763,7 @@
<name>QtGui::WebViewOptionPage</name>
<message>
<location filename="../gui/webviewoptionpage.ui" line="14"/>
<location filename="../gui/settingsdialog.cpp" line="173"/>
<location filename="../gui/settingsdialog.cpp" line="221"/>
<source>General</source>
<translation type="unfinished"></translation>
</message>
@ -687,7 +793,7 @@
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../gui/settingsdialog.cpp" line="175"/>
<location filename="../gui/settingsdialog.cpp" line="223"/>
<source>Syncthing Tray has not been built with vieb view support utilizing either Qt WebKit or Qt WebEngine.
The Web UI will be opened in the default web browser instead.</source>
<translation type="unfinished"></translation>
@ -696,13 +802,23 @@ The Web UI will be opened in the default web browser instead.</source>
<context>
<name>main</name>
<message>
<location filename="../application/main.cpp" line="61"/>
<source>The system tray is (currently) not available.</source>
<location filename="../application/main.cpp" line="62"/>
<source>You must configure how to connect to Syncthing when using Syncthing Tray the first time.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../application/main.cpp" line="65"/>
<source>The Qt libraries have not been built with tray icon support.</source>
<location filename="../application/main.cpp" line="63"/>
<source>Note that the settings dialog allows importing URL, credentials and API-key from the local Syncthing configuration.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../application/main.cpp" line="68"/>
<source>The system tray is (currently) not available. You could open the tray menu as a regular window using the -w flag, though.</source>
<translation type="unfinished"></translation>
</message>
<message>
<location filename="../application/main.cpp" line="72"/>
<source>The Qt libraries have not been built with tray icon support. You could open the tray menu as a regular window using the -w flag, though.</source>
<translation type="unfinished"></translation>
</message>
</context>