summaryrefslogtreecommitdiff
path: root/distfoldd/agent.cc
diff options
context:
space:
mode:
Diffstat (limited to 'distfoldd/agent.cc')
-rw-r--r--distfoldd/agent.cc337
1 files changed, 337 insertions, 0 deletions
diff --git a/distfoldd/agent.cc b/distfoldd/agent.cc
new file mode 100644
index 0000000..a718230
--- /dev/null
+++ b/distfoldd/agent.cc
@@ -0,0 +1,337 @@
+#include "agent.h"
+
+#ifdef Q_OS_UNIX
+#include <sys/time.h>
+#include <utime.h>
+#endif
+
+Agent::Agent(QSslSocket *socket, const QDir& dir, SyncFlags flags, QObject *parent) :
+ QObject(parent), _dir(dir), _subPath("/"), _flags(flags), _socket(socket)
+{
+ connect(_socket, SIGNAL(readyRead()), SLOT(handleDataAvailable()));
+ connect(_socket, SIGNAL(sslErrors(QList<QSslError>)), SLOT(handleSslErrors(QList<QSslError>)));
+ connect(_socket, SIGNAL(disconnected()), SLOT(handleDisconnected()));
+}
+
+Agent::RemoteFileInfo::RemoteFileInfo() :
+ _name(), _type(FILE_TYPE_NONE), _mtime(), _size(0)
+{
+}
+
+Agent::RemoteFileInfo::RemoteFileInfo(const QString &name, FileType type, const QDateTime &mtime, qint64 size) :
+ _name(name), _type(type), _mtime(mtime), _size(size)
+{
+}
+
+Agent::ActionInfo::ActionInfo(FileAction action, const QFileInfo &fileInfo) :
+ _action(action), _info(fileInfo)
+{
+}
+
+Agent::RemoteActionInfo::RemoteActionInfo(FileAction action, const QString &name, FileType type, const QDateTime &mtime, qint64 size) :
+ _action(action), _info(name, type, mtime, size)
+{
+
+}
+
+void Agent::sendMessage(MessageType msg, const QByteArray &content)
+{
+ const int header_len = sizeof(MessageHeader);
+ QByteArray ba(header_len + content.size(), '\0');
+
+ MessageHeader *h = reinterpret_cast<MessageHeader*>(ba.data());
+ h->msg = msg;
+ h->len = content.size();
+ if (h->len > 0) {
+ ba.replace(header_len, content.size(), content);
+ }
+ Q_ASSERT(ba.size() == header_len + content.size());
+
+ qDebug() << "Sending " << msg << content.size();
+
+ _socket->write(ba);
+}
+
+int Agent::inBufRequiredData() const
+{
+ const int header_len = sizeof(MessageHeader);
+ if (_inBuf.size() < header_len) {
+ return header_len;
+ } else {
+ const MessageHeader *h;
+ h = reinterpret_cast<const MessageHeader*>(_inBuf.data());
+ return header_len + h->len;
+ }
+}
+
+bool Agent::equalDateTime(const QDateTime& dt1, const QDateTime& dt2)
+{
+ const qint64 threshold = 5 * 1000;
+ qint64 msecs = dt1.msecsTo(dt2);
+ return msecs > -threshold && msecs < threshold;
+}
+
+void Agent::setLocalFileDateTime(const QString &path, const QDateTime &dt)
+{
+#ifdef Q_OS_UNIX
+ const char *filename = path.toLocal8Bit().constData();
+ struct utimbuf times;
+ times.actime = dt.toTime_t();
+ times.modtime = dt.toTime_t();
+ int rc = utime(filename, &times);
+ if (rc != 0) {
+ qWarning() << "Could not set local mtime of" << path;
+ }
+#endif
+}
+
+QString Agent::wireToLocalPath(const QString &path)
+{
+ return _dir.absolutePath() + _subPath + path;
+}
+
+QString Agent::localToWirePath(const QString& path)
+{
+ const QString& basePath = _dir.absolutePath() + _subPath;
+ Q_ASSERT(_subPath.endsWith('/'));
+ QString wire_path(path);
+ if (path.startsWith(basePath)) {
+ wire_path.remove(0, basePath.size());
+ } else if (path + '/' == basePath) {
+ wire_path = QString(); // This is the root dir.
+ } else {
+ qWarning() << "Where does this come from?" << path;
+ }
+ return wire_path;
+}
+
+QString Agent::wireParentPath(const QString &path)
+{
+ int index = path.lastIndexOf('/');
+ if (index == -1) return QString("");
+ else return path.left(index);
+}
+
+QString Agent::findExistingCommonAncestor(const QString& path,
+ const QHash<QString, QFileInfo>& local_files,
+ const QHash<QString, RemoteFileInfo>& remote_files)
+{
+ QString ancestor = path;
+ do {
+ ancestor = wireParentPath(ancestor);
+ } while (!local_files.contains(ancestor) || !remote_files.contains(ancestor));
+ return ancestor;
+}
+
+bool Agent::lessPathDepthThan(const QString& s1, const QString& s2)
+{
+ int d1 = s1.count('/'), d2 = s2.count('/');
+ return d1 < d2;
+}
+
+bool Agent::morePathDepthThan(const QString& s1, const QString& s2)
+{
+ int d1 = s1.count('/'), d2 = s2.count('/');
+ return d1 > d2;
+}
+
+QFileInfoList Agent::scanFiles(const QDir& dir)
+{
+ QFileInfoList all;
+ QFileInfoList sub = dir.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
+
+ all << QFileInfo(dir, ".");
+
+ foreach (const QFileInfo &info, sub) {
+ if (info.isDir()) {
+ all << scanFiles(QDir(info.absoluteFilePath()));
+ } else {
+ all << info;
+ }
+ }
+
+ return all;
+}
+
+QByteArray Agent::encodeFileInfoList(const QFileInfoList& list)
+{
+ QByteArray ba;
+ if (list.empty()) return ba;
+
+ ba.reserve(list.size() * (sizeof(FileListItem) + 10));
+
+ foreach (const QFileInfo& info, list) {
+ QString path = localToWirePath(info.absoluteFilePath());
+ FileListItem item;
+ item.size = info.size();
+
+ if (info.isDir() && _flags & SYNC_PULL) {
+ // If we are pulling, then simulate all of our local directories being old,
+ // so that we get all the new file creations
+ // Existing files should be compared with the proper mtimes though.
+ item.mtime = 0;
+ } else {
+ item.mtime = info.lastModified().toTime_t();
+ }
+
+ if (info.isDir()) {
+ item.type = FILE_TYPE_DIR;
+ } else if (info.isFile()) {
+ item.type = FILE_TYPE_FILE;
+ } else {
+ qWarning() << "What is this?" << path;
+ continue;
+ }
+ QByteArray utfPath = encodeFileName(path);
+
+ item.name_len = utfPath.size();
+ ba.append(reinterpret_cast<char*>(&item), sizeof(FileListItem));
+ ba.append(utfPath.constData());
+ }
+
+ return ba;
+}
+
+Agent::RemoteFileInfoList Agent::decodeFileInfoList(const QByteArray& ba)
+{
+ RemoteFileInfoList list;
+
+ int pos = 0;
+ while (pos < ba.size()) {
+ const FileListItem *item;
+ item = reinterpret_cast<const FileListItem*>(&(ba.data()[pos]));
+
+ list.append(RemoteFileInfo(QString::fromUtf8(&item->name[0],
+ item->name_len),
+ static_cast<FileType>(item->type),
+ QDateTime::fromTime_t(item->mtime),
+ item->size));
+
+ pos += sizeof(FileListItem) + item->name_len;
+ }
+
+ return list;
+}
+
+QByteArray Agent::encodeActionInfoList(const ActionInfoList &list)
+{
+ QByteArray ba;
+
+ ba.reserve(list.size() * (sizeof(ActionItem) + 10));
+
+ foreach (const ActionInfo& info, list) {
+ const QFileInfo fileInfo = info.fileInfo();
+ QString path = localToWirePath(fileInfo.absoluteFilePath());
+ ActionItem item;
+ item.action = info.action();
+ item.size = fileInfo.size();
+ item.mtime = fileInfo.lastModified().toTime_t();
+
+ if (fileInfo.isDir()) {
+ item.type = FILE_TYPE_DIR;
+ } else if (fileInfo.isFile()) {
+ item.type = FILE_TYPE_FILE;
+ } else {
+ item.type = FILE_TYPE_NONE; // Actions might refer to no-longer existing files
+ }
+
+ QByteArray utfPath = path.toUtf8();
+
+ item.name_len = utfPath.size();
+ ba.append(reinterpret_cast<char*>(&item), sizeof(item));
+ ba.append(utfPath.constData());
+ }
+
+ return ba;
+}
+
+Agent::RemoteActionInfoList Agent::decodeActionInfoList(const QByteArray& ba)
+{
+ RemoteActionInfoList list;
+
+ int pos = 0;
+ while (pos < ba.size()) {
+ const ActionItem *item;
+ item = reinterpret_cast<const ActionItem*>(&(ba.data()[pos]));
+
+ list.append(RemoteActionInfo(static_cast<FileAction>(item->action),
+ QString::fromUtf8(&item->name[0],
+ item->name_len),
+ static_cast<FileType>(item->type),
+ QDateTime::fromTime_t(item->mtime),
+ item->size));
+
+ pos += sizeof(ActionItem) + item->name_len;
+ }
+
+ return list;
+}
+
+bool Agent::lessPathDepthThan(const ActionInfo& a1, const ActionInfo& a2)
+{
+ return lessPathDepthThan(a1.fileInfo().absoluteFilePath(),
+ a2.fileInfo().absoluteFilePath());
+}
+
+bool Agent::morePathDepthThan(const ActionInfo& a1, const ActionInfo& a2)
+{
+ return morePathDepthThan(a1.fileInfo().absoluteFilePath(),
+ a2.fileInfo().absoluteFilePath());
+}
+
+QByteArray Agent::encodeFileName(const QString& wire_path)
+{
+ return wire_path.toUtf8();
+}
+
+QString Agent::decodeFileName(const QByteArray& ba)
+{
+ return QString::fromUtf8(ba);
+}
+
+QByteArray Agent::encodeFileNameItem(const QString& wire_path)
+{
+ QByteArray utf8 = encodeFileName(wire_path);
+ QByteArray ba(sizeof(FileNameItem), 0);
+ FileNameItem *item = reinterpret_cast<FileNameItem*>(ba.data());
+ item->name_len = utf8.length();
+ ba.append(utf8);
+ return ba;
+}
+
+QString Agent::decodeFileNameItem(const QByteArray& ba, int* length)
+{
+ const FileNameItem *item = reinterpret_cast<const FileNameItem*>(ba.data());
+ *length = sizeof(FileNameItem) + item->name_len;
+ QString wire_path = decodeFileName(ba.mid(sizeof(FileNameItem), item->name_len));
+ return wire_path;
+}
+
+void Agent::handleDataAvailable()
+{
+ do {
+ _inBuf.append(_socket->readAll());
+
+ while (_inBuf.size() >= inBufRequiredData()) {
+ MessageHeader *h = reinterpret_cast<MessageHeader*>(_inBuf.data());
+ QByteArray data;
+ if (h->len > 0) {
+ data = _inBuf.mid(sizeof(MessageHeader), h->len);
+ }
+ handleMessage(static_cast<MessageType>(h->msg), data);
+ _inBuf.remove(0, inBufRequiredData());
+ }
+ } while (_socket->bytesAvailable() > 0);
+}
+
+void Agent::handleSslErrors(const QList<QSslError> &errors)
+{
+ qDebug() << "SSL errors:" << errors;
+ _socket->ignoreSslErrors(); // TODO For now
+}
+
+void Agent::handleDisconnected()
+{
+ qDebug() << "Disconnected at" << QDateTime::currentDateTime();
+ deleteLater();
+}