2013-10-03 19:04:55 +04:00
|
|
|
/*
|
|
|
|
* Copyright (C) by Dominik Schmidt <dev@dominik-schmidt.de>
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful, but
|
|
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
|
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
|
|
* for more details.
|
|
|
|
*/
|
|
|
|
|
2014-07-10 17:50:24 +04:00
|
|
|
#include "mirall/socketapi.h"
|
2013-10-03 19:04:55 +04:00
|
|
|
|
|
|
|
#include "mirall/mirallconfigfile.h"
|
|
|
|
#include "mirall/folderman.h"
|
|
|
|
#include "mirall/folder.h"
|
|
|
|
#include "mirall/utility.h"
|
2014-06-05 14:02:57 +04:00
|
|
|
#include "mirall/theme.h"
|
2014-07-10 17:50:24 +04:00
|
|
|
#include "mirall/syncjournalfilerecord.h"
|
|
|
|
#include "mirall/syncfileitem.h"
|
2013-10-03 19:04:55 +04:00
|
|
|
|
|
|
|
#include <QDebug>
|
|
|
|
#include <QUrl>
|
|
|
|
#include <QMetaObject>
|
|
|
|
#include <QStringList>
|
2014-06-02 14:08:06 +04:00
|
|
|
#include <QScopedPointer>
|
2013-10-03 19:04:55 +04:00
|
|
|
#include <QFile>
|
|
|
|
#include <QDir>
|
|
|
|
#include <QApplication>
|
|
|
|
|
2014-06-19 16:08:30 +04:00
|
|
|
extern "C" {
|
|
|
|
|
|
|
|
enum csync_exclude_type_e {
|
|
|
|
CSYNC_NOT_EXCLUDED = 0,
|
|
|
|
CSYNC_FILE_SILENTLY_EXCLUDED,
|
|
|
|
CSYNC_FILE_EXCLUDE_AND_REMOVE,
|
|
|
|
CSYNC_FILE_EXCLUDE_LIST,
|
|
|
|
CSYNC_FILE_EXCLUDE_INVALID_CHAR
|
|
|
|
};
|
|
|
|
typedef enum csync_exclude_type_e CSYNC_EXCLUDE_TYPE;
|
|
|
|
|
|
|
|
CSYNC_EXCLUDE_TYPE csync_excluded(CSYNC *ctx, const char *path, int filetype);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2014-07-17 17:01:01 +04:00
|
|
|
namespace {
|
|
|
|
const int PORT = 33001;
|
|
|
|
}
|
|
|
|
|
2013-10-03 19:04:55 +04:00
|
|
|
namespace Mirall {
|
|
|
|
|
|
|
|
#define DEBUG qDebug() << "SocketApi: "
|
|
|
|
|
2014-06-19 16:08:30 +04:00
|
|
|
namespace SocketApiHelper {
|
|
|
|
|
|
|
|
SyncFileStatus fileStatus(Folder *folder, const QString& fileName );
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief recursiveFolderStatus
|
|
|
|
* @param fileName - the relative file name to examine
|
|
|
|
* @return the resulting status
|
|
|
|
*
|
|
|
|
* The resulting status can only be either SYNC which means all files
|
|
|
|
* are in sync, ERROR if an error occured, or EVAL if something needs
|
|
|
|
* to be synced underneath this dir.
|
|
|
|
*/
|
|
|
|
// compute the file status of a directory recursively. It returns either
|
|
|
|
// "all in sync" or "needs update" or "error", no more details.
|
|
|
|
SyncFileStatus recursiveFolderStatus(Folder *folder, const QString& fileName )
|
|
|
|
{
|
|
|
|
QDir dir(folder->path() + fileName);
|
|
|
|
|
|
|
|
const QStringList dirEntries = dir.entryList( QDir::AllEntries | QDir::NoDotAndDotDot );
|
|
|
|
|
2014-07-10 16:27:52 +04:00
|
|
|
SyncFileStatus result(SyncFileStatus::STATUS_SYNC);
|
2014-06-19 17:25:30 +04:00
|
|
|
|
2014-06-19 16:08:30 +04:00
|
|
|
foreach( const QString entry, dirEntries ) {
|
|
|
|
QFileInfo fi(entry);
|
|
|
|
SyncFileStatus sfs;
|
|
|
|
if( fi.isDir() ) {
|
|
|
|
sfs = recursiveFolderStatus(folder, fileName + QLatin1Char('/') + entry );
|
|
|
|
} else {
|
|
|
|
QString fs( fileName + QLatin1Char('/') + entry );
|
|
|
|
if( fileName.isEmpty() ) {
|
|
|
|
// toplevel, no slash etc. needed.
|
|
|
|
fs = entry;
|
|
|
|
}
|
|
|
|
sfs = fileStatus(folder, fs );
|
|
|
|
}
|
|
|
|
|
2014-07-10 16:27:52 +04:00
|
|
|
if( sfs.tag() == SyncFileStatus::STATUS_STAT_ERROR || sfs.tag() == SyncFileStatus::STATUS_ERROR ) {
|
|
|
|
return SyncFileStatus::STATUS_ERROR;
|
|
|
|
} else if( sfs.tag() == SyncFileStatus::STATUS_EVAL || sfs.tag() == SyncFileStatus::STATUS_NEW) {
|
|
|
|
result.set(SyncFileStatus::STATUS_EVAL);
|
2014-06-19 16:08:30 +04:00
|
|
|
}
|
|
|
|
}
|
2014-06-19 17:25:30 +04:00
|
|
|
return result;
|
2014-06-19 16:08:30 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get status about a single file.
|
|
|
|
*/
|
|
|
|
SyncFileStatus fileStatus(Folder *folder, const QString& fileName )
|
|
|
|
{
|
|
|
|
// FIXME: Find a way for STATUS_ERROR
|
|
|
|
|
2014-07-17 21:27:12 +04:00
|
|
|
QString file = folder->path();
|
|
|
|
|
|
|
|
bool isSyncRootFolder = true;
|
|
|
|
if( fileName != QLatin1String("/") && !fileName.isEmpty() ) {
|
2014-06-19 16:08:30 +04:00
|
|
|
file = folder->path() + fileName;
|
2014-07-17 21:27:12 +04:00
|
|
|
isSyncRootFolder = false;
|
2014-06-19 16:08:30 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
QFileInfo fi(file);
|
|
|
|
|
|
|
|
if( !fi.exists() ) {
|
2014-07-17 21:27:12 +04:00
|
|
|
qDebug() << "OO File " << file << " is not existing";
|
2014-07-10 16:27:52 +04:00
|
|
|
return SyncFileStatus(SyncFileStatus::STATUS_STAT_ERROR);
|
2014-06-19 16:08:30 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
// file is ignored?
|
|
|
|
if( fi.isSymLink() ) {
|
2014-07-10 16:27:52 +04:00
|
|
|
return SyncFileStatus(SyncFileStatus::STATUS_IGNORE);
|
2014-06-19 16:08:30 +04:00
|
|
|
}
|
|
|
|
int type = CSYNC_FTW_TYPE_FILE;
|
|
|
|
if( fi.isDir() ) {
|
|
|
|
type = CSYNC_FTW_TYPE_DIR;
|
|
|
|
}
|
|
|
|
|
2014-07-17 21:26:58 +04:00
|
|
|
// on windows, there might be a colon in the file name.
|
|
|
|
QRegExp rx( "^[a-zA-Z]\\:[\\\\/]+");
|
|
|
|
if( file.contains(rx) ) {
|
|
|
|
file.remove(0, 2);
|
|
|
|
}
|
2014-06-19 17:35:29 +04:00
|
|
|
CSYNC_EXCLUDE_TYPE excl = csync_excluded(folder->csyncContext(), file.toUtf8(), type);
|
|
|
|
if( excl != CSYNC_NOT_EXCLUDED ) {
|
2014-07-10 16:27:52 +04:00
|
|
|
return SyncFileStatus(SyncFileStatus::STATUS_IGNORE);
|
2014-06-19 17:35:29 +04:00
|
|
|
}
|
2014-06-19 16:08:30 +04:00
|
|
|
|
2014-07-17 21:27:12 +04:00
|
|
|
// Problem: for the sync dir itself we do not have a record in the sync journal
|
|
|
|
// so the next check must not be used for the sync root folder.
|
2014-06-19 17:35:29 +04:00
|
|
|
SyncJournalFileRecord rec = folder->journalDb()->getFileRecord(fileName);
|
2014-07-17 21:27:12 +04:00
|
|
|
if( !isSyncRootFolder && !rec.isValid() ) {
|
2014-07-10 16:27:52 +04:00
|
|
|
return SyncFileStatus(SyncFileStatus::STATUS_NEW);
|
2014-06-19 16:08:30 +04:00
|
|
|
}
|
|
|
|
|
2014-07-17 21:27:12 +04:00
|
|
|
SyncFileStatus status(SyncFileStatus::STATUS_NONE);
|
2014-06-19 16:08:30 +04:00
|
|
|
if( type == CSYNC_FTW_TYPE_DIR ) {
|
|
|
|
// compute recursive status of the directory
|
2014-07-17 21:27:12 +04:00
|
|
|
status = recursiveFolderStatus( folder, fileName );
|
2014-06-19 17:35:29 +04:00
|
|
|
} else if(fi.lastModified() != rec._modtime ) {
|
|
|
|
// file was locally modified.
|
2014-07-17 21:27:12 +04:00
|
|
|
status.set(SyncFileStatus::STATUS_EVAL);
|
2014-06-19 16:08:30 +04:00
|
|
|
} else {
|
2014-07-17 21:27:12 +04:00
|
|
|
status.set(SyncFileStatus::STATUS_SYNC);
|
2014-06-19 16:08:30 +04:00
|
|
|
}
|
2014-06-19 17:35:29 +04:00
|
|
|
|
|
|
|
if (rec._remotePerm.contains("S")) {
|
|
|
|
// FIXME! that should be an additional flag
|
2014-07-17 21:27:12 +04:00
|
|
|
status.setSharedWithMe(true);
|
2014-06-19 17:35:29 +04:00
|
|
|
}
|
|
|
|
|
2014-07-17 21:27:12 +04:00
|
|
|
return status;
|
2014-06-19 16:08:30 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2014-07-17 17:00:21 +04:00
|
|
|
SocketApi::SocketApi(QObject* parent)
|
2013-10-03 19:04:55 +04:00
|
|
|
: QObject(parent)
|
2014-07-17 17:00:21 +04:00
|
|
|
, _localServer(new QTcpServer(this))
|
2013-10-03 19:04:55 +04:00
|
|
|
{
|
|
|
|
// setup socket
|
2014-07-17 17:01:01 +04:00
|
|
|
DEBUG << "Establishing SocketAPI server at" << PORT;
|
|
|
|
if (!_localServer->listen(QHostAddress::LocalHost, PORT)) {
|
|
|
|
DEBUG << "Failed to bind to port" << PORT;
|
|
|
|
}
|
2014-06-02 14:08:06 +04:00
|
|
|
connect(_localServer, SIGNAL(newConnection()), this, SLOT(slotNewConnection()));
|
2013-10-03 19:04:55 +04:00
|
|
|
|
|
|
|
// folder watcher
|
2014-07-15 15:34:32 +04:00
|
|
|
connect(FolderMan::instance(), SIGNAL(folderSyncStateChange(QString)), this, SLOT(slotUpdateFolderView(QString)));
|
|
|
|
connect(ProgressDispatcher::instance(), SIGNAL(jobCompleted(QString,SyncFileItem)),
|
|
|
|
SLOT(slotJobCompleted(QString,SyncFileItem)));
|
2013-10-03 19:04:55 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
SocketApi::~SocketApi()
|
|
|
|
{
|
|
|
|
DEBUG << "dtor";
|
|
|
|
_localServer->close();
|
|
|
|
}
|
|
|
|
|
2014-06-02 14:08:06 +04:00
|
|
|
void SocketApi::slotNewConnection()
|
2013-10-03 19:04:55 +04:00
|
|
|
{
|
2014-07-10 17:50:24 +04:00
|
|
|
QTcpSocket* socket = _localServer->nextPendingConnection();
|
|
|
|
|
2014-06-02 14:08:06 +04:00
|
|
|
if( ! socket ) {
|
|
|
|
return;
|
|
|
|
}
|
2014-07-17 17:00:21 +04:00
|
|
|
DEBUG << "New connection" << socket;
|
2014-06-02 14:08:06 +04:00
|
|
|
connect(socket, SIGNAL(readyRead()), this, SLOT(slotReadSocket()));
|
2013-10-03 19:04:55 +04:00
|
|
|
connect(socket, SIGNAL(disconnected()), this, SLOT(onLostConnection()));
|
|
|
|
Q_ASSERT(socket->readAll().isEmpty());
|
|
|
|
|
|
|
|
_listeners.append(socket);
|
2014-07-15 19:55:55 +04:00
|
|
|
|
|
|
|
foreach( QString alias, FolderMan::instance()->map().keys() ) {
|
2014-07-25 14:10:45 +04:00
|
|
|
slotRegisterPath(alias);
|
2014-07-15 19:55:55 +04:00
|
|
|
}
|
2013-10-03 19:04:55 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
void SocketApi::onLostConnection()
|
|
|
|
{
|
|
|
|
DEBUG << "Lost connection " << sender();
|
|
|
|
|
2014-07-10 17:50:24 +04:00
|
|
|
QTcpSocket* socket = qobject_cast<QTcpSocket*>(sender());
|
2013-10-03 19:04:55 +04:00
|
|
|
_listeners.removeAll(socket);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2014-06-02 14:08:06 +04:00
|
|
|
void SocketApi::slotReadSocket()
|
2013-10-03 19:04:55 +04:00
|
|
|
{
|
2014-07-10 17:50:24 +04:00
|
|
|
QTcpSocket* socket = qobject_cast<QTcpSocket*>(sender());
|
2013-10-03 19:04:55 +04:00
|
|
|
Q_ASSERT(socket);
|
|
|
|
|
2014-06-02 14:08:06 +04:00
|
|
|
while(socket->canReadLine()) {
|
2013-10-03 19:04:55 +04:00
|
|
|
QString line = QString::fromUtf8(socket->readLine()).trimmed();
|
|
|
|
QString command = line.split(":").first();
|
|
|
|
QString function = QString(QLatin1String("command_")).append(command);
|
|
|
|
|
2014-07-10 17:50:24 +04:00
|
|
|
QString functionWithArguments = function + QLatin1String("(QString,QTcpSocket*)");
|
2013-10-03 19:04:55 +04:00
|
|
|
int indexOfMethod = this->metaObject()->indexOfMethod(functionWithArguments.toAscii());
|
|
|
|
|
|
|
|
QString argument = line.remove(0, command.length()+1).trimmed();
|
2014-06-02 14:08:06 +04:00
|
|
|
if(indexOfMethod != -1) {
|
2014-07-10 17:50:24 +04:00
|
|
|
QMetaObject::invokeMethod(this, function.toAscii(), Q_ARG(QString, argument), Q_ARG(QTcpSocket*, socket));
|
2014-06-02 14:08:06 +04:00
|
|
|
} else {
|
2013-10-03 19:04:55 +04:00
|
|
|
DEBUG << "The command is not supported by this version of the client:" << command << "with argument:" << argument;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-07-25 14:10:45 +04:00
|
|
|
void SocketApi::slotRegisterPath( const QString& alias )
|
2013-10-03 19:04:55 +04:00
|
|
|
{
|
2014-07-25 14:10:45 +04:00
|
|
|
Folder *f = FolderMan::instance()->folder(alias);
|
|
|
|
if (f) {
|
|
|
|
broadcastMessage(QLatin1String("REGISTER_PATH"), f->path() );
|
|
|
|
}
|
|
|
|
}
|
2014-07-11 13:30:47 +04:00
|
|
|
|
2014-07-25 14:10:45 +04:00
|
|
|
void SocketApi::slotUnregisterPath( const QString& alias )
|
|
|
|
{
|
2014-07-11 13:30:47 +04:00
|
|
|
Folder *f = FolderMan::instance()->folder(alias);
|
|
|
|
if (f) {
|
2014-07-25 14:10:45 +04:00
|
|
|
broadcastMessage(QLatin1String("UNREGISTER_PATH"), f->path() );
|
2014-07-11 13:30:47 +04:00
|
|
|
}
|
2014-07-25 14:10:45 +04:00
|
|
|
}
|
2014-07-11 13:30:47 +04:00
|
|
|
|
2014-07-25 14:10:45 +04:00
|
|
|
void SocketApi::slotUpdateFolderView(const QString& alias)
|
|
|
|
{
|
|
|
|
Folder *f = FolderMan::instance()->folder(alias);
|
|
|
|
if (f) {
|
|
|
|
// do only send UPDATE_VIEW for a couple of status
|
|
|
|
if( f->syncResult().status() == SyncResult::SyncPrepare ||
|
|
|
|
f->syncResult().status() == SyncResult::Success ||
|
|
|
|
f->syncResult().status() == SyncResult::Problem ||
|
|
|
|
f->syncResult().status() == SyncResult::Error ||
|
|
|
|
f->syncResult().status() == SyncResult::SetupError ) {
|
|
|
|
broadcastMessage(QLatin1String("UPDATE_VIEW"), f->path() );
|
|
|
|
}
|
|
|
|
}
|
2013-10-03 19:04:55 +04:00
|
|
|
}
|
|
|
|
|
2014-06-06 17:52:55 +04:00
|
|
|
void SocketApi::slotJobCompleted(const QString &folder, const SyncFileItem &item)
|
|
|
|
{
|
|
|
|
Folder *f = FolderMan::instance()->folder(folder);
|
|
|
|
if (!f)
|
|
|
|
return;
|
|
|
|
|
|
|
|
const QString path = f->path() + item.destination();
|
|
|
|
|
|
|
|
QString command = QLatin1String("OK");
|
|
|
|
if (Progress::isWarningKind(item._status)) {
|
|
|
|
command = QLatin1String("ERROR");
|
|
|
|
}
|
|
|
|
|
2014-07-25 14:10:45 +04:00
|
|
|
broadcastMessage(QLatin1String("BROADCAST:"), path, command);
|
2014-06-06 17:52:55 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2013-10-03 19:04:55 +04:00
|
|
|
|
2014-07-10 17:50:24 +04:00
|
|
|
void SocketApi::sendMessage(QTcpSocket *socket, const QString& message)
|
2013-10-03 19:04:55 +04:00
|
|
|
{
|
|
|
|
DEBUG << "Sending message: " << message;
|
|
|
|
QString localMessage = message;
|
2014-07-25 14:10:45 +04:00
|
|
|
if( ! localMessage.endsWith(QLatin1Char('\n'))) {
|
|
|
|
localMessage.append(QLatin1Char('\n'));
|
|
|
|
}
|
|
|
|
qint64 sent = socket->write(localMessage.toUtf8());
|
|
|
|
if( sent != localMessage.toUtf8().length() ) {
|
|
|
|
qDebug() << "WARN: Could not send all data on socket for " << localMessage;
|
|
|
|
}
|
|
|
|
|
2013-10-03 19:04:55 +04:00
|
|
|
}
|
|
|
|
|
2014-07-25 14:10:45 +04:00
|
|
|
void SocketApi::broadcastMessage( const QString& verb, const QString& path, const QString& status )
|
2013-10-03 19:04:55 +04:00
|
|
|
{
|
2014-07-25 14:10:45 +04:00
|
|
|
QString msg(verb);
|
|
|
|
|
|
|
|
if( !status.isEmpty() ) {
|
|
|
|
msg.append(QLatin1Char(':'));
|
|
|
|
msg.append(status);
|
|
|
|
}
|
|
|
|
if( !path.isEmpty() ) {
|
|
|
|
msg.append(QLatin1Char(':'));
|
|
|
|
msg.append(QDir::toNativeSeparators(path));
|
|
|
|
}
|
|
|
|
|
|
|
|
DEBUG << "Broadcasting to" << _listeners.count() << "listeners: " << msg;
|
|
|
|
foreach(QTcpSocket *socket, _listeners) {
|
|
|
|
sendMessage(socket, msg);
|
2013-10-03 19:04:55 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-07-10 17:50:24 +04:00
|
|
|
void SocketApi::command_RETRIEVE_FOLDER_STATUS(const QString& argument, QTcpSocket* socket)
|
2013-10-03 19:04:55 +04:00
|
|
|
{
|
2014-06-19 17:02:27 +04:00
|
|
|
// This command is the same as RETRIEVE_FILE_STATUS
|
2013-10-03 19:04:55 +04:00
|
|
|
|
2014-06-19 17:02:27 +04:00
|
|
|
qDebug() << Q_FUNC_INFO << argument;
|
|
|
|
command_RETRIEVE_FILE_STATUS(argument, socket);
|
2014-06-06 17:37:04 +04:00
|
|
|
}
|
|
|
|
|
2014-07-10 17:50:24 +04:00
|
|
|
void SocketApi::command_RETRIEVE_FILE_STATUS(const QString& argument, QTcpSocket* socket)
|
2014-06-06 17:37:04 +04:00
|
|
|
{
|
|
|
|
if( !socket ) {
|
|
|
|
qDebug() << "No valid socket object.";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
qDebug() << Q_FUNC_INFO << argument;
|
|
|
|
|
|
|
|
QString statusString;
|
|
|
|
|
2014-07-10 16:27:52 +04:00
|
|
|
Folder* syncFolder = FolderMan::instance()->folderForPath( argument );
|
|
|
|
if (!syncFolder) {
|
|
|
|
// this can happen in offline mode e.g.: nothing to worry about
|
2014-06-06 17:37:04 +04:00
|
|
|
DEBUG << "folder offline or not watched:" << argument;
|
|
|
|
statusString = QLatin1String("NOP");
|
2014-07-10 16:27:52 +04:00
|
|
|
} else {
|
|
|
|
const QString file = argument.mid(syncFolder->path().length());
|
|
|
|
SyncFileStatus fileStatus = SocketApiHelper::fileStatus(syncFolder, file);
|
2014-06-06 17:37:04 +04:00
|
|
|
|
2014-07-10 16:27:52 +04:00
|
|
|
statusString = fileStatus.toSocketAPIString();
|
2013-10-03 19:04:55 +04:00
|
|
|
}
|
2014-06-06 17:37:04 +04:00
|
|
|
|
2014-07-17 19:41:20 +04:00
|
|
|
QString message = QLatin1String("STATUS:")+statusString+QLatin1Char(':')
|
|
|
|
+QDir::toNativeSeparators(argument);
|
2014-06-06 17:37:04 +04:00
|
|
|
sendMessage(socket, message);
|
2013-10-03 19:04:55 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace Mirall
|