#include "agent.h" #ifdef Q_OS_UNIX #include #include #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)), SLOT(handleSslErrors(QList))); 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(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(_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, ×); 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& local_files, const QHash& 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(&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(&(ba.data()[pos])); list.append(RemoteFileInfo(QString::fromUtf8(&item->name[0], item->name_len), static_cast(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(&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(&(ba.data()[pos])); list.append(RemoteActionInfo(static_cast(item->action), QString::fromUtf8(&item->name[0], item->name_len), static_cast(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(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(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(_inBuf.data()); QByteArray data; if (h->len > 0) { data = _inBuf.mid(sizeof(MessageHeader), h->len); } handleMessage(static_cast(h->msg), data); _inBuf.remove(0, inBufRequiredData()); } } while (_socket->bytesAvailable() > 0); } void Agent::handleSslErrors(const QList &errors) { qDebug() << "SSL errors:" << errors; _socket->ignoreSslErrors(); // TODO For now } void Agent::handleDisconnected() { qDebug() << "Disconnected at" << QDateTime::currentDateTime(); deleteLater(); }