Add syncthingctl, see README.md
This commit is contained in:
parent
6470038fc7
commit
1f21c2dc52
|
@ -3,7 +3,6 @@ cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR)
|
|||
# metadata
|
||||
set(META_PROJECT_NAME syncthingtray)
|
||||
set(META_PROJECT_TYPE application)
|
||||
set(META_APP_NAME "Syncthing Tray")
|
||||
set(META_APP_AUTHOR "Martchus")
|
||||
set(META_APP_URL "https://github.com/${META_APP_AUTHOR}/${META_PROJECT_NAME}")
|
||||
set(META_APP_DESCRIPTION "Tray application for Syncthing")
|
||||
|
@ -15,12 +14,16 @@ set(META_VERSION_PATCH 2)
|
|||
project(${META_PROJECT_NAME})
|
||||
|
||||
# options for partial build
|
||||
option(NO_CLI "specifies whether building CLI should be skipped" OFF)
|
||||
option(NO_TRAY "specifies whether building the tray should be skipped" OFF)
|
||||
option(NO_MODEL "specifies whether building models should be skipped, implies NO_TRAY" OFF)
|
||||
|
||||
# add subdirectories
|
||||
add_subdirectory(connector)
|
||||
link_directories(${LIB_SYNCTHING_CONNECTOR_BINARY_DIR})
|
||||
if(NOT NO_CLI)
|
||||
add_subdirectory(cli)
|
||||
endif()
|
||||
if(NOT NO_MODEL)
|
||||
add_subdirectory(model)
|
||||
link_directories(${LIB_SYNCTHING_MODEL_BINARY_DIR})
|
||||
|
|
|
@ -31,12 +31,12 @@ support
|
|||
* 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)
|
||||
* Allows quickly switching between multiple Syncthing instances
|
||||
* Features a simple command line utility `syncthingctl` to check Syncthing status and trigger rescan/pause/resume/restart
|
||||
|
||||
## Planned features
|
||||
The tray is still under development; the following features are planned:
|
||||
* Show recently processed items
|
||||
* Improve notification handling
|
||||
* Create simple command line application
|
||||
* Create Plasmoid for Plasma 5 desktop
|
||||
|
||||
## Screenshots
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR)
|
||||
|
||||
# metadata
|
||||
set(META_PROJECT_NAME syncthingctl)
|
||||
set(META_APP_NAME "Syncthing control")
|
||||
set(META_APP_DESCRIPTION "Command line app to control Syncthing")
|
||||
set(META_PROJECT_TYPE application)
|
||||
set(META_GUI_OPTIONAL false)
|
||||
|
||||
# add project files
|
||||
set(HEADER_FILES
|
||||
helper.h
|
||||
args.h
|
||||
application.h
|
||||
)
|
||||
set(SRC_FILES
|
||||
main.cpp
|
||||
args.cpp
|
||||
application.cpp
|
||||
)
|
||||
|
||||
# find c++utilities
|
||||
find_package(c++utilities 4.1.0 REQUIRED)
|
||||
use_cpp_utilities()
|
||||
|
||||
# find qtutilities
|
||||
find_package(qtutilities 5.0.0 REQUIRED)
|
||||
use_qt_utilities()
|
||||
|
||||
# find backend libraries
|
||||
find_package(syncthingconnector ${META_APP_VERSION} REQUIRED)
|
||||
use_syncthingconnector()
|
||||
|
||||
# link also explicitely against the following Qt 5 modules
|
||||
list(APPEND ADDITIONAL_QT_MODULES Network)
|
||||
|
||||
# include modules to apply configuration
|
||||
include(BasicConfig)
|
||||
include(QtConfig)
|
||||
include(WindowsResources)
|
||||
include(AppTarget)
|
||||
include(ShellCompletion)
|
||||
include(Doxygen)
|
||||
include(ConfigHeader)
|
|
@ -0,0 +1,385 @@
|
|||
#include "./application.h"
|
||||
#include "./helper.h"
|
||||
|
||||
#include "../connector/syncthingconfig.h"
|
||||
|
||||
#include <c++utilities/application/failure.h>
|
||||
#include <c++utilities/io/ansiescapecodes.h>
|
||||
#include <c++utilities/chrono/timespan.h>
|
||||
#include <c++utilities/conversion/stringconversion.h>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QHostAddress>
|
||||
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
using namespace std::placeholders;
|
||||
using namespace ApplicationUtilities;
|
||||
using namespace EscapeCodes;
|
||||
using namespace ChronoUtilities;
|
||||
using namespace ConversionUtilities;
|
||||
using namespace Data;
|
||||
|
||||
namespace Cli {
|
||||
|
||||
Application::Application() :
|
||||
m_expectedResponse(0)
|
||||
{
|
||||
// take ownership over the global QNetworkAccessManager
|
||||
networkAccessManager().setParent(this);
|
||||
|
||||
// setup argument callbacks
|
||||
m_args.status.setCallback(bind(&Application::printStatus, this, _1));
|
||||
m_args.log.setCallback(bind(&Application::requestLog, this, _1));
|
||||
m_args.restart.setCallback(bind(&Application::requestRestart, this, _1));
|
||||
m_args.rescan.setCallback(bind(&Application::requestRescan, this, _1));
|
||||
m_args.rescanAll.setCallback(bind(&Application::requestRescanAll, this, _1));
|
||||
m_args.pause.setCallback(bind(&Application::requestPause, this, _1));
|
||||
m_args.pauseAll.setCallback(bind(&Application::requestPauseAll, this, _1));
|
||||
m_args.resume.setCallback(bind(&Application::requestResume, this, _1));
|
||||
m_args.resumeAll.setCallback(bind(&Application::requestResumeAll, this, _1));
|
||||
|
||||
// connect signals and slots
|
||||
connect(&m_connection, &SyncthingConnection::statusChanged, this, &Application::handleStatusChanged);
|
||||
connect(&m_connection, &SyncthingConnection::error, this, &Application::handleError);
|
||||
}
|
||||
|
||||
Application::~Application()
|
||||
{}
|
||||
|
||||
int Application::exec(int argc, const char * const *argv)
|
||||
{
|
||||
try {
|
||||
// parse arguments
|
||||
m_args.parser.readArgs(argc, argv);
|
||||
m_args.parser.checkConstraints();
|
||||
|
||||
// handle help argument
|
||||
if(m_args.help.isPresent()) {
|
||||
m_args.parser.printHelp(cout);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// locate and read Syncthing config file
|
||||
QString configFile;
|
||||
const char *configFileArgValue = m_args.configFile.firstValue();
|
||||
if(configFileArgValue) {
|
||||
configFile = QString::fromLocal8Bit(configFileArgValue);
|
||||
} else {
|
||||
configFile = SyncthingConfig::locateConfigFile();
|
||||
}
|
||||
SyncthingConfig config;
|
||||
const char *apiKeyArgValue = m_args.apiKey.firstValue();
|
||||
if(!config.restore(configFile)) {
|
||||
if(configFileArgValue) {
|
||||
cerr << "Error: Unable to locate specified Syncthing config file \"" << configFileArgValue << "\"" << endl;
|
||||
return -1;
|
||||
} else if(!apiKeyArgValue) {
|
||||
cerr << "Error: Unable to locate Syncthing config file and no API key specified" << endl;
|
||||
return -2;
|
||||
}
|
||||
}
|
||||
|
||||
// apply settings for connection
|
||||
if(const char *urlArgValue = m_args.url.firstValue()) {
|
||||
m_settings.syncthingUrl = QString::fromLocal8Bit(urlArgValue);
|
||||
} else if(!config.guiAddress.isEmpty()) {
|
||||
m_settings.syncthingUrl = (config.guiEnforcesSecureConnection || !QHostAddress(config.guiAddress.mid(0, config.guiAddress.indexOf(QChar(':')))).isLoopback() ? QStringLiteral("https://") : QStringLiteral("http://")) + config.guiAddress;
|
||||
} else {
|
||||
m_settings.syncthingUrl = QStringLiteral("http://localhost:8080");
|
||||
}
|
||||
if(m_args.credentials.isPresent()) {
|
||||
m_settings.authEnabled = true;
|
||||
m_settings.userName = QString::fromLocal8Bit(m_args.credentials.values(0)[0]);
|
||||
m_settings.password = QString::fromLocal8Bit(m_args.credentials.values(0)[1]);
|
||||
}
|
||||
if(apiKeyArgValue) {
|
||||
m_settings.apiKey.append(apiKeyArgValue);
|
||||
} else {
|
||||
m_settings.apiKey.append(config.guiApiKey);
|
||||
}
|
||||
if(const char *certArgValue = m_args.certificate.firstValue()) {
|
||||
m_settings.httpsCertPath = QString::fromLocal8Bit(certArgValue);
|
||||
if(m_settings.httpsCertPath.isEmpty() || !m_settings.loadHttpsCert()) {
|
||||
cerr << "Error: Unable to load specified certificate \"" << m_args.certificate.firstValue() << "\"" << endl;
|
||||
return -3;
|
||||
}
|
||||
}
|
||||
|
||||
// finally to request / establish connection
|
||||
if(m_args.status.isPresent() || m_args.rescanAll.isPresent() || m_args.pauseAll.isPresent() || m_args.resumeAll.isPresent()) {
|
||||
// those arguments rquire establishing a connection first, the actual handler is called by handleStatusChanged() when
|
||||
// the connection has been established
|
||||
m_connection.reconnect(m_settings);
|
||||
cerr << "Connecting to " << m_settings.syncthingUrl.toLocal8Bit().data() << " ...";
|
||||
cerr.flush();
|
||||
} else {
|
||||
// call handler for any other arguments directly
|
||||
m_connection.applySettings(m_settings);
|
||||
m_args.parser.invokeCallbacks();
|
||||
}
|
||||
|
||||
// enter event loop
|
||||
return QCoreApplication::exec();
|
||||
|
||||
} catch(const Failure &ex) {
|
||||
cerr << "Unable to parse arguments. " << ex.what() << "\nSee --help for available commands." << endl;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
void Application::handleStatusChanged(SyncthingStatus newStatus)
|
||||
{
|
||||
Q_UNUSED(newStatus)
|
||||
if(m_connection.isConnected()) {
|
||||
eraseLine(cout);
|
||||
cout << '\r';
|
||||
m_args.parser.invokeCallbacks();
|
||||
m_connection.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::handleResponse()
|
||||
{
|
||||
if(m_expectedResponse) {
|
||||
if(!--m_expectedResponse) {
|
||||
QCoreApplication::quit();
|
||||
}
|
||||
} else {
|
||||
cerr << "Error: Unexpected response" << endl;
|
||||
QCoreApplication::exit(-4);
|
||||
}
|
||||
}
|
||||
|
||||
void Application::handleError(const QString &message)
|
||||
{
|
||||
eraseLine(cout);
|
||||
cerr << "\rError: " << message.toLocal8Bit().data() << endl;
|
||||
QCoreApplication::exit(-3);
|
||||
}
|
||||
|
||||
void Application::requestLog(const ArgumentOccurrence &)
|
||||
{
|
||||
m_connection.requestLog(bind(&Application::printLog, this, _1));
|
||||
cerr << "Request log from " << m_settings.syncthingUrl.toLocal8Bit().data() << " ...";
|
||||
cerr.flush();
|
||||
}
|
||||
|
||||
void Application::requestRestart(const ArgumentOccurrence &)
|
||||
{
|
||||
connect(&m_connection, &SyncthingConnection::restartTriggered, &QCoreApplication::quit);
|
||||
m_connection.restart();
|
||||
cerr << "Request restart " << m_settings.syncthingUrl.toLocal8Bit().data() << " ...";
|
||||
cerr.flush();
|
||||
}
|
||||
|
||||
void Application::requestRescan(const ArgumentOccurrence &occurrence)
|
||||
{
|
||||
m_expectedResponse = occurrence.values.size();
|
||||
connect(&m_connection, &SyncthingConnection::rescanTriggered, this, &Application::handleResponse);
|
||||
for(const char *value : occurrence.values) {
|
||||
cerr << "Request rescanning " << value << " ...\n";
|
||||
m_connection.rescan(QString::fromLocal8Bit(value));
|
||||
}
|
||||
cerr.flush();
|
||||
}
|
||||
|
||||
void Application::requestRescanAll(const ArgumentOccurrence &)
|
||||
{
|
||||
m_expectedResponse = m_connection.dirInfo().size();
|
||||
connect(&m_connection, &SyncthingConnection::rescanTriggered, this, &Application::handleResponse);
|
||||
cerr << "Request rescanning all directories ..." << endl;
|
||||
m_connection.rescanAllDirs();
|
||||
}
|
||||
|
||||
void Application::requestPause(const ArgumentOccurrence &occurrence)
|
||||
{
|
||||
m_expectedResponse = occurrence.values.size();
|
||||
connect(&m_connection, &SyncthingConnection::pauseTriggered, this, &Application::handleResponse);
|
||||
for(const char *value : occurrence.values) {
|
||||
cerr << "Request pausing " << value << " ...\n";
|
||||
m_connection.pause(QString::fromLocal8Bit(value));
|
||||
}
|
||||
cerr.flush();
|
||||
}
|
||||
|
||||
void Application::requestPauseAll(const ArgumentOccurrence &)
|
||||
{
|
||||
m_expectedResponse = m_connection.devInfo().size();
|
||||
connect(&m_connection, &SyncthingConnection::pauseTriggered, this, &Application::handleResponse);
|
||||
cerr << "Request pausing all devices ..." << endl;
|
||||
m_connection.pauseAllDevs();
|
||||
}
|
||||
|
||||
void Application::requestResume(const ArgumentOccurrence &occurrence)
|
||||
{
|
||||
m_expectedResponse = occurrence.values.size();
|
||||
connect(&m_connection, &SyncthingConnection::resumeTriggered, this, &Application::handleResponse);
|
||||
for(const char *value : occurrence.values) {
|
||||
cerr << "Request resuming " << value << " ...\n";
|
||||
m_connection.resume(QString::fromLocal8Bit(value));
|
||||
}
|
||||
cerr.flush();
|
||||
}
|
||||
|
||||
void Application::requestResumeAll(const ArgumentOccurrence &)
|
||||
{
|
||||
m_expectedResponse = m_connection.devInfo().size();
|
||||
connect(&m_connection, &SyncthingConnection::resumeTriggered, this, &Application::handleResponse);
|
||||
cerr << "Request resuming all devices ..." << endl;
|
||||
m_connection.resumeAllDevs();
|
||||
}
|
||||
|
||||
void Application::printStatus(const ArgumentOccurrence &)
|
||||
{
|
||||
// find relevant dirs and devs
|
||||
std::vector<const SyncthingDir *> relevantDirs;
|
||||
std::vector<const SyncthingDev *> relevantDevs;
|
||||
int dummy;
|
||||
if(m_args.dir.isPresent()) {
|
||||
relevantDirs.reserve(m_args.dir.occurrences());
|
||||
for(size_t i = 0; i != m_args.dir.occurrences(); ++i) {
|
||||
if(const SyncthingDir *dir = m_connection.findDirInfo(QString::fromLocal8Bit(m_args.dir.values(i).front()), dummy)) {
|
||||
relevantDirs.emplace_back(dir);
|
||||
} else {
|
||||
cerr << "Warning: Specified directory \"" << m_args.dir.values(i).front() << "\" does not exist" << endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(m_args.dev.isPresent()) {
|
||||
relevantDevs.reserve(m_args.dev.occurrences());
|
||||
for(size_t i = 0; i != m_args.dev.occurrences(); ++i) {
|
||||
const SyncthingDev *dev = m_connection.findDevInfo(QString::fromLocal8Bit(m_args.dev.values(i).front()), dummy);
|
||||
if(!dev) {
|
||||
dev = m_connection.findDevInfoByName(QString::fromLocal8Bit(m_args.dev.values(i).front()), dummy);
|
||||
}
|
||||
if(dev) {
|
||||
relevantDevs.emplace_back(dev);
|
||||
} else {
|
||||
cerr << "Warning: Specified device \"" << m_args.dev.values(i).front() << "\" does not exist" << endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(relevantDirs.empty() && relevantDevs.empty()) {
|
||||
relevantDirs.reserve(m_connection.dirInfo().size());
|
||||
for(const SyncthingDir &dir : m_connection.dirInfo()) {
|
||||
relevantDirs.emplace_back(&dir);
|
||||
}
|
||||
relevantDevs.reserve(m_connection.devInfo().size());
|
||||
for(const SyncthingDev &dev : m_connection.devInfo()) {
|
||||
relevantDevs.emplace_back(&dev);
|
||||
}
|
||||
}
|
||||
|
||||
// display dirs
|
||||
if(!relevantDirs.empty()) {
|
||||
setStyle(cout, TextAttribute::Bold);
|
||||
cout << "Directories\n";
|
||||
setStyle(cout);
|
||||
for(const SyncthingDir *dir : relevantDirs) {
|
||||
cout << " - ";
|
||||
setStyle(cout, TextAttribute::Bold);
|
||||
cout << dir->id.toLocal8Bit().data() << '\n';
|
||||
setStyle(cout);
|
||||
printProperty("Label", dir->label);
|
||||
printProperty("Path", dir->path);
|
||||
const char *status;
|
||||
switch(dir->status) {
|
||||
case DirStatus::Idle:
|
||||
status = "idle"; break;
|
||||
case DirStatus::Scanning:
|
||||
status = "scanning"; break;
|
||||
case DirStatus::Synchronizing:
|
||||
status = "synchronizing"; break;
|
||||
case DirStatus::Paused:
|
||||
status = "paused"; break;
|
||||
case DirStatus::OutOfSync:
|
||||
status = "out of sync"; break;
|
||||
default:
|
||||
status = "unknown";
|
||||
}
|
||||
printProperty("Status", status);
|
||||
printProperty("Last scan time", dir->lastScanTime);
|
||||
printProperty("Last file time", dir->lastFileTime);
|
||||
printProperty("Last file name", dir->lastFileName);
|
||||
printProperty("Download progress", dir->downloadLabel);
|
||||
printProperty("Devices", dir->devices);
|
||||
printProperty("Read-only", dir->readOnly);
|
||||
printProperty("Ignore permissions", dir->ignorePermissions);
|
||||
printProperty("Auto-normalize", dir->autoNormalize);
|
||||
printProperty("Rescan interval", TimeSpan::fromSeconds(dir->rescanInterval));
|
||||
printProperty("Min. free disk percentage", dir->minDiskFreePercentage);
|
||||
cout << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
// display devs
|
||||
if(!relevantDevs.empty()) {
|
||||
setStyle(cout, TextAttribute::Bold);
|
||||
cout << "Devices\n";
|
||||
setStyle(cout);
|
||||
for(const SyncthingDev *dev : relevantDevs) {
|
||||
cout << " - ";
|
||||
setStyle(cout, TextAttribute::Bold);
|
||||
cout << dev->name.toLocal8Bit().data() << '\n';
|
||||
setStyle(cout);
|
||||
printProperty("ID", dev->id);
|
||||
const char *status;
|
||||
if(dev->paused) {
|
||||
status = "paused";
|
||||
} else {
|
||||
switch(dev->status) {
|
||||
case DevStatus::Disconnected:
|
||||
status = "disconnected"; break;
|
||||
case DevStatus::OwnDevice:
|
||||
status = "own device"; break;
|
||||
case DevStatus::Idle:
|
||||
status = "idle"; break;
|
||||
case DevStatus::Synchronizing:
|
||||
status = "synchronizing"; break;
|
||||
case DevStatus::OutOfSync:
|
||||
status = "out of sync"; break;
|
||||
case DevStatus::Rejected:
|
||||
status = "rejected"; break;
|
||||
default:
|
||||
status = "unknown";
|
||||
}
|
||||
}
|
||||
printProperty("Status", status);
|
||||
printProperty("Addresses", dev->addresses);
|
||||
printProperty("Compression", dev->compression);
|
||||
printProperty("Cert name", dev->certName);
|
||||
printProperty("Connection address", dev->connectionAddress);
|
||||
printProperty("Connection type", dev->connectionType);
|
||||
printProperty("Client version", dev->clientVersion);
|
||||
printProperty("Last seen", dev->lastSeen);
|
||||
if(dev->totalIncomingTraffic > 0) {
|
||||
printProperty("Incoming traffic", dataSizeToString(static_cast<uint64>(dev->totalIncomingTraffic)).data());
|
||||
}
|
||||
if(dev->totalOutgoingTraffic > 0) {
|
||||
printProperty("Outgoing traffic", dataSizeToString(static_cast<uint64>(dev->totalOutgoingTraffic)).data());
|
||||
}
|
||||
cout << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
cout.flush();
|
||||
QCoreApplication::exit();
|
||||
}
|
||||
|
||||
void Application::printLog(const std::vector<SyncthingLogEntry> &logEntries)
|
||||
{
|
||||
eraseLine(cout);
|
||||
cout << '\r';
|
||||
|
||||
for(const SyncthingLogEntry &entry : logEntries) {
|
||||
cout << DateTime::fromIsoStringLocal(entry.when.toLocal8Bit().data()).toString(DateTimeOutputFormat::DateAndTime, true).data() << ':' << ' ' << entry.message.toLocal8Bit().data() << '\n';
|
||||
}
|
||||
cout.flush();
|
||||
QCoreApplication::exit();
|
||||
}
|
||||
|
||||
} // namespace Cli
|
|
@ -0,0 +1,48 @@
|
|||
#ifndef CLI_APPLICATION_H
|
||||
#define CLI_APPLICATION_H
|
||||
|
||||
#include "./args.h"
|
||||
|
||||
#include "../connector/syncthingconnection.h"
|
||||
#include "../connector/syncthingconnectionsettings.h"
|
||||
|
||||
#include <QObject>
|
||||
|
||||
namespace Cli {
|
||||
|
||||
class Application : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Application();
|
||||
~Application();
|
||||
|
||||
int exec(int argc, const char *const *argv);
|
||||
|
||||
private slots:
|
||||
void handleStatusChanged(Data::SyncthingStatus newStatus);
|
||||
void handleResponse();
|
||||
void handleError(const QString &message);
|
||||
|
||||
private:
|
||||
void requestLog(const ArgumentOccurrence &);
|
||||
void requestRestart(const ArgumentOccurrence &);
|
||||
void requestRescan(const ArgumentOccurrence &occurrence);
|
||||
void requestRescanAll(const ArgumentOccurrence &);
|
||||
void requestPause(const ArgumentOccurrence &occurrence);
|
||||
void requestPauseAll(const ArgumentOccurrence &);
|
||||
void requestResume(const ArgumentOccurrence &);
|
||||
void requestResumeAll(const ArgumentOccurrence &);
|
||||
void printStatus(const ArgumentOccurrence &);
|
||||
void printLog(const std::vector<Data::SyncthingLogEntry> &logEntries);
|
||||
|
||||
Args m_args;
|
||||
Data::SyncthingConnectionSettings m_settings;
|
||||
Data::SyncthingConnection m_connection;
|
||||
size_t m_expectedResponse;
|
||||
};
|
||||
|
||||
} // namespace Cli
|
||||
|
||||
#endif // CLI_APPLICATION_H
|
|
@ -0,0 +1,45 @@
|
|||
#include "./args.h"
|
||||
|
||||
namespace Cli {
|
||||
|
||||
Args::Args() :
|
||||
help(parser),
|
||||
status("status", 's', "shows the status"),
|
||||
log("log", 'l', "shows the Syncthing log"),
|
||||
restart("restart", '\0', "restarts Syncthing"),
|
||||
rescan("rescan", 'r', "rescans the specified directories"),
|
||||
rescanAll("rescan-all", '\0', "rescans all directories"),
|
||||
pause("pause", '\0', "pauses the specified devices"),
|
||||
pauseAll("pause-all", '\0', "pauses all devices"),
|
||||
resume("resume", '\0', "resumes the specified devices"),
|
||||
resumeAll("resume-all", '\0', "resumes all devices"),
|
||||
dir("dir", 'd', "specifies the directory to display status info for (default is all dirs)", {"ID"}),
|
||||
dev("dev", '\0', "specifies the device to display status info for (default is all devs)", {"ID"}),
|
||||
configFile("config-file", 'f', "specifies the Syncthing config file", {"path"}),
|
||||
apiKey("api-key", 'k', "specifies the API key", {"key"}),
|
||||
url("url", 'u', "specifies the Syncthing URL, default is http://localhost:8080", {"URL"}),
|
||||
credentials("credentials", 'c', "specifies user name and password", {"user name", "password"}),
|
||||
certificate("cert", '\0', "specifies the certificate used by the Syncthing instance", {"path"})
|
||||
{
|
||||
dir.setConstraints(0, -1), dev.setConstraints(0, -1);
|
||||
status.setSubArguments({&dir, &dev});
|
||||
|
||||
rescan.setValueNames({"dir ID"});
|
||||
rescan.setRequiredValueCount(-1);
|
||||
pause.setValueNames({"dev ID"});
|
||||
pause.setRequiredValueCount(-1);
|
||||
resume.setValueNames({"dev ID"});
|
||||
resume.setRequiredValueCount(-1);
|
||||
|
||||
parser.setMainArguments({&status, &log, &restart, &rescan, &rescanAll, &pause, &pauseAll,
|
||||
&resume, &resumeAll,
|
||||
&configFile, &apiKey, &url, &credentials, &certificate, &help});
|
||||
|
||||
// allow setting default values via environment
|
||||
configFile.setEnvironmentVariable("SYNCTHING_CTL_CONFIG_FILE");
|
||||
apiKey.setEnvironmentVariable("SYNCTHING_CTL_API_KEY");
|
||||
url.setEnvironmentVariable("SYNCTHING_CTL_URL");
|
||||
certificate.setEnvironmentVariable("SYNCTHING_CTL_CERTIFICATE");
|
||||
}
|
||||
|
||||
} // namespace Cli
|
|
@ -0,0 +1,22 @@
|
|||
#ifndef CLI_ARGS_H
|
||||
#define CLI_ARGS_H
|
||||
|
||||
#include <c++utilities/application/argumentparser.h>
|
||||
|
||||
namespace Cli {
|
||||
|
||||
using namespace ApplicationUtilities;
|
||||
|
||||
struct Args
|
||||
{
|
||||
Args();
|
||||
ArgumentParser parser;
|
||||
HelpArgument help;
|
||||
OperationArgument status, log, restart, rescan, rescanAll, pause, pauseAll, resume, resumeAll;
|
||||
ConfigValueArgument dir, dev;
|
||||
ConfigValueArgument configFile, apiKey, url, credentials, certificate;
|
||||
};
|
||||
|
||||
} // namespace Cli
|
||||
|
||||
#endif // CLI_ARGS_H
|
|
@ -0,0 +1,70 @@
|
|||
#ifndef SYNCTHINGCTL_HELPER
|
||||
#define SYNCTHINGCTL_HELPER
|
||||
|
||||
#include <c++utilities/application/commandlineutils.h>
|
||||
#include <c++utilities/chrono/datetime.h>
|
||||
#include <c++utilities/chrono/timespan.h>
|
||||
#include <c++utilities/conversion/stringconversion.h>
|
||||
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
|
||||
namespace Cli {
|
||||
|
||||
inline void printProperty(const char *propName, const char *value, const char *suffix = nullptr, ApplicationUtilities::Indentation indentation = 3)
|
||||
{
|
||||
if(*value) {
|
||||
std::cout << indentation << propName << ApplicationUtilities::Indentation(30 - strlen(propName)) << value;
|
||||
if(suffix) {
|
||||
std::cout << ' ' << suffix;
|
||||
}
|
||||
std::cout << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
inline void printProperty(const char *propName, const QString &value, const char *suffix = nullptr, ApplicationUtilities::Indentation indentation = 3)
|
||||
{
|
||||
printProperty(propName, value.toLocal8Bit().data(), suffix, indentation);
|
||||
}
|
||||
|
||||
inline void printProperty(const char *propName, const QStringList &value, const char *suffix = nullptr, ApplicationUtilities::Indentation indentation = 3)
|
||||
{
|
||||
for(const QString &str : value) {
|
||||
printProperty(propName, str, suffix, indentation);
|
||||
propName = "";
|
||||
}
|
||||
}
|
||||
|
||||
inline void printProperty(const char *propName, ChronoUtilities::TimeSpan timeSpan, const char *suffix = nullptr, ApplicationUtilities::Indentation indentation = 3)
|
||||
{
|
||||
if(!timeSpan.isNull()) {
|
||||
printProperty(propName, timeSpan.toString(ChronoUtilities::TimeSpanOutputFormat::WithMeasures).data(), suffix, indentation);
|
||||
}
|
||||
}
|
||||
|
||||
inline void printProperty(const char *propName, ChronoUtilities::DateTime dateTime, const char *suffix = nullptr, ApplicationUtilities::Indentation indentation = 3)
|
||||
{
|
||||
if(!dateTime.isNull()) {
|
||||
printProperty(propName, dateTime.toString().data(), suffix, indentation);
|
||||
}
|
||||
}
|
||||
|
||||
inline void printProperty(const char *propName, bool value, const char *suffix = nullptr, ApplicationUtilities::Indentation indentation = 3)
|
||||
{
|
||||
printProperty(propName, value ? "yes" : "no", suffix, indentation);
|
||||
}
|
||||
|
||||
template<typename intType>
|
||||
inline void printProperty(const char *propName, const intType value, const char *suffix = nullptr, bool force = false, ApplicationUtilities::Indentation indentation = 3)
|
||||
{
|
||||
if(value != 0 || force) {
|
||||
printProperty(propName, ConversionUtilities::numberToString<intType>(value).data(), suffix, indentation);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // SYNCTHINGCTL_HELPER
|
|
@ -0,0 +1,15 @@
|
|||
#include "./application.h"
|
||||
|
||||
#include "resources/config.h"
|
||||
|
||||
#include <c++utilities/application/argumentparser.h>
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
SET_APPLICATION_INFO;
|
||||
QCoreApplication coreApp(argc, argv);
|
||||
Cli::Application cliApp;
|
||||
return cliApp.exec(argc, argv);
|
||||
}
|
|
@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR)
|
|||
# metadata
|
||||
set(META_PROJECT_NAME syncthingconnector)
|
||||
set(META_PROJECT_TYPE library)
|
||||
set(META_APP_NAME "Connection backend of Syncthing Tray")
|
||||
set(META_APP_DESCRIPTION "Connection backend of Syncthing Tray")
|
||||
set(META_PROJECT_VARNAME_UPPER LIB_SYNCTHING_CONNECTOR)
|
||||
|
||||
|
|
|
@ -30,8 +30,8 @@ namespace Data {
|
|||
*/
|
||||
QNetworkAccessManager &networkAccessManager()
|
||||
{
|
||||
static QNetworkAccessManager networkAccessManager;
|
||||
return networkAccessManager;
|
||||
static auto networkAccessManager = new QNetworkAccessManager;
|
||||
return *networkAccessManager;
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -226,24 +226,12 @@ void SyncthingConnection::reconnect()
|
|||
}
|
||||
|
||||
/*!
|
||||
* \brief Applies the specifies configuration and tries to reconnect via reconnect().
|
||||
* \brief Applies the specified configuration and tries to reconnect via reconnect().
|
||||
* \remarks The expected SSL errors of the specified configuration are updated accordingly.
|
||||
*/
|
||||
void SyncthingConnection::reconnect(SyncthingConnectionSettings &connectionSettings)
|
||||
{
|
||||
setSyncthingUrl(connectionSettings.syncthingUrl);
|
||||
setApiKey(connectionSettings.apiKey);
|
||||
if(connectionSettings.authEnabled) {
|
||||
setCredentials(connectionSettings.userName, connectionSettings.password);
|
||||
} else {
|
||||
setCredentials(QString(), QString());
|
||||
}
|
||||
setTrafficPollInterval(connectionSettings.trafficPollInterval);
|
||||
setDevStatsPollInterval(connectionSettings.devStatsPollInterval);
|
||||
loadSelfSignedCertificate();
|
||||
if(connectionSettings.expectedSslErrors.isEmpty()) {
|
||||
connectionSettings.expectedSslErrors = expectedSslErrors();
|
||||
}
|
||||
applySettings(connectionSettings);
|
||||
reconnect();
|
||||
}
|
||||
|
||||
|
@ -290,7 +278,10 @@ void SyncthingConnection::pause(const QString &devId)
|
|||
{
|
||||
QUrlQuery query;
|
||||
query.addQueryItem(QStringLiteral("device"), devId);
|
||||
QObject::connect(postData(QStringLiteral("system/pause"), query), &QNetworkReply::finished, this, &SyncthingConnection::readPauseResume);
|
||||
QNetworkReply *reply = postData(QStringLiteral("system/pause"), query);
|
||||
reply->setProperty("devId", devId);
|
||||
reply->setProperty("resume", false);
|
||||
QObject::connect(reply, &QNetworkReply::finished, this, &SyncthingConnection::readPauseResume);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -314,7 +305,10 @@ void SyncthingConnection::resume(const QString &devId)
|
|||
{
|
||||
QUrlQuery query;
|
||||
query.addQueryItem(QStringLiteral("device"), devId);
|
||||
QObject::connect(postData(QStringLiteral("system/resume"), query), &QNetworkReply::finished, this, &SyncthingConnection::readPauseResume);
|
||||
QNetworkReply *reply = postData(QStringLiteral("system/resume"), query);
|
||||
reply->setProperty("devId", devId);
|
||||
reply->setProperty("resume", true);
|
||||
QObject::connect(reply, &QNetworkReply::finished, this, &SyncthingConnection::readPauseResume);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -338,7 +332,9 @@ void SyncthingConnection::rescan(const QString &dirId)
|
|||
{
|
||||
QUrlQuery query;
|
||||
query.addQueryItem(QStringLiteral("folder"), dirId);
|
||||
QObject::connect(postData(QStringLiteral("db/scan"), query), &QNetworkReply::finished, this, &SyncthingConnection::readRescan);
|
||||
QNetworkReply *reply = postData(QStringLiteral("db/scan"), query);
|
||||
reply->setProperty("dirId", dirId);
|
||||
QObject::connect(reply, &QNetworkReply::finished, this, &SyncthingConnection::readRescan);
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -383,6 +379,7 @@ QNetworkRequest SyncthingConnection::prepareRequest(const QString &path, const Q
|
|||
url.setPassword(password());
|
||||
url.setQuery(query);
|
||||
QNetworkRequest request(url);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, QByteArray("application/x-www-form-urlencoded"));
|
||||
request.setRawHeader("X-API-Key", m_apiKey);
|
||||
return request;
|
||||
}
|
||||
|
@ -441,6 +438,23 @@ SyncthingDev *SyncthingConnection::findDevInfo(const QString &devId, int &row)
|
|||
return nullptr; // TODO: dev is unknown, trigger refreshing the config
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns the device info object for the first device with the specified name.
|
||||
* \returns Returns a pointer to the object or nullptr if not found.
|
||||
* \remarks The returned object becomes invalid when the newConfig() signal is emitted or the connection is destroyed.
|
||||
*/
|
||||
SyncthingDev *SyncthingConnection::findDevInfoByName(const QString &devName, int &row)
|
||||
{
|
||||
row = 0;
|
||||
for(SyncthingDev &d : m_devs) {
|
||||
if(d.name == devName) {
|
||||
return &d;
|
||||
}
|
||||
++row;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Continues connecting if both - config and status - have been parsed yet and continuous polling is enabled.
|
||||
*/
|
||||
|
@ -612,12 +626,18 @@ void SyncthingConnection::loadSelfSignedCertificate()
|
|||
// ensure current exceptions for self-signed certificates are cleared
|
||||
m_expectedSslErrors.clear();
|
||||
|
||||
// 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(host).isLoopback()) {
|
||||
// not required when not using secure connection
|
||||
const QUrl syncthingUrl(m_syncthingUrl);
|
||||
if(!syncthingUrl.scheme().endsWith(QChar('s'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// only possible if the Syncthing instance is running on the local machine
|
||||
const QString host(syncthingUrl.host());
|
||||
if(host.compare(QLatin1String("localhost"), Qt::CaseInsensitive) != 0 && !QHostAddress(host).isLoopback()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// find cert
|
||||
const QString certPath = !m_configDir.isEmpty() ? (m_configDir + QStringLiteral("/https-cert.pem")) : SyncthingConfig::locateHttpsCertificate();
|
||||
if(certPath.isEmpty()) {
|
||||
|
@ -637,6 +657,32 @@ void SyncthingConnection::loadSelfSignedCertificate()
|
|||
m_expectedSslErrors << QSslError(QSslError::HostNameMismatch, cert.at(0));
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Applies the specified configuration.
|
||||
* \remarks
|
||||
* - The expected SSL errors of the specified configuration are updated accordingly.
|
||||
* - The configuration is not used instantly. It will be used on the next reconnect.
|
||||
* \sa reconnect()
|
||||
*/
|
||||
void SyncthingConnection::applySettings(SyncthingConnectionSettings &connectionSettings)
|
||||
{
|
||||
setSyncthingUrl(connectionSettings.syncthingUrl);
|
||||
setApiKey(connectionSettings.apiKey);
|
||||
if(connectionSettings.authEnabled) {
|
||||
setCredentials(connectionSettings.userName, connectionSettings.password);
|
||||
} else {
|
||||
setCredentials(QString(), QString());
|
||||
}
|
||||
setTrafficPollInterval(connectionSettings.trafficPollInterval);
|
||||
setDevStatsPollInterval(connectionSettings.devStatsPollInterval);
|
||||
if(connectionSettings.expectedSslErrors.isEmpty()) {
|
||||
loadSelfSignedCertificate();
|
||||
connectionSettings.expectedSslErrors = expectedSslErrors();
|
||||
} else {
|
||||
m_expectedSslErrors = connectionSettings.expectedSslErrors;
|
||||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Reads results of requestConfig().
|
||||
*/
|
||||
|
@ -855,7 +901,6 @@ void SyncthingConnection::readConnections()
|
|||
|
||||
/*!
|
||||
* \brief Reads results of requestDirStatistics().
|
||||
* \remarks TODO
|
||||
*/
|
||||
void SyncthingConnection::readDirStatistics()
|
||||
{
|
||||
|
@ -916,7 +961,6 @@ void SyncthingConnection::readDirStatistics()
|
|||
|
||||
/*!
|
||||
* \brief Reads results of requestDeviceStatistics().
|
||||
* \remarks TODO
|
||||
*/
|
||||
void SyncthingConnection::readDeviceStatistics()
|
||||
{
|
||||
|
@ -957,6 +1001,9 @@ void SyncthingConnection::readDeviceStatistics()
|
|||
}
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Reads results of requestErrors().
|
||||
*/
|
||||
void SyncthingConnection::readErrors()
|
||||
{
|
||||
auto *reply = static_cast<QNetworkReply *>(sender());
|
||||
|
@ -1257,6 +1304,7 @@ void SyncthingConnection::readDeviceEvent(DateTime eventTime, const QString &eve
|
|||
|
||||
/*!
|
||||
* \brief Reads results of requestEvents().
|
||||
* \remarks TODO
|
||||
*/
|
||||
void SyncthingConnection::readItemStarted(DateTime eventTime, const QJsonObject &eventData)
|
||||
{
|
||||
|
@ -1305,6 +1353,7 @@ void SyncthingConnection::readRescan()
|
|||
reply->deleteLater();
|
||||
switch(reply->error()) {
|
||||
case QNetworkReply::NoError:
|
||||
emit rescanTriggered(reply->property("dirId").toString());
|
||||
break;
|
||||
default:
|
||||
emit error(tr("Unable to request rescan: ") + reply->errorString());
|
||||
|
@ -1320,6 +1369,11 @@ void SyncthingConnection::readPauseResume()
|
|||
reply->deleteLater();
|
||||
switch(reply->error()) {
|
||||
case QNetworkReply::NoError:
|
||||
if(reply->property("resume").toBool()) {
|
||||
emit resumeTriggered(reply->property("devId").toString());
|
||||
} else {
|
||||
emit pauseTriggered(reply->property("devId").toString());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
emit error(tr("Unable to request pause/resume: ") + reply->errorString());
|
||||
|
@ -1335,6 +1389,7 @@ void SyncthingConnection::readRestart()
|
|||
reply->deleteLater();
|
||||
switch(reply->error()) {
|
||||
case QNetworkReply::NoError:
|
||||
emit restartTriggered();
|
||||
break;
|
||||
default:
|
||||
emit error(tr("Unable to request restart: ") + reply->errorString());
|
||||
|
@ -1404,4 +1459,99 @@ void SyncthingConnection::emitNotification(DateTime when, const QString &message
|
|||
emit newNotification(when, message);
|
||||
}
|
||||
|
||||
/*!
|
||||
* \fn SyncthingConnection::newConfig()
|
||||
* \brief Indicates new configuration (dirs, devs, ...) is available.
|
||||
* \remarks
|
||||
* - Configuration is requested automatically when connecting.
|
||||
* - Previous directories (and directory info objects!) are invalidated.
|
||||
* - Previous devices (and device info objects!) are invalidated.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* \fn SyncthingConnection::newDirs()
|
||||
* \brief Indicates new directories are available.
|
||||
* \remarks Always emitted after newConfig() as soon as new directory info objects become available.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* \fn SyncthingConnection::newDevices()
|
||||
* \brief Indicates new devices are available.
|
||||
* \remarks Always emitted after newConfig() as soon as new device info objects become available.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* \fn SyncthingConnection::newEvents()
|
||||
* \brief Indicates new events (dir status changed, ...) are available.
|
||||
* \remarks New events are automatically polled when connected.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* \fn SyncthingConnection::dirStatusChanged()
|
||||
* \brief Indicates the status of the specified \a dir changed.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* \fn SyncthingConnection::devStatusChanged()
|
||||
* \brief Indicates the status of the specified \a dev changed.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* \fn SyncthingConnection::downloadProgressChanged()
|
||||
* \brief Indicates the download progress changed.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* \fn SyncthingConnection::newNotification()
|
||||
* \brief Indicates a new Syncthing notification is available.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* \fn SyncthingConnection::error()
|
||||
* \brief Indicates a request (for configuration, events, ...) failed.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* \fn SyncthingConnection::statusChanged()
|
||||
* \brief Indicates the status of the connection changed.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* \fn SyncthingConnection::configDirChanged()
|
||||
* \brief Indicates the Syncthing home/configuration directory changed.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* \fn SyncthingConnection::myIdChanged()
|
||||
* \brief Indicates ID of the own Syncthing device changed.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* \fn SyncthingConnection::trafficChanged()
|
||||
* \brief Indicates totalIncomingTraffic() or totalOutgoingTraffic() has changed.
|
||||
*/
|
||||
|
||||
/*!
|
||||
* \fn SyncthingConnection::rescanTriggered()
|
||||
* \brief Indicates a rescan has been triggered sucessfully.
|
||||
* \remarks Only emitted for rescans triggered internally via rescan() or rescanAll().
|
||||
*/
|
||||
|
||||
/*!
|
||||
* \fn SyncthingConnection::pauseTriggered()
|
||||
* \brief Indicates a device has been paused sucessfully.
|
||||
* \remarks Only emitted for pausing triggered internally via pause() or pauseAll().
|
||||
*/
|
||||
|
||||
/*!
|
||||
* \fn SyncthingConnection::resumeTriggered()
|
||||
* \brief Indicates a device has been resumed sucessfully.
|
||||
* \remarks Only emitted for resuming triggered internally via resume() or resumeAll().
|
||||
*/
|
||||
|
||||
/*!
|
||||
* \fn SyncthingConnection::restartTriggered()
|
||||
* \brief Indicates a restart has been successfully triggered via restart().
|
||||
*/
|
||||
|
||||
}
|
||||
|
|
|
@ -194,9 +194,13 @@ public:
|
|||
QMetaObject::Connection requestQrCode(const QString &text, std::function<void (const QByteArray &)> callback);
|
||||
QMetaObject::Connection requestLog(std::function<void (const std::vector<SyncthingLogEntry> &)> callback);
|
||||
const QList<QSslError> &expectedSslErrors();
|
||||
SyncthingDir *findDirInfo(const QString &dirId, int &row);
|
||||
SyncthingDev *findDevInfo(const QString &devId, int &row);
|
||||
SyncthingDev *findDevInfoByName(const QString &devName, int &row);
|
||||
|
||||
public Q_SLOTS:
|
||||
void loadSelfSignedCertificate();
|
||||
void applySettings(SyncthingConnectionSettings &connectionSettings);
|
||||
void connect();
|
||||
void disconnect();
|
||||
void reconnect();
|
||||
|
@ -211,77 +215,23 @@ public Q_SLOTS:
|
|||
void considerAllNotificationsRead();
|
||||
|
||||
Q_SIGNALS:
|
||||
/*!
|
||||
* \brief Indicates new configuration (dirs, devs, ...) is available.
|
||||
* \remarks
|
||||
* - Configuration is requested automatically when connecting.
|
||||
* - Previous directories (and directory info objects!) are invalidated.
|
||||
* - Previous devices (and device info objects!) are invalidated.
|
||||
*/
|
||||
void newConfig(const QJsonObject &config);
|
||||
|
||||
/*!
|
||||
* \brief Indicates new directories are available.
|
||||
* \remarks Always emitted after newConfig() as soon as new directory info objects become available.
|
||||
*/
|
||||
void newDirs(const std::vector<SyncthingDir> &dirs);
|
||||
|
||||
/*!
|
||||
* \brief Indicates new devices are available.
|
||||
* \remarks Always emitted after newConfig() as soon as new device info objects become available.
|
||||
*/
|
||||
void newDevices(const std::vector<SyncthingDev> &devs);
|
||||
|
||||
/*!
|
||||
* \brief Indicates new events (dir status changed, ...) are available.
|
||||
* \remarks New events are automatically polled when connected.
|
||||
*/
|
||||
void newEvents(const QJsonArray &events);
|
||||
|
||||
/*!
|
||||
* \brief Indicates the status of the specified \a dir changed.
|
||||
*/
|
||||
void dirStatusChanged(const SyncthingDir &dir, int index);
|
||||
|
||||
/*!
|
||||
* \brief Indicates the status of the specified \a dev changed.
|
||||
*/
|
||||
void devStatusChanged(const SyncthingDev &dev, int index);
|
||||
|
||||
/*!
|
||||
* \brief Indicates the download progress changed.
|
||||
*/
|
||||
void downloadProgressChanged();
|
||||
|
||||
/*!
|
||||
* \brief Indicates a new Syncthing notification is available.
|
||||
*/
|
||||
void newNotification(ChronoUtilities::DateTime when, const QString &message);
|
||||
|
||||
/*!
|
||||
* \brief Indicates a request (for configuration, events, ...) failed.
|
||||
*/
|
||||
void error(const QString &errorMessage);
|
||||
|
||||
/*!
|
||||
* \brief Indicates the status of the connection changed.
|
||||
*/
|
||||
void statusChanged(SyncthingStatus newStatus);
|
||||
|
||||
/*!
|
||||
* \brief Indicates the Syncthing home/configuration directory changed.
|
||||
*/
|
||||
void configDirChanged(const QString &newConfigDir);
|
||||
|
||||
/*!
|
||||
* \brief Indicates ID of the own Syncthing device changed.
|
||||
*/
|
||||
void myIdChanged(const QString &myNewId);
|
||||
|
||||
/*!
|
||||
* \brief Indicates totalIncomingTraffic() or totalOutgoingTraffic() has changed.
|
||||
*/
|
||||
void trafficChanged(int totalIncomingTraffic, int totalOutgoingTraffic);
|
||||
void rescanTriggered(const QString &dirId);
|
||||
void pauseTriggered(const QString &devId);
|
||||
void resumeTriggered(const QString &devId);
|
||||
void restartTriggered();
|
||||
|
||||
private Q_SLOTS:
|
||||
void requestConfig();
|
||||
|
@ -322,8 +272,6 @@ private:
|
|||
QNetworkRequest prepareRequest(const QString &path, const QUrlQuery &query, bool rest = true);
|
||||
QNetworkReply *requestData(const QString &path, const QUrlQuery &query, bool rest = true);
|
||||
QNetworkReply *postData(const QString &path, const QUrlQuery &query, const QByteArray &data = QByteArray());
|
||||
SyncthingDir *findDirInfo(const QString &dirId, int &row);
|
||||
SyncthingDev *findDevInfo(const QString &devId, int &row);
|
||||
|
||||
QString m_syncthingUrl;
|
||||
QByteArray m_apiKey;
|
||||
|
@ -536,8 +484,8 @@ inline const std::vector<SyncthingDev> &SyncthingConnection::devInfo() const
|
|||
}
|
||||
|
||||
/*!
|
||||
* \brief Returns a list of all expected certificate errors.
|
||||
* \remarks This list is shared by all instances and updated via loadSelfSignedCertificate().
|
||||
* \brief Returns a list of all expected certificate errors. This is meant to allow self-signed certificates.
|
||||
* \remarks This list is updated via loadSelfSignedCertificate().
|
||||
*/
|
||||
inline const QList<QSslError> &SyncthingConnection::expectedSslErrors()
|
||||
{
|
||||
|
|
|
@ -4,12 +4,12 @@ namespace Data {
|
|||
|
||||
bool SyncthingConnectionSettings::loadHttpsCert()
|
||||
{
|
||||
expectedSslErrors.clear();
|
||||
if(!httpsCertPath.isEmpty()) {
|
||||
const QList<QSslCertificate> cert = QSslCertificate::fromPath(httpsCertPath);
|
||||
if(cert.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
expectedSslErrors.clear();
|
||||
expectedSslErrors.reserve(4);
|
||||
expectedSslErrors << QSslError(QSslError::UnableToGetLocalIssuerCertificate, cert.at(0));
|
||||
expectedSslErrors << QSslError(QSslError::UnableToVerifyFirstCertificate, cert.at(0));
|
||||
|
|
|
@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR)
|
|||
# metadata
|
||||
set(META_PROJECT_NAME syncthingmodel)
|
||||
set(META_PROJECT_TYPE library)
|
||||
set(META_APP_NAME "Data models of Syncthing Tray")
|
||||
set(META_APP_DESCRIPTION "Data models of Syncthing Tray")
|
||||
set(META_PROJECT_VARNAME_UPPER LIB_SYNCTHING_MODEL)
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ cmake_minimum_required(VERSION 3.1.0 FATAL_ERROR)
|
|||
# metadata
|
||||
set(META_PROJECT_TYPE application)
|
||||
set(META_GUI_OPTIONAL false)
|
||||
set(META_APP_NAME "Syncthing Tray")
|
||||
|
||||
# add project files
|
||||
set(WIDGETS_HEADER_FILES
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
../README.md
|
|
@ -18,6 +18,7 @@
|
|||
#include <qtutilities/settingsdialog/qtsettings.h>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QMessageBox>
|
||||
|
||||
#include <iostream>
|
||||
|
@ -94,6 +95,7 @@ int runApplication(int argc, const char *const *argv)
|
|||
QApplication application(argc, const_cast<char **>(argv));
|
||||
QGuiApplication::setQuitOnLastWindowClosed(false);
|
||||
SingleInstance singleInstance(argc, argv);
|
||||
networkAccessManager().setParent(&singleInstance);
|
||||
QObject::connect(&singleInstance, &SingleInstance::newInstance, &runApplication);
|
||||
|
||||
Settings::restore();
|
||||
|
@ -125,7 +127,8 @@ int runApplication(int argc, const char *const *argv)
|
|||
}
|
||||
} catch(const Failure &ex) {
|
||||
CMD_UTILS_START_CONSOLE;
|
||||
cout << "Unable to parse arguments. " << ex.what() << "\nSee --help for available commands." << endl;
|
||||
cerr << "Unable to parse arguments. " << ex.what() << "\nSee --help for available commands." << endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
Loading…
Reference in New Issue