diff --git a/cli/application.cpp b/cli/application.cpp index 45386ce..5f854bf 100644 --- a/cli/application.cpp +++ b/cli/application.cpp @@ -12,6 +12,7 @@ #include #include +#include #include #include @@ -46,7 +47,9 @@ inline QString argToQString(const char *arg, int size = -1) } Application::Application() : - m_expectedResponse(0) + m_expectedResponse(0), + m_preventDisconnect(false), + m_callbacksInvoked(false) { // take ownership over the global QNetworkAccessManager networkAccessManager().setParent(this); @@ -66,6 +69,7 @@ Application::Application() : m_args.resumeAllDevs.setCallback(bind(&Application::requestResumeAllDevs, this, _1)); m_args.resumeAllDirs.setCallback(bind(&Application::requestResumeAllDirs, this, _1)); m_args.waitForIdle.setCallback(bind(&Application::initWaitForIdle, this, _1)); + m_args.pwd.setCallback(bind(&Application::operateOnPwd, this, _1)); // connect signals and slots connect(&m_connection, &SyncthingConnection::statusChanged, this, &Application::handleStatusChanged); @@ -143,7 +147,7 @@ int Application::exec(int argc, const char * const *argv) // finally to request / establish connection if(m_args.status.isPresent() || m_args.rescanAll.isPresent() || m_args.pauseAllDirs.isPresent() || m_args.pauseAllDevs.isPresent() || m_args.resumeAllDirs.isPresent() || m_args.resumeAllDevs.isPresent() || m_args.pause.isPresent() - || m_args.resume.isPresent() || m_args.waitForIdle.isPresent()) { + || m_args.resume.isPresent() || m_args.waitForIdle.isPresent() || m_args.pwd.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); @@ -167,11 +171,15 @@ int Application::exec(int argc, const char * const *argv) void Application::handleStatusChanged(SyncthingStatus newStatus) { Q_UNUSED(newStatus) + if(m_callbacksInvoked) { + return; + } if(m_connection.isConnected()) { eraseLine(cout); cout << '\r'; + m_callbacksInvoked = true; m_args.parser.invokeCallbacks(); - if(!m_args.waitForIdle.isPresent()) { + if(!m_preventDisconnect) { m_connection.disconnect(); } } @@ -203,7 +211,7 @@ void Application::findRelevantDirsAndDevs() void Application::requestLog(const ArgumentOccurrence &) { - m_connection.requestLog(bind(&Application::printLog, this, _1)); + m_connection.requestLog(&Application::printLog); cerr << "Request log from " << m_settings.syncthingUrl.toLocal8Bit().data() << " ..."; cerr.flush(); } @@ -374,6 +382,59 @@ void Application::findRelevantDirsAndDevs(OperationType operationType) } } +void Application::printDir(const SyncthingDir *dir) +{ + cout << " - "; + setStyle(cout, TextAttribute::Bold); + cout << dir->id.toLocal8Bit().data() << '\n'; + setStyle(cout); + printProperty("Label", dir->label); + printProperty("Path", dir->path); + printProperty("Status", dir->statusString()); + 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); + if(!dir->errors.empty()) { + cout << " Errors\n"; + for(const SyncthingDirError &error : dir->errors) { + printProperty(" - Message", error.message); + printProperty(" File", error.path); + } + } + cout << '\n'; +} + +void Application::printDev(const SyncthingDev *dev) +{ + cout << " - "; + setStyle(cout, TextAttribute::Bold); + cout << dev->name.toLocal8Bit().data() << '\n'; + setStyle(cout); + printProperty("ID", dev->id); + printProperty("Status", dev->statusString()); + 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(dev->totalIncomingTraffic)).data()); + } + if(dev->totalOutgoingTraffic > 0) { + printProperty("Outgoing traffic", dataSizeToString(static_cast(dev->totalOutgoingTraffic)).data()); + } + cout << '\n'; +} + void Application::printStatus(const ArgumentOccurrence &) { findRelevantDirsAndDevs(); @@ -383,33 +444,7 @@ void Application::printStatus(const ArgumentOccurrence &) setStyle(cout, TextAttribute::Bold); cout << "Directories\n"; setStyle(cout); - for(const SyncthingDir *dir : m_relevantDirs) { - cout << " - "; - setStyle(cout, TextAttribute::Bold); - cout << dir->id.toLocal8Bit().data() << '\n'; - setStyle(cout); - printProperty("Label", dir->label); - printProperty("Path", dir->path); - printProperty("Status", dir->statusString()); - 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); - if(!dir->errors.empty()) { - cout << " Errors\n"; - for(const SyncthingDirError &error : dir->errors) { - printProperty(" - Message", error.message); - printProperty(" File", error.path); - } - } - cout << '\n'; - } + for_each(m_relevantDirs.cbegin(), m_relevantDirs.cend(), &Application::printDir); } // display devs @@ -417,28 +452,7 @@ void Application::printStatus(const ArgumentOccurrence &) setStyle(cout, TextAttribute::Bold); cout << "Devices\n"; setStyle(cout); - for(const SyncthingDev *dev : m_relevantDevs) { - cout << " - "; - setStyle(cout, TextAttribute::Bold); - cout << dev->name.toLocal8Bit().data() << '\n'; - setStyle(cout); - printProperty("ID", dev->id); - printProperty("Status", dev->statusString()); - 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(dev->totalIncomingTraffic)).data()); - } - if(dev->totalOutgoingTraffic > 0) { - printProperty("Outgoing traffic", dataSizeToString(static_cast(dev->totalOutgoingTraffic)).data()); - } - cout << '\n'; - } + for_each(m_relevantDevs.cbegin(), m_relevantDevs.cend(), &Application::printDev); } cout.flush(); @@ -459,6 +473,8 @@ void Application::printLog(const std::vector &logEntries) void Application::initWaitForIdle(const ArgumentOccurrence &) { + m_preventDisconnect = true; + findRelevantDirsAndDevs(); // might idle already @@ -499,4 +515,67 @@ void Application::waitForIdle() QCoreApplication::exit(); } +void Application::operateOnPwd(const ArgumentOccurrence &occurrence) +{ + // find SyncthingDir for pwd + const QString pwd(QDir::currentPath()); + const SyncthingDir *relatedDir = nullptr; + QString relativePath; + for(const SyncthingDir &dir : m_connection.dirInfo()) { + if(pwd == dir.pathWithoutTrailingSlash()) { + relatedDir = &dir; + } else if(pwd.startsWith(dir.path)) { + relatedDir = &dir; + relativePath = pwd.mid(dir.path.size()); + } + } + if(!relatedDir) { + cerr << "Error: The current working directory \"" << pwd.toLocal8Bit().data() << "\" is not (part of) a Syncthing directory." << endl; + QCoreApplication::exit(2); + return; + } + + // do specified operation + const char *operation = occurrence.values.front(); + if(!strcmp(operation, "status")) { + printDir(relatedDir); + } else if(!strcmp(operation, "rescan")) { + if(relativePath.isEmpty()) { + cerr << "Request rescanning directory \"" << relatedDir->path.toLocal8Bit().data() << "\" ..." << endl; + } else { + cerr << "Request rescanning item \"" << relativePath.toLocal8Bit().data() << "\" in directory \"" << relatedDir->path.toLocal8Bit().data() << "\" ..." << endl; + } + m_connection.rescan(relatedDir->id, relativePath); + connect(&m_connection, &SyncthingConnection::rescanTriggered, this, &Application::handleResponse); + m_expectedResponse = 1; + return; + } else if(!strcmp(operation, "pause")) { + if(m_connection.pauseDirectories(QStringList(relatedDir->id))) { + cerr << "Request pausing directory \"" << relatedDir->path.toLocal8Bit().data() << "\" ..." << endl; + connect(&m_connection, &SyncthingConnection::directoryPauseTriggered, this, &Application::handleResponse); + m_preventDisconnect = true; + m_expectedResponse = 1; + return; + } else { + cerr << "Directory \"" << relatedDir->path.toLocal8Bit().data() << " already paused" << endl; + } + } else if(!strcmp(operation, "resume")) { + if(m_connection.resumeDirectories(QStringList(relatedDir->id))) { + cerr << "Request resuming directory \"" << relatedDir->path.toLocal8Bit().data() << "\" ..." << endl; + connect(&m_connection, &SyncthingConnection::directoryResumeTriggered, this, &Application::handleResponse); + m_preventDisconnect = true; + m_expectedResponse = 1; + return; + } else { + cerr << "Directory \"" << relatedDir->path.toLocal8Bit().data() << " not paused" << endl; + } + } else { + cerr << "Error: The specified operation \"" << operation << "\" is invalid." << endl; + QCoreApplication::exit(1); + return; + } + + QCoreApplication::quit(); +} + } // namespace Cli diff --git a/cli/application.h b/cli/application.h index 9f6e144..f250782 100644 --- a/cli/application.h +++ b/cli/application.h @@ -46,15 +46,20 @@ private: void requestPauseAllDirs(const ArgumentOccurrence &); void requestResumeAllDevs(const ArgumentOccurrence &); void requestResumeAllDirs(const ArgumentOccurrence &); + static void printDir(const Data::SyncthingDir *dir); + static void printDev(const Data::SyncthingDev *dev); void printStatus(const ArgumentOccurrence &); - void printLog(const std::vector &logEntries); + static void printLog(const std::vector &logEntries); void initWaitForIdle(const ArgumentOccurrence &); void waitForIdle(); + void operateOnPwd(const ArgumentOccurrence &occurrence); Args m_args; Data::SyncthingConnectionSettings m_settings; Data::SyncthingConnection m_connection; size_t m_expectedResponse; + bool m_preventDisconnect; + bool m_callbacksInvoked; std::vector m_relevantDirs; std::vector m_relevantDevs; diff --git a/cli/args.cpp b/cli/args.cpp index 55b4b1e..eb4e647 100644 --- a/cli/args.cpp +++ b/cli/args.cpp @@ -17,6 +17,7 @@ Args::Args() : resumeAllDevs("resume-all-devs", '\0', "resumes all devices"), resumeAllDirs("resume-all-dirs", '\0', "resumes all directories"), waitForIdle("wait-for-idle", 'w', "waits until the specified dirs/devs are idling"), + pwd("pwd", 'p', "operates in the current working directory"), statusDir("dir", 'd', "specifies the directoies (default is all dirs)", {"ID"}), statusDev("dev", '\0', "specifies the devices (default is all devs)", {"ID"}), pauseDir("dir", 'd', "specifies the directories", {"ID"}), @@ -32,6 +33,8 @@ Args::Args() : } status.setSubArguments({&statusDir, &statusDev}); waitForIdle.setSubArguments({&statusDir, &statusDev}); + pwd.setValueNames({"status/rescan/pause/resume"}); + pwd.setRequiredValueCount(1); rescan.setValueNames({"dir ID"}); rescan.setRequiredValueCount(-1); @@ -39,7 +42,7 @@ Args::Args() : resume.setSubArguments({&pauseDir, &pauseDev}); parser.setMainArguments({&status, &log, &stop, &restart, &rescan, &rescanAll, &pause, &pauseAllDevs, &pauseAllDirs, &resume, &resumeAllDevs, - &resumeAllDirs, &waitForIdle, &configFile, &apiKey, &url, &credentials, &certificate, &help}); + &resumeAllDirs, &waitForIdle, &pwd, &configFile, &apiKey, &url, &credentials, &certificate, &help}); // allow setting default values via environment configFile.setEnvironmentVariable("SYNCTHING_CTL_CONFIG_FILE"); diff --git a/cli/args.h b/cli/args.h index 10cbfea..187c5ff 100644 --- a/cli/args.h +++ b/cli/args.h @@ -12,7 +12,7 @@ struct Args Args(); ArgumentParser parser; HelpArgument help; - OperationArgument status, log, stop, restart, rescan, rescanAll, pause, pauseAllDevs, pauseAllDirs, resume, resumeAllDevs, resumeAllDirs, waitForIdle; + OperationArgument status, log, stop, restart, rescan, rescanAll, pause, pauseAllDevs, pauseAllDirs, resume, resumeAllDevs, resumeAllDirs, waitForIdle, pwd; ConfigValueArgument statusDir, statusDev, pauseDir, pauseDev; ConfigValueArgument configFile, apiKey, url, credentials, certificate; }; diff --git a/connector/syncthingdir.cpp b/connector/syncthingdir.cpp index bdb1a10..36e5986 100644 --- a/connector/syncthingdir.cpp +++ b/connector/syncthingdir.cpp @@ -130,6 +130,19 @@ QString SyncthingDir::statusString() const } } +QStringRef SyncthingDir::pathWithoutTrailingSlash() const +{ + QStringRef dirPath(&path); + while(dirPath.endsWith(QChar('/'))) { +#if QT_VERSION_MAJOR >= 5 && QT_VERSION_MINOR >= 8 + dirPath.chop(1); +#else + dirPath = dirPath.left(dirPath.size() - 1); +#endif + } + return dirPath; +} + SyncthingItemDownloadProgress::SyncthingItemDownloadProgress(const QString &containingDirPath, const QString &relativeItemPath, const QJsonObject &values) : relativePath(relativeItemPath), fileInfo(containingDirPath % QChar('/') % QString(relativeItemPath).replace(QChar('\\'), QChar('/'))), diff --git a/connector/syncthingdir.h b/connector/syncthingdir.h index e29e326..44aaee1 100644 --- a/connector/syncthingdir.h +++ b/connector/syncthingdir.h @@ -66,6 +66,7 @@ struct LIB_SYNCTHING_CONNECTOR_EXPORT SyncthingDir bool assignStatus(SyncthingDirStatus newStatus, ChronoUtilities::DateTime time); QString displayName() const; QString statusString() const; + QStringRef pathWithoutTrailingSlash() const; QString id; QString label; diff --git a/fileitemactionplugin/syncthingfileitemaction.cpp b/fileitemactionplugin/syncthingfileitemaction.cpp index fa5c390..3fbedc7 100644 --- a/fileitemactionplugin/syncthingfileitemaction.cpp +++ b/fileitemactionplugin/syncthingfileitemaction.cpp @@ -278,14 +278,7 @@ QList SyncthingFileItemAction::createActions(const KFileItemListPrope QList detectedItems; const SyncthingDir *lastDir; for(const SyncthingDir &dir : dirs) { - QStringRef dirPath(&dir.path); - while(dirPath.endsWith(QChar('/'))) { -#if QT_VERSION_MAJOR >= 5 && QT_VERSION_MINOR >= 8 - dirPath.chop(1); -#else - dirPath = dirPath.left(dirPath.size() - 1); -#endif - } + QStringRef dirPath(dir.pathWithoutTrailingSlash()); for(const QString &path : paths) { if(path == dirPath) { lastDir = &dir; diff --git a/fileitemactionplugin/translations/syncthingfileitemaction_de_DE.ts b/fileitemactionplugin/translations/syncthingfileitemaction_de_DE.ts index 4194b4c..bb10ea9 100644 --- a/fileitemactionplugin/translations/syncthingfileitemaction_de_DE.ts +++ b/fileitemactionplugin/translations/syncthingfileitemaction_de_DE.ts @@ -8,27 +8,27 @@ nicht mehr verfügbar - + Status: not available anymore Status: nicht mehr verfügbar - + Directory info for %1 Verzeichnisinfo für %1 - + Status: Status: - + Last scan time: Letzter Scan: - + Rescan interval: %1 seconds Scanintervall: %1 Sekunden @@ -36,60 +36,65 @@ SyncthingFileItemAction - + Rescan %1 (in %2) "%1" neu scannen (in "%2") - + Rescan selected items Auswahl neu scannen - + Rescan selected directories Ausgewählte Verzeichnisse neu scannen - + Resume selected directories Ausgewählte verzeichnisse fortsetzen - - + + Rescan %1 "%1" neu scannen - - + + Syncthing connection error + + + + + Resume %1 "%1" fortsetzen - - + + Pause %1 "%1" pausieren - + Pause selected directories Ausgewählte Verzeichnisse pausieren - + Rescan containing directories Beinhaltendes Verzeichnis neu scannen - + Resume containing directories Beinhaltendes Verzeichnis fortsetzen - + Pause containing directories Beinhaltendes Verzeichnis pausieren @@ -106,8 +111,8 @@ Scanintervall: %1 Sekunden - - + + About Über @@ -115,12 +120,12 @@ SyncthingMenuAction - + Syncthing - + Syncthing - connecting Syncthing - verbinde diff --git a/fileitemactionplugin/translations/syncthingfileitemaction_en_US.ts b/fileitemactionplugin/translations/syncthingfileitemaction_en_US.ts index e0cfe31..119192a 100644 --- a/fileitemactionplugin/translations/syncthingfileitemaction_en_US.ts +++ b/fileitemactionplugin/translations/syncthingfileitemaction_en_US.ts @@ -4,27 +4,27 @@ SyncthingDirActions - + Status: not available anymore - + Directory info for %1 - + Status: - + Last scan time: - + Rescan interval: %1 seconds @@ -32,66 +32,71 @@ SyncthingFileItemAction - + Rescan %1 (in %2) - + Rescan selected items - + Rescan selected directories - + Resume selected directories - - + + Rescan %1 - - + + Syncthing connection error + + + + + Resume %1 - - + + Pause %1 - + Pause selected directories - + Rescan containing directories - + Resume containing directories - + Pause containing directories - - + + About @@ -99,12 +104,12 @@ SyncthingMenuAction - + Syncthing - + Syncthing - connecting