1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308
|
#include "./utils.h"
#include "./syncthingconnection.h"
#include <qtutilities/misc/compat.h>
#include <c++utilities/chrono/datetime.h>
#include <c++utilities/conversion/stringconversion.h>
#include <c++utilities/io/ansiescapecodes.h>
#include <QCoreApplication>
#include <QHostAddress>
#include <QJsonArray>
#include <QJsonObject>
#include <QJsonValue>
#include <QNetworkInterface>
#include <QString>
#include <QStringBuilder>
#include <QUrl>
#ifdef SYNCTHINGCONNECTION_SUPPORT_METERED
#include <QNetworkInformation>
#ifdef Q_OS_ANDROID
#include <QDebug>
#endif
#endif
#include <iostream>
using namespace CppUtilities;
namespace Data {
/*!
* \brief Returns a string like "2 min 45 s ago" for the specified \a dateTime.
*/
QString agoString(DateTime dateTime)
{
const TimeSpan delta(DateTime::now() - dateTime);
if (!delta.isNegative() && static_cast<std::uint64_t>(delta.totalTicks()) > (TimeSpan::ticksPerMinute / 4uL)) {
return QCoreApplication::translate("Data::Utils", "%1 ago")
.arg(QString::fromUtf8(delta.toString(TimeSpanOutputFormat::WithMeasures, true).data()));
} else {
return QCoreApplication::translate("Data::Utils", "right now");
}
}
/*!
* \brief Returns the "traffic string" for the specified \a total bytes and the specified \a rate.
*
* Eg. "10.2 GiB (45 kib/s)" or only "10.2 GiB" if rate is unknown or "unknown" if both values are unknown.
*/
QString trafficString(std::uint64_t total, double rate)
{
static const QString unknownStr(QCoreApplication::translate("Data::Utils", "unknown"));
if (rate != 0.0) {
return total != SyncthingConnection::unknownTraffic
? QStringLiteral("%1 (%2)").arg(QString::fromUtf8(bitrateToString(rate, true).data()), QString::fromUtf8(dataSizeToString(total).data()))
: QString::fromUtf8(bitrateToString(rate, true).data());
} else if (total != SyncthingConnection::unknownTraffic) {
return QString::fromUtf8(dataSizeToString(total).data());
}
return unknownStr;
}
/*!
* \brief Returns the string for global/local directory status, eg. "5 files, 1 directory, 23.7 MiB".
*/
QString directoryStatusString(const SyncthingStatistics &stats)
{
return QCoreApplication::translate("Data::Utils", "%1 file(s)", nullptr, trQuandity(stats.files)).arg(stats.files) % QChar(',') % QChar(' ')
% QCoreApplication::translate("Data::Utils", "%1 dir(s)", nullptr, trQuandity(stats.dirs)).arg(stats.dirs) % QChar(',') % QChar(' ')
% QString::fromUtf8(dataSizeToString(stats.bytes).data());
}
/*!
* \brief Returns the "sync complete" notification message for the specified directories.
*/
QString syncCompleteString(const std::vector<const SyncthingDir *> &completedDirs, const SyncthingDev *remoteDevice)
{
const auto devName(remoteDevice ? remoteDevice->displayName() : QString());
switch (completedDirs.size()) {
case 0:
return QString();
case 1:
if (devName.isEmpty()) {
return QCoreApplication::translate("Data::Utils", "Synchronization of local folder %1 complete")
.arg(completedDirs.front()->displayName());
}
return QCoreApplication::translate("Data::Utils", "Synchronization of %1 on %2 complete").arg(completedDirs.front()->displayName(), devName);
default:;
}
const auto names(things(completedDirs, [](const auto *dir) { return dir->displayName(); }));
if (devName.isEmpty()) {
return QCoreApplication::translate("Data::Utils", "Synchronization of the following local folders complete:\n")
+ names.join(QStringLiteral(", "));
}
return QCoreApplication::translate("Data::Utils", "Synchronization of the following folders on %1 complete:\n").arg(devName)
+ names.join(QStringLiteral(", "));
}
/*!
* \brief Returns the string representation of the specified \a rescanInterval.
*/
QString rescanIntervalString(int rescanInterval, bool fileSystemWatcherEnabled)
{
if (!rescanInterval) {
if (!fileSystemWatcherEnabled) {
return QCoreApplication::translate("Data::Utils", "file system watcher and periodic rescan disabled");
}
return QCoreApplication::translate("Data::Utils", "file system watcher active, periodic rescan disabled");
}
return QString::fromLatin1(TimeSpan::fromSeconds(rescanInterval).toString(TimeSpanOutputFormat::WithMeasures, true).data())
+ (fileSystemWatcherEnabled ? QCoreApplication::translate("Data::Utils", ", file system watcher enabled")
: QCoreApplication::translate("Data::Utils", ", file system watcher disabled"));
}
/*!
* \brief Strips the port from the specified \a address.
*/
QString stripPort(const QString &address)
{
const auto portStart = address.lastIndexOf(QChar(':'));
return portStart < 0 ? address : address.mid(0, portStart);
}
/*!
* \brief Returns whether the specified \a hostName is the local machine.
*/
bool isLocal(const QString &hostName)
{
return isLocal(hostName, QHostAddress(hostName));
}
/*!
* \brief Returns whether the specified \a hostName and \a hostAddress are the local machine.
*/
bool isLocal(const QString &hostName, const QHostAddress &hostAddress)
{
return hostName.compare(QLatin1String("localhost"), Qt::CaseInsensitive) == 0 || hostAddress.isLoopback()
#ifndef QT_NO_NETWORKINTERFACE
|| QNetworkInterface::allAddresses().contains(hostAddress)
#endif
;
}
/*!
* \brief Sets the key "paused" of the specified \a object to \a paused.
* \returns Returns whether object has been altered.
*/
bool setPausedValue(QJsonObject &object, bool paused)
{
const QJsonObject::Iterator pausedIterator(object.find(QLatin1String("paused")));
if (pausedIterator == object.end()) {
object.insert(QLatin1String("paused"), paused);
} else {
QJsonValueRef pausedValue = pausedIterator.value();
if (pausedValue.toBool(false) == paused) {
return false;
}
pausedValue = paused;
}
return true;
}
/*!
* \brief Alters the specified \a syncthingConfig so that the dirs with specified IDs are paused or not.
* \returns Returns whether the config has been altered (all dirs might have been already paused/unpaused).
*/
bool setDirectoriesPaused(QJsonObject &syncthingConfig, const QStringList &dirIds, bool paused)
{
// get reference to folders array
const QJsonObject::Iterator foldersIterator(syncthingConfig.find(QLatin1String("folders")));
if (foldersIterator == syncthingConfig.end()) {
return false;
}
QJsonValueRef folders = foldersIterator.value();
if (!folders.isArray()) {
return false;
}
// alter folders
bool altered = false;
QJsonArray foldersArray = folders.toArray();
for (QJsonValueRef folder : foldersArray) {
QJsonObject folderObj = folder.toObject();
// skip devices not matching the specified IDs or are already paused/unpaused
if (!dirIds.isEmpty() && !dirIds.contains(folderObj.value(QLatin1String("id")).toString())) {
continue;
}
// alter paused value
if (setPausedValue(folderObj, paused)) {
folder = folderObj;
altered = true;
}
}
// re-assign altered folders to array reference
if (altered) {
folders = foldersArray;
}
return altered;
}
/*!
* \brief Alters the specified \a syncthingConfig so that the devs with the specified IDs are paused or not.
* \returns Returns whether the config has been altered (all devs might have been already paused/unpaused).
*/
bool setDevicesPaused(QJsonObject &syncthingConfig, const QStringList &devIds, bool paused)
{
// get reference to devices array
const QJsonObject::Iterator devicesIterator(syncthingConfig.find(QLatin1String("devices")));
if (devicesIterator == syncthingConfig.end()) {
return false;
}
QJsonValueRef devices = devicesIterator.value();
if (!devices.isArray()) {
return false;
}
// alter devices
bool altered = false;
QJsonArray devicesArray = devices.toArray();
for (QJsonValueRef device : devicesArray) {
QJsonObject deviceObj = device.toObject();
// skip devices not matching the specified IDs
if (!devIds.isEmpty() && !devIds.contains(deviceObj.value(QLatin1String("deviceID")).toString())) {
continue;
}
// alter paused value
if (setPausedValue(deviceObj, paused)) {
device = deviceObj;
altered = true;
}
}
// re-assign altered devices to array reference
if (altered) {
devices = devicesArray;
}
return altered;
}
/*!
* \brief Substitutes "~" as first element in \a path with \a tilde assuming the elements in \a path
* are separated by \a pathSeparator.
*/
QString substituteTilde(const QString &path, const QString &tilde, const QString &pathSeparator)
{
if (tilde.isEmpty() || pathSeparator.isEmpty() || !path.startsWith(QChar('~'))) {
return path;
}
if (path.size() < 2) {
return tilde;
}
if (QtUtilities::midRef(path, 1).startsWith(pathSeparator)) {
return tilde % pathSeparator % QtUtilities::midRef(path, 1 + pathSeparator.size());
}
return path;
}
#ifdef SYNCTHINGCONNECTION_SUPPORT_METERED
/*!
* \brief Loads the QNetworkInformation backend for determining whether the connection is metered.
*/
const QNetworkInformation *loadNetworkInformationBackendForMetered()
{
static const auto *const backend = []() -> const QNetworkInformation * {
#ifdef Q_OS_ANDROID
// load the network information plugin under Android by its name because it doesn't advertise supporting detection of metered
// connections even though it supports it (at least as of Qt 6.8.0)
constexpr auto expectedFeatures = QNetworkInformation::Feature::TransportMedium;
QNetworkInformation::loadBackendByName(QStringLiteral("android"));
#else
constexpr auto expectedFeatures = QNetworkInformation::Feature::Metered;
QNetworkInformation::loadBackendByFeatures(expectedFeatures);
#endif
if (const auto *const networkInformation = QNetworkInformation::instance();
networkInformation && networkInformation->supports(expectedFeatures)) {
return networkInformation;
}
#ifdef Q_OS_ANDROID
qDebug() << "Unable to load network information backend, available backends: " << QNetworkInformation::availableBackends();
#else
std::cerr << EscapeCodes::Phrases::Error
<< "Unable to load network information backend to monitor metered connections, available backends:" << EscapeCodes::Phrases::End;
const auto availableBackends = QNetworkInformation::availableBackends();
if (availableBackends.isEmpty()) {
std::cerr << "none\n";
} else {
for (const auto &backendName : availableBackends) {
std::cerr << " - " << backendName.toStdString() << '\n';
}
}
#endif
return nullptr;
}();
return backend;
}
#endif
} // namespace Data
|