mirror of
https://github.com/nextcloud/desktop.git
synced 2024-11-27 17:37:36 +03:00
1a63ad67f0
already deleted files.
1706 lines
63 KiB
C++
1706 lines
63 KiB
C++
/******************************************************************************
|
|
* Copyright 2011 Juan Carlos Cornejo jc2@paintblack.com
|
|
*
|
|
* This file is part of owncloud_sync_qt.
|
|
*
|
|
* owncloud_sync 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 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* owncloud_sync 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.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with owncloud_sync. If not, see <http://www.gnu.org/licenses/>.
|
|
******************************************************************************/
|
|
#include "SyncGlobal.h"
|
|
#include "OwnCloudSync.h"
|
|
#include "sqlite3_util.h"
|
|
#include "QWebDAV.h"
|
|
|
|
#include <QFile>
|
|
#include <QtSql/QSqlDatabase>
|
|
#include <QtSql/QSqlError>
|
|
#include <QtSql/QSqlQuery>
|
|
#include <QDir>
|
|
#include <QFileInfo>
|
|
#include <QDateTime>
|
|
#include <QTimer>
|
|
#include <QSystemTrayIcon>
|
|
#include <QFileSystemWatcher>
|
|
#include <QFileDialog>
|
|
#include <QTableWidgetItem>
|
|
#include <QComboBox>
|
|
|
|
#ifdef Q_OS_LINUX
|
|
#include <kwallet.h>
|
|
#endif
|
|
|
|
OwnCloudSync::OwnCloudSync(QString name, WId id,QSet<QString> *globalFilters)
|
|
: mAccountName(name),mWinId(id),mGlobalFilters(globalFilters)
|
|
{
|
|
mBusy = false;
|
|
mIsPaused = false;
|
|
|
|
// Set the pointers so we can delete them without worrying :)
|
|
mSyncTimer = 0;
|
|
mFileWatcher = 0;
|
|
mWallet = 0;
|
|
|
|
mHardStop = false;
|
|
mIsFirstRun = true;
|
|
mDownloadingConflictingFile = false;
|
|
mFileAccessBusy = false;
|
|
mConflictsExist = false;
|
|
mSettingsCheck = true;
|
|
mIsEnabled = false;
|
|
mAllowedToSync = false;
|
|
mNeedsSync = false;
|
|
mNotifySyncEmitted = false;
|
|
mLastSyncAborted = SYNCFINISHED;
|
|
mSyncPosition = SYNCFINISHED;
|
|
|
|
mRequestTimer = new QTimer(this);
|
|
connect(mRequestTimer,SIGNAL(timeout()),this,SLOT(requestTimedout()));
|
|
|
|
// Create a QWebDAV instance
|
|
mWebdav = new QWebDAV();
|
|
|
|
// Connect to QWebDAV signals
|
|
connect(mWebdav,SIGNAL(directoryListingError(QString)),
|
|
this, SLOT(directoryListingError(QString)));
|
|
connect(mWebdav,SIGNAL(directoryListingReady(QList<QWebDAV::FileInfo>)),
|
|
this, SLOT(processDirectoryListing(QList<QWebDAV::FileInfo>)));
|
|
connect(mWebdav,SIGNAL(fileReady(QNetworkReply*,QString)),
|
|
this, SLOT(processFileReady(QNetworkReply*,QString)));
|
|
|
|
connect(mWebdav,SIGNAL(uploadComplete(QString)),
|
|
this, SLOT(updateDBUpload(QString)));
|
|
connect(mWebdav,SIGNAL(directoryCreated(QString)),
|
|
this, SLOT(serverDirectoryCreated(QString)));
|
|
|
|
mDownloadingFiles.clear();
|
|
mDownloadConflict.clear();
|
|
mUploadingFiles.clear();
|
|
|
|
// Initialize the Database
|
|
mDB = QSqlDatabase::addDatabase("QSQLITE",mAccountName);
|
|
#ifdef Q_OS_LINUX
|
|
// In linux, we will store all databases in
|
|
// $HOME/.local/share/data/owncloud_sync
|
|
mHomeDirectory = QDir::home().path();
|
|
mConfigDirectory = mHomeDirectory+"/.local/share/data/owncloud_sync";
|
|
QDir configDir(mConfigDirectory);
|
|
configDir.mkpath(mConfigDirectory);
|
|
QDir logsDir(mConfigDirectory+"/logs");
|
|
logsDir.mkpath(mConfigDirectory+"/logs");
|
|
//mDB.setDatabaseName(mConfigDirectory+"/owncloud_sync.db");
|
|
mDB.setDatabaseName(":memory:"); // Use memory for now
|
|
mDBFileName = mConfigDirectory+"/"+mAccountName+".db";
|
|
#endif
|
|
// Find out if the database exists.
|
|
QFile dbFile(mConfigDirectory+"/"+mAccountName+".db");
|
|
if( dbFile.exists() ) {
|
|
if(!mDB.open()) {
|
|
syncDebug() << "Cannot open database!";
|
|
syncDebug() << mDB.lastError().text();
|
|
mDBOpen = false;
|
|
} else {
|
|
mDBOpen = true;
|
|
loadDBFromFile();
|
|
readConfigFromDB();
|
|
//ui->buttonSave->setDisabled(true);
|
|
syncDebug() << "Checking configuration!";
|
|
#ifdef Q_OS_LINUX
|
|
// Wait until the password is set
|
|
#else
|
|
initialize();
|
|
#endif
|
|
}
|
|
} else {
|
|
createDataBase(); // Create the database in memory
|
|
}
|
|
|
|
mSaveDBTimer = new QTimer(this);
|
|
connect(mSaveDBTimer, SIGNAL(timeout()), this, SLOT(saveDBToFile()));
|
|
mSaveDBTimer->start(370000);
|
|
updateStatus();
|
|
|
|
// Now the password management
|
|
#ifdef Q_OS_LINUX
|
|
mWallet= KWallet::Wallet::openWallet(
|
|
KWallet::Wallet::NetworkWallet(),
|
|
mWinId,KWallet::Wallet::Asynchronous);
|
|
connect(mWallet, SIGNAL(walletOpened(bool)), SLOT(walletOpened(bool)));
|
|
#else
|
|
#endif
|
|
}
|
|
|
|
void OwnCloudSync::setEnabled( bool enabled)
|
|
{
|
|
mIsEnabled = enabled;
|
|
saveConfigToDB();
|
|
QSqlQuery query(QSqlDatabase::database(mAccountName));
|
|
query.exec("SELECT * FROM config;");
|
|
if(query.next() ) { // Update
|
|
query.exec(QString("UPDATE config SET enabled='%1';")
|
|
.arg(mIsEnabled?"yes":"no"));
|
|
} else {
|
|
query.exec(QString("INSERT INTO config(enabled) values('%1');")
|
|
.arg(mIsEnabled?"yes":"no"));
|
|
}
|
|
|
|
if(mIsEnabled) {
|
|
syncDebug() << "Starting " << mAccountName;
|
|
start();
|
|
} else {
|
|
syncDebug() << "Stopping " << mAccountName;
|
|
stop();
|
|
}
|
|
}
|
|
|
|
void OwnCloudSync::directoryListingError(QString url)
|
|
{
|
|
if(mSettingsCheck) {
|
|
syncDebug() << "Something wrong with the settings, please check.";
|
|
emit toLog(tr("Settings could not be confirmed for account %1. Please "
|
|
"confirm your settings and try again.").arg(mAccountName));
|
|
}
|
|
}
|
|
|
|
void OwnCloudSync::updateStatus()
|
|
{
|
|
if( !mBusy ) {
|
|
// ???
|
|
} else {
|
|
if( mSyncTimer )
|
|
mSyncTimer->stop();
|
|
emit toStatus(tr("%1 out of %2 bytes").arg(mTransferState+mCurrentFile)
|
|
.arg(mCurrentFileSize));
|
|
}
|
|
}
|
|
|
|
void OwnCloudSync::timeToSync()
|
|
{
|
|
if(mIsPaused) { // Paused, skip this sync cycle
|
|
return;
|
|
}
|
|
mNotifySyncEmitted = true;
|
|
emit readyToSync(this);
|
|
}
|
|
|
|
void OwnCloudSync::sync()
|
|
{
|
|
mNeedsSync = true;
|
|
mNotifySyncEmitted = false;
|
|
if(!mIsEnabled) {
|
|
return;
|
|
}
|
|
|
|
if ( mBusy ) {
|
|
emit toLog(tr("Ooops, looks like %1 is busy, we'll try again later")
|
|
.arg(mAccountName));
|
|
return;
|
|
}
|
|
|
|
if( !mDBOpen ) {
|
|
emit toStatus(tr("Database is not open. Aborting sync!"));
|
|
return;
|
|
}
|
|
|
|
// Announce we are busy!
|
|
mBusy = true;
|
|
if(mSyncTimer)
|
|
mSyncTimer->stop();
|
|
|
|
emit toLog(tr("\nSynchronizing %1 on: %2")
|
|
.arg(mAccountName)
|
|
.arg(QDateTime::currentDateTime().toString()));
|
|
|
|
// First, continue right where the last sync left off.
|
|
if( mLastSyncAborted != SYNCFINISHED ) {
|
|
switch(mLastSyncAborted) {
|
|
emit toLog(tr("Last sync unsuccessful. Resumming."));
|
|
case LISTLOCALDIR:
|
|
scanLocalDirectory(mLocalDirectory);
|
|
break;
|
|
case LISTREMOTEDIR:
|
|
restartRequestTimer();
|
|
mWebdav->dirList(mRemoteDirectory+"/");
|
|
return;
|
|
case TRANSFER:
|
|
processNextStep();
|
|
return;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If this is the first run, scan the directory, otherwise just wait
|
|
// for the watcher to update us :)
|
|
if(mIsFirstRun) {
|
|
//syncDebug() << "Scanning local directory: ";
|
|
scanLocalDirectory(mLocalDirectory);
|
|
//syncDebug() << "Scanning local directory!!!";
|
|
}
|
|
|
|
if ( mScanDirectoriesSet.size() != 0 ) {
|
|
while( mScanDirectories.size() > 0 ) {
|
|
QString relativeName = mScanDirectories.dequeue();
|
|
QString name(relativeName);
|
|
name.replace("_sssspace_"," ");
|
|
scanLocalDirectoryForNewFiles(name);
|
|
mScanDirectoriesSet.remove(relativeName);
|
|
}
|
|
}
|
|
|
|
// Then scan the base directory of the WebDAV server
|
|
//syncDebug() << "Scanning server: " << mRemoteDirectory+"/";
|
|
mWebdav->dirList(mRemoteDirectory+"/");
|
|
mSyncPosition = LISTREMOTEDIR;
|
|
restartRequestTimer();
|
|
}
|
|
|
|
OwnCloudSync::~OwnCloudSync()
|
|
{
|
|
delete mWebdav;
|
|
mDB.close();
|
|
}
|
|
|
|
void OwnCloudSync::processDirectoryListing(QList<QWebDAV::FileInfo> fileInfo)
|
|
{
|
|
stopRequestTimer();
|
|
if( mSettingsCheck ) {
|
|
// Great, we were just checking
|
|
mSettingsCheck = false;
|
|
settingsAreFine();
|
|
return;
|
|
}
|
|
// Compare against the database of known files
|
|
QSqlQuery query(QSqlDatabase::database(mAccountName));
|
|
QSqlQuery add(QSqlDatabase::database(mAccountName));
|
|
QString conflict("");
|
|
QString prev("");
|
|
for(int i = 0; i < fileInfo.size(); i++ ){
|
|
// Check if it is a restricted file
|
|
if ( isFileFiltered(fileInfo[i].fileName)) {
|
|
continue;
|
|
}
|
|
query = queryDBFileInfo(fileInfo[i].fileName,"server_files");
|
|
if(query.next()) { // File exists get conflict and last_modified
|
|
prev = query.value(4).toString();
|
|
conflict = query.value(7).toString();
|
|
if ( conflict != "" && !mUploadingConflictFilesSet.contains(
|
|
fileInfo[i].fileName.replace(" ","_sssspace_")) ) {
|
|
// Enable the conflict resolution window
|
|
emit conflictExists(this);
|
|
mConflictsExist = true;
|
|
//syncDebug() << "SFile still conflicts: " << fileInfo[i].fileName;
|
|
}
|
|
} // Now add to the processing DB
|
|
QString addStatement = QString("INSERT INTO server_files_processing("
|
|
"file_name,file_size,file_type,"
|
|
"last_modified,conflict,"
|
|
"prev_modified) "
|
|
"values('%1','%2','%3','%4','%5',"
|
|
"'%6');")
|
|
.arg(fileInfo[i].fileName).arg(fileInfo[i].size)
|
|
.arg(fileInfo[i].type).arg(fileInfo[i].lastModified)
|
|
.arg(conflict).arg(prev);
|
|
//syncDebug() << "Query: " << addStatement;
|
|
add.exec(addStatement);
|
|
// If a collection, list those contents too
|
|
if(fileInfo[i].type == "collection") {
|
|
mDirectoryQueue.enqueue(fileInfo[i].fileName);
|
|
}
|
|
}
|
|
if(!mDirectoryQueue.empty()) {
|
|
mWebdav->dirList(mDirectoryQueue.dequeue());
|
|
mSyncPosition = LISTREMOTEDIR;
|
|
restartRequestTimer();
|
|
} else {
|
|
syncFiles();
|
|
}
|
|
}
|
|
|
|
void OwnCloudSync::processFileReady(QNetworkReply *reply,QString fileName)
|
|
{
|
|
fileName = stringRemoveBasePath(fileName,mRemoteDirectory);
|
|
// Temporarily remove this watcher so we don't get a message when
|
|
// we modify it.
|
|
if(mFileWatcher)
|
|
mFileWatcher->removePath(mLocalDirectory+fileName);
|
|
QString finalName;
|
|
if(mDownloadingConflictingFile) {
|
|
finalName = getConflictName(fileName);
|
|
//syncDebug() << "Downloading conflicting file " << fileName;
|
|
} else {
|
|
finalName = fileName;
|
|
}
|
|
QFile file(mLocalDirectory+finalName);
|
|
if (!file.open(QIODevice::WriteOnly)) {
|
|
syncDebug() << "Could not open file " << file.fileName() << "for writting.";
|
|
processNextStep();
|
|
return;
|
|
}
|
|
QDataStream out(&file);
|
|
qint64 bytes = reply->bytesAvailable();
|
|
qint64 bytesToRead;
|
|
while( bytes > 0 ) {
|
|
if( bytes > 100 ) {
|
|
bytesToRead = 100;
|
|
bytes -= 100;
|
|
} else {
|
|
bytesToRead = bytes;
|
|
bytes = 0;
|
|
}
|
|
out.writeRawData(reply->read(bytesToRead).constData(),bytesToRead);
|
|
}
|
|
file.flush();
|
|
file.close();
|
|
updateDBDownload(fileName);
|
|
if(mFileWatcher)
|
|
mFileWatcher->addPath(mLocalDirectory+fileName); // Add the watcher back!
|
|
reply->deleteLater();
|
|
processNextStep();
|
|
}
|
|
|
|
void OwnCloudSync::processNextStep()
|
|
{
|
|
stopRequestTimer();
|
|
if(mHardStop) { // Hard stop, usually indicates account will be removed
|
|
return;
|
|
}
|
|
|
|
mSyncPosition = TRANSFER;
|
|
|
|
if(mIsPaused) {
|
|
return;
|
|
}
|
|
|
|
if( mMakeServerDirs.size() != 0 ) {
|
|
mWebdav->mkdir(mMakeServerDirs.dequeue());
|
|
restartRequestTimer();
|
|
//syncDebug() << "Making the following directories on server: " <<
|
|
// serverDirs[i];
|
|
// Check if there is another file to dowload, if so, start that process
|
|
}else if( mDownloadingFiles.size() != 0 ) {
|
|
download(mDownloadingFiles.dequeue());
|
|
} else if ( mUploadingFiles.size() != 0 ) { // Maybe an upload?
|
|
upload(mUploadingFiles.dequeue());
|
|
} else if ( mUploadingConflictFiles.size() !=0 ) { // Upload conflict files
|
|
FileInfo info = mUploadingConflictFiles.dequeue();
|
|
upload(info);
|
|
clearFileConflict(info.name);
|
|
mUploadingConflictFilesSet.remove(info.name.replace(" ","_sssspace_"));
|
|
} else if ( mDownloadConflict.size() != 0 ) { // Download conflicting files
|
|
mDownloadingConflictingFile = true;
|
|
download(mDownloadConflict.dequeue());
|
|
emit conflictExists(this);
|
|
} else { // We are done! Start the sync clock
|
|
mDownloadingConflictingFile = false;
|
|
mBusy = false;
|
|
if(mSyncTimer)
|
|
mSyncTimer->start();
|
|
emit toLog(tr("Finished %1: %2").arg(mAccountName)
|
|
.arg(QDateTime::currentDateTime().toString()));
|
|
if(mConflictsExist) {
|
|
//mSystemTray->setIcon(mDefaultConflictIcon);
|
|
} else {
|
|
//mSystemTray->setIcon(mDefaultIcon);
|
|
}
|
|
QSqlQuery query(QSqlDatabase::database(mAccountName));
|
|
query.exec(QString("UPDATE config SET lastsync='%1';").arg(
|
|
QDateTime::currentDateTime().toString()));
|
|
mNeedsSync = false;
|
|
mLastSyncAborted = SYNCFINISHED;
|
|
mSyncPosition = SYNCFINISHED;
|
|
emit finishedSync(this);
|
|
}
|
|
updateStatus();
|
|
}
|
|
|
|
QString OwnCloudSync::getLastSync()
|
|
{
|
|
QSqlQuery query(QSqlDatabase::database(mAccountName));
|
|
query.exec("SELECT lastsync FROM config;");
|
|
if( query.next() ) {
|
|
return query.value(0).toString();
|
|
}
|
|
return QString("");
|
|
}
|
|
|
|
void OwnCloudSync::scanLocalDirectory( QString dirPath)
|
|
{
|
|
QDir dir(dirPath);
|
|
dir.setFilter(QDir::Files|QDir::NoDot|QDir::NoDotDot|QDir::AllEntries
|
|
|QDir::Hidden);
|
|
QStringList list = dir.entryList();
|
|
for( int i = 0; i < list.size(); i++ ) {
|
|
QString name = list.at(i);
|
|
//syncDebug() << "Processing local file: " + dirPath+"/"+list.at(i);
|
|
if( isFileFiltered(name) ) {
|
|
continue;
|
|
}
|
|
|
|
//syncDebug() << "Relative Path: " << relativeName;
|
|
processLocalFile(dirPath + "/" + name);
|
|
|
|
// Check if it is a directory, and if so, process it
|
|
}
|
|
}
|
|
|
|
void OwnCloudSync::processLocalFile(QString name)
|
|
{
|
|
QFileInfo file( name );
|
|
QString type;
|
|
QString append;
|
|
// Check if it is a directory, and if so, process it
|
|
if ( file.isDir() ) {
|
|
type = "collection";
|
|
append = "/";
|
|
} else {
|
|
type = "file";
|
|
append = "";
|
|
}
|
|
|
|
// Add to the watcher
|
|
mFileWatcher->addPath(name+append);
|
|
updateDBLocalFile(name + append,
|
|
file.size(),file.lastModified().toUTC()
|
|
.toMSecsSinceEpoch(),type);
|
|
|
|
if ( file.isDir() ) {
|
|
scanLocalDirectory(file.absoluteFilePath() );
|
|
}
|
|
}
|
|
|
|
void OwnCloudSync::updateDBLocalFile(QString name, qint64 size, qint64 last,
|
|
QString type )
|
|
{
|
|
// Do not upload the server conflict files
|
|
if( isFileFiltered(name)) {
|
|
return;
|
|
}
|
|
// Get the relative name of the file
|
|
name = stringRemoveBasePath(name, mLocalDirectory);
|
|
name = mRemoteDirectory + name;
|
|
//syncDebug() << "Local file name: " << name;
|
|
// Check against the database
|
|
QSqlQuery query = queryDBFileInfo(name,"local_files");
|
|
QString prev("");
|
|
QString conflict("");
|
|
QString sync("");
|
|
if (query.next() ) { // We already knew about this file. Update info.
|
|
prev = query.value(4).toString();
|
|
conflict = query.value(8).toString();
|
|
sync = query.value(5).toString();
|
|
qint64 prevModified = prev.toLongLong();
|
|
// Sometimes the watcher goes crazy, though. So check to see
|
|
// if last == previous, if so, then it never changed anything!
|
|
//syncDebug() << "Last: " << last << " Prev: " << prevModified;
|
|
if( (last != prevModified) || mIsFirstRun ) {
|
|
if (conflict != "") {
|
|
// Enable the conflict resolution button
|
|
emit conflictExists(this);
|
|
mConflictsExist = true;
|
|
//syncDebug() << "LFile still conflicts: " << name;
|
|
}
|
|
} else {
|
|
return; // Nothing changed
|
|
}
|
|
}
|
|
QString addStatement = QString("INSERT INTO local_files_processing "
|
|
"(file_name,file_size,file_type,"
|
|
"last_modified,prev_modified,conflict,"
|
|
"last_sync) values('%1','%2','%3','%4','%5',"
|
|
"'%6','%7');")
|
|
.arg(name).arg(size).arg(type).arg(last).arg(prev).arg(conflict)
|
|
.arg(sync);
|
|
//syncDebug() << "Query: " << addStatement;
|
|
query.exec(addStatement);
|
|
mNeedsSync = true; // Since a local file was changed, we need to sync
|
|
// before closing
|
|
//syncDebug() << "Processing: " << mLocalDirectory + relativeName << " Size: "
|
|
// << file.size();
|
|
}
|
|
|
|
QSqlQuery OwnCloudSync::queryDBFileInfo(QString fileName, QString table)
|
|
{
|
|
QSqlQuery query(QSqlDatabase::database(mAccountName));
|
|
query.exec("SELECT * FROM " + table + " WHERE file_name = '" +
|
|
fileName + "';");
|
|
return query;
|
|
}
|
|
|
|
QSqlQuery OwnCloudSync::queryDBAllFiles(QString table)
|
|
{
|
|
QSqlQuery query(QSqlDatabase::database(mAccountName));
|
|
query.exec("SELECT * FROM " + table + ";");
|
|
return query;
|
|
}
|
|
|
|
void OwnCloudSync::syncFiles()
|
|
{
|
|
QList<QString> localDirs;
|
|
QSqlQuery localQuery;
|
|
if( !mIsFirstRun ) {
|
|
localQuery = queryDBAllFiles("local_files");
|
|
while ( localQuery.next() ) {
|
|
QSqlQuery query(QSqlDatabase::database(mAccountName));
|
|
query.exec(QString("SELECT file_name FROM local_files_processing "
|
|
"WHERE file_name='%1'")
|
|
.arg(localQuery.value(1).toString()));
|
|
if(!query.next()) {
|
|
query.exec(QString("INSERT INTO local_files_processing (file_name,"
|
|
"file_size,file_type,last_modified,last_sync,"
|
|
"prev_modified,conflict) "
|
|
"values('%1','%2','%3','%4','%5','%6','%7');")
|
|
.arg(localQuery.value(1).toString())
|
|
.arg(localQuery.value(2).toString())
|
|
.arg(localQuery.value(3).toString())
|
|
.arg(localQuery.value(4).toString())
|
|
.arg(localQuery.value(5).toString())
|
|
.arg(localQuery.value(7).toString())
|
|
.arg(localQuery.value(8).toString())
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
localQuery = queryDBAllFiles("local_files_processing");
|
|
// Reset the progress trackers
|
|
mTotalToDownload = 0;
|
|
mTotalToUpload = 0;
|
|
mTotalToTransfer = 0;
|
|
mTotalDownloaded = 0;
|
|
mTotalUploaded = 0;
|
|
mTotalTransfered = 0;
|
|
//mUploadingFiles.clear();
|
|
//mDownloadingFiles.clear();
|
|
// Find out which local files need to be uploaded
|
|
while ( localQuery.next() && localQuery.value(7).toString() == "" ) {
|
|
QString localName = localQuery.value(1).toString();
|
|
qint64 localSize = localQuery.value(2).toString().toLongLong();
|
|
QString localType = localQuery.value(3).toString();
|
|
qint64 localModified = localQuery.value(4).toString().toLongLong();
|
|
qint64 lastSync = localQuery.value(5).toString().toLongLong();
|
|
qint64 localPrevModified = localQuery.value(6).toString().toLongLong();
|
|
QDateTime localModifiedTime;
|
|
localModifiedTime.setTimeSpec(Qt::UTC);
|
|
localModifiedTime.setMSecsSinceEpoch(localModified);
|
|
QDateTime localPrevModifiedTime;
|
|
localPrevModifiedTime.setTimeSpec(Qt::UTC);
|
|
localPrevModifiedTime.setMSecsSinceEpoch(localPrevModified);
|
|
QDateTime lastSyncTime;
|
|
lastSyncTime.setTimeSpec(Qt::UTC);
|
|
lastSyncTime.setMSecsSinceEpoch(lastSync);
|
|
//syncDebug() << "LFile: " << localName << " Size: " << localSize << " vs "
|
|
// << localQuery.value(2).toString() << " type: " << localType ;
|
|
// Query the database and look for this file
|
|
QSqlQuery query = queryDBFileInfo(localName,"server_files_processing");
|
|
if( query.next() ) {
|
|
// Check when this file was last modified, and check to see
|
|
// when we last synced
|
|
//QString serverType = query.value(3).toString();
|
|
qint64 serverSize = query.value(2).toString().toLongLong();
|
|
qint64 serverModified = query.value(4).toString().toLongLong();
|
|
qint64 serverPrevModified = query.value(5).toString().toLongLong();
|
|
QDateTime serverModifiedTime;
|
|
serverModifiedTime.setTimeSpec(Qt::UTC);
|
|
serverModifiedTime.setMSecsSinceEpoch(serverModified);
|
|
QDateTime serverPrevModifiedTime;
|
|
serverPrevModifiedTime.setTimeSpec(Qt::UTC);
|
|
serverPrevModifiedTime.setMSecsSinceEpoch(serverPrevModified);
|
|
if( serverModifiedTime < localModifiedTime &&
|
|
localModifiedTime > lastSyncTime ) { // Server is older!
|
|
// Now check to see if the server too modified the file
|
|
if( localType == "collection" ) { // But already exists!
|
|
//serverDirs.append(localName);
|
|
} else {
|
|
if( serverPrevModifiedTime != serverModifiedTime &&
|
|
serverModifiedTime > lastSyncTime) {
|
|
// There is a conflict, both files got changed since the
|
|
// last time we synced
|
|
syncDebug() << "Conflict with sfile " << localName
|
|
<< serverModifiedTime << serverPrevModifiedTime
|
|
<< localModifiedTime << lastSyncTime;
|
|
setFileConflict(localName,localSize,
|
|
serverModifiedTime.toString(),
|
|
localModifiedTime.toString());
|
|
//syncDebug() << "UPLOAD: " << localName;
|
|
} else { // There is no conflict
|
|
mUploadingFiles.enqueue(FileInfo(localName,localSize));
|
|
mTotalToUpload +=localSize;
|
|
//syncDebug() << "File " << localName << " is newer than server!";
|
|
}
|
|
}
|
|
} else if ( serverModifiedTime > localModifiedTime &&
|
|
serverModifiedTime > lastSyncTime ) { // Server is newer
|
|
// Check to see if local file was also modified
|
|
if( localType == "collection" ) {
|
|
//localDirs.append(localName);
|
|
} else {
|
|
if( localPrevModifiedTime != localModifiedTime
|
|
&& localModifiedTime > lastSyncTime) {
|
|
// There is a conflict, both files got changed since the
|
|
// last time we synced
|
|
syncDebug() << "Conflict with lfile " << localName
|
|
<< serverModifiedTime << serverPrevModifiedTime
|
|
<< localModifiedTime << lastSyncTime;
|
|
setFileConflict(localName,serverSize,
|
|
serverModifiedTime.toString(),
|
|
localModifiedTime.toString());
|
|
} else { // There is no conflict
|
|
mDownloadingFiles.enqueue(FileInfo(localName,serverSize));
|
|
mTotalToDownload += serverSize;
|
|
//syncDebug() << "OLDER: " << localName;
|
|
}
|
|
}
|
|
} else { // Up to date
|
|
if(!mIsFirstRun)
|
|
copyLocalProcessing(localName);
|
|
}
|
|
} else { // Does not exist on server! Check if maybe it was deleted
|
|
QSqlQuery check = queryDBFileInfo(localName,"server_files");
|
|
if(!check.next()) {
|
|
//syncDebug() << "NEW: " << localName;
|
|
if ( localType == "collection") {
|
|
mMakeServerDirs.enqueue(localName);
|
|
} else {
|
|
mUploadingFiles.enqueue(FileInfo(localName,localSize));
|
|
mTotalToUpload += localSize;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Find out which files exist on the server but not locally. Set to download
|
|
QSqlQuery serverQuery = queryDBAllFiles("server_files_processing");
|
|
while ( serverQuery.next() ) {
|
|
QString serverName = serverQuery.value(1).toString();
|
|
qint64 serverSize = serverQuery.value(2).toString().toLongLong();
|
|
QString serverType = serverQuery.value(3).toString();
|
|
//syncDebug() << "SFile: " << serverName << " Size: " << serverSize << " vs "
|
|
// << serverQuery.value(2).toString() << " type: " << serverType ;
|
|
QSqlQuery query;
|
|
if(mIsFirstRun) {
|
|
query = queryDBFileInfo(serverName,"local_files_processing");
|
|
} else {
|
|
query = queryDBFileInfo(serverName,"local_files");
|
|
}
|
|
if( !query.next() ) {
|
|
// If this is the first run, it could also just be a deleted file
|
|
query = queryDBFileInfo(serverName,"local_files");
|
|
if(mIsFirstRun && !query.next()) { // Could have just been a deleted file.
|
|
if( serverType == "collection") {
|
|
localDirs.append(serverName);
|
|
} else {
|
|
mDownloadingFiles.enqueue(FileInfo(serverName,serverSize));
|
|
mTotalToDownload += serverSize;
|
|
}
|
|
syncDebug() << "DOWNLOAD new file: " << serverName;
|
|
}
|
|
}
|
|
}
|
|
for( int i = 0; i < mDownloadConflict.size(); i++ ) {
|
|
mTotalToDownload += mDownloadConflict[i].size;
|
|
}
|
|
for( int i = 0; i < mUploadingConflictFiles.size(); i++ ) {
|
|
mTotalToUpload += mUploadingConflictFiles[i].size;
|
|
}
|
|
mTotalToTransfer = mTotalToDownload+mTotalToUpload;
|
|
|
|
// Make local dirs
|
|
for(int i = 0; i < localDirs.size(); i++ ) {
|
|
QDir dir;
|
|
if (!dir.mkdir(mLocalDirectory+
|
|
stringRemoveBasePath(localDirs[i],mRemoteDirectory)) ) {
|
|
syncDebug() << "Could not make directory "+mLocalDirectory+
|
|
stringRemoveBasePath(localDirs[i],mRemoteDirectory);
|
|
} else {
|
|
emit toLog(tr("Created local directory: %1").arg(localDirs[i]));
|
|
//syncDebug() << "Made directory "+mLocalDirectory+localDirs[i];
|
|
}
|
|
}
|
|
|
|
// Delete removed files and reset the file status
|
|
deleteRemovedFiles();
|
|
mIsFirstRun = false;
|
|
|
|
// Let's get the ball rolling!
|
|
processNextStep();
|
|
}
|
|
|
|
void OwnCloudSync::setFileConflict(QString name, qint64 size, QString server_last,
|
|
QString local_last)
|
|
{
|
|
QSqlQuery conflict(QSqlDatabase::database(mAccountName));
|
|
QString conflictText = QString("UPDATE server_files_processing SET conflict='yes'"
|
|
" WHERE file_name='%1';").arg(name);
|
|
conflict.exec(conflictText);
|
|
conflictText = QString("UPDATE local_files_processing SET conflict='yes'"
|
|
" WHERE file_name='%1';").arg(name);
|
|
conflict.exec(conflictText);
|
|
conflictText = QString("INSERT INTO conflicts values('%1','','%2','%3');")
|
|
.arg(name).arg(server_last).arg(local_last);
|
|
conflict.exec(conflictText);
|
|
mDownloadConflict.enqueue(FileInfo(name,size));
|
|
mConflictsExist = true;
|
|
emit toMessage(tr("%1 has a conflict!").arg(mAccountName),
|
|
tr("File %1 conflicts.").arg(name),
|
|
QSystemTrayIcon::Warning);
|
|
emit conflictExists(this);
|
|
}
|
|
|
|
void OwnCloudSync::download( FileInfo file )
|
|
{
|
|
syncDebug() << "Will download file: " << file.name;
|
|
mCurrentFileSize = file.size;
|
|
mCurrentFile = file.name;
|
|
if(mDownloadingConflictingFile) {
|
|
mTransferState = tr("Downloading conflicting file ");
|
|
} else {
|
|
mTransferState = tr("Downloading ");
|
|
}
|
|
QNetworkReply *reply = mWebdav->get(file.name);
|
|
connect(reply, SIGNAL(downloadProgress(qint64,qint64)),
|
|
this, SLOT(transferProgress(qint64,qint64)));
|
|
restartRequestTimer();
|
|
updateStatus();
|
|
}
|
|
|
|
void OwnCloudSync::upload( FileInfo fileInfo)
|
|
{
|
|
QString localName = fileInfo.name;
|
|
localName = stringRemoveBasePath(localName,mRemoteDirectory);
|
|
mCurrentFileSize = fileInfo.size;
|
|
mCurrentFile = fileInfo.name;
|
|
mTransferState = tr("Uploading ");
|
|
syncDebug() << "Uploading File " +mLocalDirectory + mCurrentFile;
|
|
QFile file(mLocalDirectory+localName);
|
|
if (!file.open(QIODevice::ReadOnly)) {
|
|
syncDebug() << "File read error " + mLocalDirectory+localName+" Code: "
|
|
<< file.error();
|
|
return;
|
|
}
|
|
QNetworkReply *reply = mWebdav->put(fileInfo.name,mLocalDirectory+localName,
|
|
"_ocs_uploading.");
|
|
connect(reply, SIGNAL(uploadProgress(qint64,qint64)),
|
|
this, SLOT(transferProgress(qint64,qint64)));
|
|
restartRequestTimer();
|
|
updateStatus();
|
|
}
|
|
|
|
void OwnCloudSync::updateDBDownload(QString name)
|
|
{
|
|
// This seems redundant, a little, really.
|
|
QString fileName = mLocalDirectory+name;
|
|
QFileInfo file(fileName);
|
|
QString dbName = name;
|
|
if( mRemoteDirectory != "/") {
|
|
dbName = mRemoteDirectory + name;
|
|
}
|
|
|
|
QString downloadText;
|
|
if( mDownloadingConflictingFile ) {
|
|
downloadText = tr("Downloaded conflicting file: %1").arg(dbName);
|
|
} else {
|
|
// Check against the database
|
|
QSqlQuery query = queryDBFileInfo(dbName,"local_files");
|
|
if (query.next() ) { // We already knew about this file. Update.
|
|
QString updateStatement =
|
|
QString("UPDATE local_files SET file_size='%1',"
|
|
"last_modified='%2',last_sync='%3' where file_name='%4'")
|
|
.arg(file.size())
|
|
.arg(file.lastModified().toUTC()
|
|
.toMSecsSinceEpoch())
|
|
.arg(file.lastModified().toUTC()
|
|
.toMSecsSinceEpoch())
|
|
.arg(dbName);
|
|
query.exec(updateStatement);
|
|
} else { // We did not know about this file, add
|
|
QString addStatement = QString("INSERT INTO local_files (file_name,"
|
|
"file_size,file_type,last_modified,last_sync) "
|
|
"values('%1','%2','%3','%4','%5');")
|
|
.arg(dbName).arg(file.size())
|
|
.arg("file")
|
|
.arg(file.lastModified().toUTC().toMSecsSinceEpoch())
|
|
.arg(file.lastModified().toUTC().toMSecsSinceEpoch());
|
|
query.exec(addStatement);
|
|
}
|
|
copyServerProcessing(dbName);
|
|
downloadText = tr("Downloaded file: %1").arg(dbName);
|
|
}
|
|
emit toLog(downloadText);
|
|
//syncDebug() << "Did this get called?";
|
|
mTotalTransfered += mCurrentFileSize;
|
|
}
|
|
|
|
void OwnCloudSync::updateDBUpload(QString name)
|
|
{
|
|
QString fileName = mLocalDirectory+name;
|
|
QFileInfo file(fileName);
|
|
qint64 time = QDateTime::currentMSecsSinceEpoch();
|
|
//syncDebug() << "Debug: File: " << name << " Size: " << file.size();
|
|
|
|
// Check against the database
|
|
QSqlQuery query = queryDBFileInfo(name,"server_files");
|
|
if (query.next() ) { // We already knew about this file. Update.
|
|
copyServerProcessing(name);
|
|
QString updateStatement =
|
|
QString("UPDATE server_files SET file_size='%1',"
|
|
"last_modified='%2' where file_name='%3'")
|
|
.arg(file.size())
|
|
.arg(time).arg(name);
|
|
//syncDebug() << "Query: " << updateStatement;
|
|
query.exec(updateStatement);
|
|
// updateStatement =
|
|
// QString("UPDATE local_files_processing SET last_sync='%1'"
|
|
// "where file_name='%2'")
|
|
// .arg(time).arg(name);
|
|
// query.exec(updateStatement);
|
|
//syncDebug() << "Query: " << updateStatement;
|
|
} else { // We did not know about this file, add
|
|
QString addStatement = QString("INSERT INTO server_files (file_name,"
|
|
"file_size,file_type,last_modified) "
|
|
"values('%1','%2','%3','%4');")
|
|
.arg(name).arg(file.size())
|
|
.arg("file")
|
|
.arg(time);
|
|
query.exec(addStatement);
|
|
// QString updateStatement =
|
|
// QString("UPDATE local_files_processing SET file_size='%1',"
|
|
// "last_modified='%2',last_sync='%3' where file_name='%4'")
|
|
// .arg(file.size()).arg(time).arg(time).arg(name);
|
|
// query.exec(updateStatement);
|
|
}
|
|
emit toLog(tr("Uploaded file: %1").arg(name));
|
|
QString updateStatement =
|
|
QString("UPDATE local_files_processing SET last_sync='%1'"
|
|
"where file_name='%2'")
|
|
.arg(time).arg(name);
|
|
query.exec(updateStatement);
|
|
copyLocalProcessing(name);
|
|
mTotalTransfered += mCurrentFileSize;
|
|
processNextStep();
|
|
}
|
|
|
|
void OwnCloudSync::transferProgress(qint64 current, qint64 total)
|
|
{
|
|
stopRequestTimer();
|
|
// First update the current file progress bar
|
|
qint64 percent;
|
|
if ( total > 0 ) {
|
|
percent = 100*current/total;
|
|
emit progressFile(percent);
|
|
//ui->progressFile->setValue(percent);
|
|
} else {
|
|
percent = 0;
|
|
}
|
|
|
|
// Then update the total progress bar
|
|
qint64 additional = (mCurrentFileSize*percent)/100;
|
|
if (mTotalToTransfer > 0) {
|
|
emit progressTotal(100*(mTotalTransfered+additional)/mTotalToTransfer);
|
|
}
|
|
restartRequestTimer();
|
|
}
|
|
|
|
void OwnCloudSync::updateDBVersion(int fromVersion)
|
|
{
|
|
QSqlQuery query(QSqlDatabase::database(mAccountName));
|
|
switch(fromVersion) {
|
|
case 0: // Same as Version 1 (used in case a version is not found)
|
|
case 1:
|
|
case 2:
|
|
QString createVersion("create table db_version(\n"
|
|
"\tversion integer"
|
|
");");
|
|
QString updateVersion = QString("INSERT INTO db_version values('%1');")
|
|
.arg(_OCS_DB_VERSION);
|
|
QString createLocalProcessing("create table local_files_processing(\n"
|
|
"\tid INTEGER PRIMARY KEY ASC,\n"
|
|
"\tfile_name text unique,\n"
|
|
"\tfile_size text,\n"
|
|
"\tfile_type text,\n"
|
|
"\tlast_modified text,\n"
|
|
"\tlast_sync text,\n"
|
|
"\tprev_modified text,\n"
|
|
"\tconflict text\n"
|
|
");");
|
|
QString createServerProcessing("create table server_files_processing(\n"
|
|
"\tid INTEGER PRIMARY KEY ASC,\n"
|
|
"\tfile_name text unique,\n"
|
|
"\tfile_size text,\n"
|
|
"\tfile_type text,\n"
|
|
"\tlast_modified text,\n"
|
|
"\tprev_modified text,\n"
|
|
"\tconflict text\n"
|
|
");");
|
|
|
|
|
|
query.exec(createVersion);
|
|
query.exec(updateVersion);
|
|
query.exec(createLocalProcessing);
|
|
query.exec(createServerProcessing);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void OwnCloudSync::createDataBase()
|
|
{
|
|
syncDebug() << "Creating Database!";
|
|
if(!mDB.open()) {
|
|
syncDebug() << "Cannot open database for creation!";
|
|
syncDebug() << mDB.lastError().text();
|
|
mDBOpen = false;
|
|
} else {
|
|
mDBOpen = true;
|
|
}
|
|
QString createLocal("create table local_files(\n"
|
|
"\tid INTEGER PRIMARY KEY ASC,\n"
|
|
"\tfile_name text unique,\n"
|
|
"\tfile_size text,\n"
|
|
"\tfile_type text,\n"
|
|
"\tlast_modified text,\n"
|
|
"\tlast_sync text,\n"
|
|
"\tfound text,\n"
|
|
"\tprev_modified text,\n"
|
|
"\tconflict text\n"
|
|
");");
|
|
QString createServer("create table server_files(\n"
|
|
"\tid INTEGER PRIMARY KEY ASC,\n"
|
|
"\tfile_name text unique,\n"
|
|
"\tfile_size text,\n"
|
|
"\tfile_type text,\n"
|
|
"\tlast_modified text,\n"
|
|
"\tfound text,\n"
|
|
"\tprev_modified text,\n"
|
|
"\tconflict text\n"
|
|
");");
|
|
|
|
QString createLocalProcessing("create table local_files_processing(\n"
|
|
"\tid INTEGER PRIMARY KEY ASC,\n"
|
|
"\tfile_name text unique,\n"
|
|
"\tfile_size text,\n"
|
|
"\tfile_type text,\n"
|
|
"\tlast_modified text,\n"
|
|
"\tlast_sync text,\n"
|
|
"\tprev_modified text,\n"
|
|
"\tconflict text\n"
|
|
");");
|
|
QString createServerProcessing("create table server_files_processing(\n"
|
|
"\tid INTEGER PRIMARY KEY ASC,\n"
|
|
"\tfile_name text unique,\n"
|
|
"\tfile_size text,\n"
|
|
"\tfile_type text,\n"
|
|
"\tlast_modified text,\n"
|
|
"\tprev_modified text,\n"
|
|
"\tconflict text\n"
|
|
");");
|
|
|
|
QString createConflicts("create table conflicts(\n"
|
|
"\tfile_name text unique,\n"
|
|
"\tresolution text,\n"
|
|
"\tserver_modified text,\n"
|
|
"\tlocal_modified text\n"
|
|
");");
|
|
|
|
QString createConfig("create table config(\n"
|
|
"\thost text,\n"
|
|
"\tusername text,\n"
|
|
"\tpassword text,\n"
|
|
"\tlocaldir text,\n"
|
|
"\tupdatetime text,\n"
|
|
"\tenabled text,\n"
|
|
"\tremotedir text,\n"
|
|
"\tlastsync text\n"
|
|
");");
|
|
|
|
QString createFilters("create table filters(\n"
|
|
"\tfilter text\n"
|
|
");");
|
|
|
|
QString createVersion("create table db_version(\n"
|
|
"\tversion integer\n"
|
|
");");
|
|
|
|
QSqlQuery query(QSqlDatabase::database(mAccountName));
|
|
query.exec(createLocal);
|
|
query.exec(createServer);
|
|
query.exec(createLocalProcessing);
|
|
query.exec(createServerProcessing);
|
|
query.exec(createConfig);
|
|
query.exec(createConflicts);
|
|
query.exec(createFilters);
|
|
query.exec(createVersion);
|
|
|
|
}
|
|
|
|
void OwnCloudSync::readConfigFromDB()
|
|
{
|
|
QSqlQuery query(QSqlDatabase::database(mAccountName));
|
|
|
|
// First identify what database verion we have
|
|
query.exec("SELECT * from version;");
|
|
if( query.next() ) { // We found a version
|
|
int version = query.value(0).toInt();
|
|
if( version < _OCS_DB_VERSION )
|
|
updateDBVersion(version);
|
|
} else { // No version information, update from beginning
|
|
updateDBVersion(1);
|
|
}
|
|
query.exec("SELECT * from config;");
|
|
if(query.next()) {
|
|
mHost = query.value(0).toString();
|
|
mUsername = query.value(1).toString();
|
|
mReadPassword = true;
|
|
mPassword = query.value(2).toString();
|
|
mLocalDirectory = query.value(3).toString();
|
|
mRemoteDirectory = query.value(6).toString();
|
|
mUpdateTime = query.value(4).toString().toLongLong();
|
|
if( query.value(5).toString() == "yes" ) {
|
|
mIsEnabled = true;
|
|
} else {
|
|
mIsEnabled = false;
|
|
}
|
|
} else {
|
|
// There is no configuration on the db
|
|
mDBOpen = false;
|
|
}
|
|
|
|
// Now also read the filters on file
|
|
query.exec("SELECT * from filters;");
|
|
while(query.next()) {
|
|
mFilters.insert(query.value(0).toString());
|
|
}
|
|
}
|
|
|
|
void OwnCloudSync::removeFilter(QString filter)
|
|
{
|
|
mFilters.remove(filter);
|
|
QSqlQuery query(QSqlDatabase::database(mAccountName));
|
|
query.exec(QString("DELETE FROM filters WHERE filter='%1';").arg(filter));
|
|
}
|
|
|
|
void OwnCloudSync::addFilter(QString filter)
|
|
{
|
|
if(!mFilters.contains(filter)) {
|
|
mFilters.insert(filter);
|
|
QSqlQuery query(QSqlDatabase::database(mAccountName));
|
|
query.exec(QString("INSERT into filters values('%1');").arg(filter));
|
|
}
|
|
}
|
|
|
|
void OwnCloudSync::saveConfigToDB()
|
|
{
|
|
bool savePw = true;
|
|
#ifdef Q_OS_LINUX
|
|
savePw = false;
|
|
#endif
|
|
QSqlQuery query(QSqlDatabase::database(mAccountName));
|
|
query.exec("SELECT * from config;");
|
|
if(query.next()) { // Update
|
|
QString update = QString("UPDATE config SET host='%1',username='%2',"
|
|
"password='%3',localdir='%4',updatetime='%5',"
|
|
"enabled='%6',remotedir='%7';").arg(mHost)
|
|
.arg(mUsername).arg(savePw?mPassword:"").arg(mLocalDirectory)
|
|
.arg(mUpdateTime).arg(mIsEnabled?"yes":"no")
|
|
.arg(mRemoteDirectory);
|
|
query.exec(update);
|
|
} else { // Insert
|
|
QString add = QString("INSERT INTO config values('%1','%2',"
|
|
"'%3','%4','%5','%6','%7');").arg(mHost)
|
|
.arg(mUsername).arg(savePw?mPassword:"").arg(mLocalDirectory)
|
|
.arg(mUpdateTime).arg(mIsEnabled?"yes":"no")
|
|
.arg(mRemoteDirectory);
|
|
query.exec(add);
|
|
}
|
|
}
|
|
|
|
void OwnCloudSync::initialize()
|
|
{
|
|
initialize(mHost,mUsername,mPassword,mRemoteDirectory,mLocalDirectory,
|
|
mUpdateTime);
|
|
}
|
|
|
|
void OwnCloudSync::settingsAreFine()
|
|
{
|
|
if(mIsEnabled) {
|
|
start();
|
|
} else {
|
|
stop();
|
|
}
|
|
}
|
|
|
|
void OwnCloudSync::start()
|
|
{
|
|
if(mSyncTimer)
|
|
delete mSyncTimer;
|
|
mSyncTimer = new QTimer(this);
|
|
connect(mSyncTimer, SIGNAL(timeout()), this, SLOT(timeToSync()));
|
|
mSyncTimer->start(mUpdateTime*1000);
|
|
}
|
|
|
|
void OwnCloudSync::stop()
|
|
{
|
|
if( mNeedsSync && !mNotifySyncEmitted )
|
|
emit readyToSync(this);
|
|
|
|
if(mSyncTimer)
|
|
mSyncTimer->stop();
|
|
delete mSyncTimer;
|
|
mSyncTimer = 0;
|
|
}
|
|
|
|
void OwnCloudSync::deleteWatcher()
|
|
{
|
|
// Delete the watcher. Should only be called when we are quitting!!!
|
|
delete mFileWatcher;
|
|
mFileWatcher = 0;
|
|
}
|
|
|
|
void OwnCloudSync::localDirectoryChanged(QString name)
|
|
{
|
|
// Maybe this was caused by us renaming a file, just wait it out
|
|
while (mFileAccessBusy ) {
|
|
sleep(1);
|
|
}
|
|
// If it was caused by one directory being deleted, then delete it now
|
|
// and don't scan it!
|
|
QDir dir(name);
|
|
if( !dir.exists() ) {
|
|
name = stringRemoveBasePath(name,mLocalDirectory);
|
|
name = mRemoteDirectory + name;
|
|
emit toLog(tr("Local directory was deleted: %1").arg(name));
|
|
deleteFromServer(stringRemoveBasePath(name,mLocalDirectory));
|
|
return;
|
|
}
|
|
// Since we don't want to be scanning the directories every single
|
|
// time a file is changed (since temporary files could be the cause)
|
|
// instead we'll add them to a list and have a separate timer
|
|
// randomly go through them
|
|
QString relativeName(name);
|
|
relativeName = stringRemoveBasePath(relativeName,mLocalDirectory);
|
|
// Replace spaces because it may confuse QSet
|
|
relativeName.replace(" ","_sssspace_");
|
|
if( !mScanDirectoriesSet.contains(relativeName) ) {
|
|
// Add to the list
|
|
mScanDirectoriesSet.insert(relativeName);
|
|
mScanDirectories.enqueue(relativeName);
|
|
}
|
|
}
|
|
|
|
void OwnCloudSync::localFileChanged(QString name)
|
|
{
|
|
//syncDebug() << "Checking file status: " << name;
|
|
QFileInfo info(name);
|
|
name = stringRemoveBasePath(name,mLocalDirectory);
|
|
if( info.exists() ) { // Ok, file did not get deleted
|
|
updateDBLocalFile(name,info.size(),
|
|
info.lastModified().toUTC().toMSecsSinceEpoch(),"file");
|
|
} else { // File got deleted (or moved!) I can't do anything about
|
|
// the moves for now. But I can delete! So do that for now :)
|
|
name = mRemoteDirectory + name;
|
|
emit toLog(tr("Local file was deleted: %1").arg(name));
|
|
deleteFromServer(stringRemoveBasePath(name,mLocalDirectory));
|
|
}
|
|
}
|
|
|
|
void OwnCloudSync::scanLocalDirectoryForNewFiles(QString path)
|
|
{
|
|
QString remote;
|
|
//path = path=="/"?"":path;
|
|
if(mRemoteDirectory != "/") {
|
|
remote = mRemoteDirectory+"/";
|
|
}
|
|
//syncDebug() << "Scanning local directory: " << path;
|
|
QDir dir(mLocalDirectory+path);
|
|
dir.setFilter(QDir::Files|QDir::NoDot|QDir::NoDotDot|QDir::AllEntries
|
|
|QDir::Hidden);
|
|
QStringList list = dir.entryList();
|
|
for( int i = 0; i < list.size(); i++ ) {
|
|
// Skip current and previous directory and conflict related files
|
|
QString name = list.at(i);
|
|
if( isFileFiltered(name) ) {
|
|
continue;
|
|
}
|
|
|
|
QSqlQuery query(QSqlDatabase::database(mAccountName));
|
|
query.exec(QString("SELECT * from local_files where file_name='%1'")
|
|
.arg(path+remote+list[i]));
|
|
if( !query.next() ) { // Ok, this file does not exist. It might be a
|
|
// directory, however, so let's check again!
|
|
query.exec(QString("SELECT * from local_files where "
|
|
"file_name='%1/'").arg(path+remote+list[i]));
|
|
if( !query.next() ) { // Definitely does not exist! Good!
|
|
// File really doesn't exist!!!
|
|
//syncDebug() << "New file found!" << path +remote + list[i];
|
|
processLocalFile(mLocalDirectory+path+list[i]);
|
|
}
|
|
}
|
|
//QString name = pathi+"/"+list[i];
|
|
}
|
|
}
|
|
|
|
void OwnCloudSync::saveDBToFile()
|
|
{
|
|
if( sqlite3_util::sqliteDBMemFile( mDB, mDBFileName, true ) ) {
|
|
syncDebug() << "Successfully saved DB to file!";
|
|
} else {
|
|
syncDebug() << "Failed to save DB to file!";
|
|
}
|
|
#ifdef Q_OS_LINUX
|
|
saveWalletPassword();
|
|
#endif
|
|
}
|
|
|
|
void OwnCloudSync::loadDBFromFile()
|
|
{
|
|
if( sqlite3_util::sqliteDBMemFile( mDB, mDBFileName, false ) ) {
|
|
syncDebug() << "Successfully loaded DB from file!";
|
|
} else {
|
|
syncDebug() << "Failed to load DB from file!";
|
|
}
|
|
}
|
|
|
|
void OwnCloudSync::deleteRemovedFiles()
|
|
{
|
|
QStringList localCopy;
|
|
QStringList serverCopy;
|
|
// Any file that has not been found will be deleted!
|
|
if( mIsFirstRun ) {
|
|
//syncDebug() << "Looking for server files to delete!";
|
|
// Since we don't always query local files except for the first run
|
|
// only do this if it is the first run
|
|
QSqlQuery local(QSqlDatabase::database(mAccountName));
|
|
QSqlQuery localFound(QSqlDatabase::database(mAccountName));
|
|
|
|
// First delete the files
|
|
local.exec("SELECT file_name from local_files WHERE file_type='file';");
|
|
while(local.next()) {
|
|
localFound.exec(QString("SELECT file_name from "
|
|
"local_files_processing WHERE "
|
|
"file_name='%1';")
|
|
.arg(local.value(0).toString()));
|
|
if(!localFound.next()) {
|
|
// Local file as deleted. Delete from server too.
|
|
//syncDebug() << "Deleting file from server: " << local.value(0).toString();
|
|
//emit toLog(tr("File claims to be not found: %1").arg(
|
|
// local.value(0).toString()));
|
|
deleteFromServer(local.value(0).toString());
|
|
} else {
|
|
//copyLocalProcessing(local.value(0).toString());
|
|
localCopy.push_back(local.value(0).toString());
|
|
}
|
|
}
|
|
|
|
// Then delete the collections
|
|
local.exec("SELECT file_name from local_files WHERE "
|
|
"file_type='collection';");
|
|
while(local.next()) {
|
|
localFound.exec(QString("SELECT file_name from "
|
|
"local_files_processing WHERE "
|
|
"file_name='%1';")
|
|
.arg(local.value(0).toString()));
|
|
if(!localFound.next()) {
|
|
// Local file as deleted. Delete from server too.
|
|
//syncDebug() << "Deleting file from server: " << local.value(0).toString();
|
|
//emit toLog(tr("File claims to be not found: %1").arg(
|
|
// local.value(0).toString()));
|
|
deleteFromServer(local.value(0).toString());
|
|
} else {
|
|
//copyLocalProcessing(local.value(0).toString());
|
|
localCopy.push_back(local.value(0).toString());
|
|
}
|
|
}
|
|
}
|
|
|
|
//syncDebug() << "Looking for local files to delete!";
|
|
QSqlQuery server(QSqlDatabase::database(mAccountName));
|
|
QSqlQuery serverFound(QSqlDatabase::database(mAccountName));
|
|
// First delete the files
|
|
// First delete the files
|
|
server.exec("SELECT file_name from server_files WHERE file_type='file';");
|
|
while(server.next()) {
|
|
serverFound.exec(QString("SELECT file_name from "
|
|
"server_files_processing WHERE "
|
|
"file_name='%1';")
|
|
.arg(server.value(0).toString()));
|
|
if(!serverFound.next()) {
|
|
// Local file as deleted. Delete from server too.
|
|
//syncDebug() << "Deleting file from server: " << server.value(0).toString();
|
|
//emit toLog(tr("File claims to be not found: %1").arg(
|
|
// server.value(0).toString()));
|
|
syncDebug() << "Will delete local file: " << server.value(0).toString();
|
|
deleteFromLocal(server.value(0).toString(),false);
|
|
} else {
|
|
//copyServerProcessing(server.value(0).toString());
|
|
serverCopy.push_back(server.value(0).toString());
|
|
}
|
|
}
|
|
|
|
// Then delete the collections
|
|
server.exec("SELECT file_name from server_files WHERE "
|
|
"file_type='collection';");
|
|
while(server.next()) {
|
|
serverFound.exec(QString("SELECT file_name from "
|
|
"server_files_processing WHERE "
|
|
"file_name='%1';")
|
|
.arg(server.value(0).toString()));
|
|
if(!serverFound.next()) {
|
|
// Local file as deleted. Delete from server too.
|
|
//syncDebug() << "Deleting file from server: " << server.value(0).toString();
|
|
//emit toLog(tr("File claims to be not found: %1").arg(
|
|
// server.value(0).toString()));
|
|
deleteFromLocal(server.value(0).toString(),true);
|
|
} else {
|
|
//copyServerProcessing(server.value(0).toString());
|
|
serverCopy.push_back(server.value(0).toString());
|
|
}
|
|
}
|
|
|
|
// Now copy the processing entries over to the main table
|
|
for(int i = 0; i < localCopy.size(); i++ ) {
|
|
copyLocalProcessing(localCopy[i]);
|
|
}
|
|
for(int i = 0; i < serverCopy.size(); i++ ) {
|
|
copyServerProcessing(serverCopy[i]);
|
|
}
|
|
|
|
}
|
|
|
|
void OwnCloudSync::deleteFromLocal(QString name, bool isDir)
|
|
{
|
|
// Remove the watcher before deleting.
|
|
QString localName = stringRemoveBasePath(name,mRemoteDirectory);
|
|
mFileWatcher->removePath(mLocalDirectory+localName);
|
|
if(!isDir) {
|
|
if( !QFile::remove(mLocalDirectory+localName ) ) {
|
|
syncDebug() << "File deletion failed: " << mLocalDirectory+localName;
|
|
return;
|
|
}
|
|
emit toLog(tr("Deleted local file: %1").arg(name));
|
|
} else {
|
|
QDir dir;
|
|
if( !dir.rmdir(mLocalDirectory+localName) ) {
|
|
syncDebug() << "Directory deletion failed: "
|
|
<< mLocalDirectory+localName;
|
|
return;
|
|
}
|
|
emit toLog(tr("Deleted local directory: %1").arg(name));
|
|
}
|
|
dropFromDB("local_files","file_name",name);
|
|
dropFromDB("server_files","file_name",name);
|
|
dropFromDB("local_files_processing","file_name",name);
|
|
dropFromDB("server_files_processing","file_name",name);
|
|
}
|
|
|
|
void OwnCloudSync::deleteFromServer(QString name)
|
|
{
|
|
// Delete from server
|
|
mWebdav->deleteFile(name);
|
|
emit toLog(tr("Deleting from server: %1").arg(name));
|
|
dropFromDB("server_files","file_name",name);
|
|
dropFromDB("local_files","file_name",name);
|
|
dropFromDB("server_files_processing","file_name",name);
|
|
dropFromDB("local_files_processing","file_name",name);
|
|
}
|
|
|
|
void OwnCloudSync::dropFromDB(QString table, QString column, QString condition)
|
|
{
|
|
QSqlQuery drop(QSqlDatabase::database(mAccountName));
|
|
drop.exec("DELETE FROM "+table+" WHERE "+column+"='"+condition+"';");
|
|
}
|
|
|
|
void OwnCloudSync::processFileConflict(QString name, QString wins)
|
|
{
|
|
QString localName = stringRemoveBasePath(name,mRemoteDirectory);
|
|
if( wins == "local" ) {
|
|
QFileInfo info(mLocalDirectory+localName);
|
|
QFile::remove(mLocalDirectory+getConflictName(localName));
|
|
mUploadingConflictFiles.enqueue(FileInfo(name,info.size()));
|
|
mUploadingConflictFilesSet.insert(name.replace(" ","_sssspace_"));
|
|
} else {
|
|
// Stop watching the old file, since it will get removed
|
|
mFileWatcher->removePath(mLocalDirectory+localName);
|
|
QFileInfo info(mLocalDirectory+getConflictName(localName));
|
|
qint64 last = info.lastModified().toMSecsSinceEpoch();
|
|
QFile::remove(mLocalDirectory+localName);
|
|
QFile::rename(mLocalDirectory+getConflictName(localName),
|
|
mLocalDirectory+localName);
|
|
QSqlQuery query(QSqlDatabase::database(mAccountName));
|
|
QString statement = QString("UPDATE local_files SET last_sync='%1'"
|
|
"WHERE file_name='%2';").arg(last).arg(name);
|
|
query.exec(statement);
|
|
statement = QString("UPDATE local_files_processing SET last_sync='%1'"
|
|
"WHERE file_name='%2';").arg(last).arg(name);
|
|
query.exec(statement);
|
|
|
|
// Add back to the watcher
|
|
mFileWatcher->addPath(mLocalDirectory+localName);
|
|
clearFileConflict(name);
|
|
}
|
|
}
|
|
|
|
void OwnCloudSync::clearFileConflict(QString name)
|
|
{
|
|
QSqlQuery query(QSqlDatabase::database(mAccountName));
|
|
QString statement = QString("DELETE FROM conflicts where file_name='%1';")
|
|
.arg(name);
|
|
query.exec(statement);
|
|
statement = QString("UPDATE local_files_processing set conflict='' where file_name='%1';")
|
|
.arg(name);
|
|
query.exec(statement);
|
|
statement = QString("UPDATE server_files_processing set conflict='' where file_name='%1';")
|
|
.arg(name);
|
|
query.exec(statement);
|
|
statement = QString("UPDATE local_files set conflict='' where file_name='%1';")
|
|
.arg(name);
|
|
query.exec(statement);
|
|
statement = QString("UPDATE server_files set conflict='' where file_name='%1';")
|
|
.arg(name);
|
|
query.exec(statement);
|
|
}
|
|
|
|
QString OwnCloudSync::getConflictName(QString name)
|
|
{
|
|
QFileInfo info(name);
|
|
return QString(info.absolutePath()+
|
|
"/_ocs_serverconflict."+info.fileName());
|
|
}
|
|
|
|
void OwnCloudSync::initialize(QString host, QString user, QString pass,
|
|
QString remote, QString local, qint64 time)
|
|
{
|
|
mHost = stringRemoveBasePath(host,"/files/webdav.php");
|
|
mUsername = user;
|
|
mPassword = pass;
|
|
mRemoteDirectory = remote;
|
|
mLocalDirectory = local;
|
|
mUpdateTime = time;
|
|
// Initialize WebDAV
|
|
mWebdav->initialize(mHost+"/files/webdav.php",
|
|
mUsername,mPassword,"/files/webdav.php");
|
|
|
|
// Create the local directory if it doesn't exist
|
|
QDir localDir(mLocalDirectory);
|
|
if(!localDir.exists()) {
|
|
localDir.mkpath(mLocalDirectory);
|
|
}
|
|
|
|
// Create a File System Watcher
|
|
delete mFileWatcher;
|
|
mFileWatcher = new QFileSystemWatcher(this);
|
|
connect(mFileWatcher,SIGNAL(fileChanged(QString)),
|
|
this, SLOT(localFileChanged(QString)));
|
|
connect(mFileWatcher,SIGNAL(directoryChanged(QString)),
|
|
this, SLOT(localDirectoryChanged(QString)));
|
|
|
|
mFileWatcher->addPath(mLocalDirectory+"/");
|
|
saveConfigToDB();
|
|
saveDBToFile();
|
|
mSettingsCheck = true;
|
|
mWebdav->dirList(remote+"/");
|
|
mSyncPosition = CHECKSETTINGS;
|
|
restartRequestTimer();
|
|
}
|
|
|
|
QStringList OwnCloudSync::getFilterList()
|
|
{
|
|
QStringList list;
|
|
QList<QString> filters = mFilters.toList();
|
|
for( int i = 0; i < filters.size(); i++ ) {
|
|
list << filters[i];
|
|
//syncDebug() << filters[i];
|
|
}
|
|
return list;
|
|
}
|
|
|
|
bool OwnCloudSync::isFileFiltered(QString name)
|
|
{
|
|
// Standard filters applicable to *ALL* files
|
|
if( name == "." || name == ".." ||
|
|
name.contains("_ocs_serverconflict.") ||
|
|
name.contains("_ocs_uploading.") ||
|
|
name.contains("_ocs_downloading." )) {
|
|
//syncDebug() << "File: " +name+" ignored by " + mAccountName;
|
|
return true;
|
|
}
|
|
QList<QString> list = mFilters.toList();
|
|
list.append( mGlobalFilters->toList() );
|
|
|
|
// Else, look through the filters and see if this file is excluded
|
|
for( int i = 0; i < list.size(); i++ ) {
|
|
QString filter = list[i];
|
|
if(filter.contains("*")) { // Must build general expression
|
|
filter.replace("?","\\\?");
|
|
filter.replace(".","\\\.");
|
|
filter.replace("*",".*");
|
|
QRegExp reg(filter);
|
|
if( name.contains(reg) ) {
|
|
//syncDebug() << "File: " +name+" ignored by " + mAccountName + " because of " + filter;
|
|
return true;
|
|
}
|
|
|
|
} else if( name.contains(filter) ) {
|
|
//syncDebug() << "File: " +name+" ignored by " + mAccountName + " because of " + filter;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void OwnCloudSync::deleteAccount()
|
|
{
|
|
// Stop all transfer processes
|
|
mHardStop = true;
|
|
|
|
// Stop and delete the timer
|
|
if(mSyncTimer) {
|
|
mSyncTimer->stop();
|
|
}
|
|
|
|
// Delete the database
|
|
mDB.close();
|
|
QFile dbFile(mConfigDirectory+"/"+mAccountName+".db");
|
|
dbFile.remove();
|
|
}
|
|
|
|
void OwnCloudSync::requestTimedout()
|
|
{
|
|
//emit toLog(tr("The request timed out"));
|
|
mBusy = false;
|
|
start();
|
|
emit toLog(tr("Sync timedout %1: %2").arg(mAccountName)
|
|
.arg(QDateTime::currentDateTime().toString()));
|
|
emit finishedSync(this);
|
|
mLastSyncAborted = mSyncPosition;
|
|
stopRequestTimer();
|
|
}
|
|
|
|
void OwnCloudSync::restartRequestTimer()
|
|
{
|
|
mRequestTimer->start(7000);
|
|
}
|
|
|
|
void OwnCloudSync::stopRequestTimer()
|
|
{
|
|
mRequestTimer->stop();
|
|
}
|
|
|
|
bool OwnCloudSync::needsSync()
|
|
{
|
|
if(mLastSyncAborted != SYNCFINISHED ) {
|
|
return false;
|
|
}
|
|
return mNeedsSync;
|
|
}
|
|
|
|
#ifdef Q_OS_LINUX
|
|
|
|
void OwnCloudSync::requestPassword()
|
|
{
|
|
QMap<QString,QString> map;
|
|
mWallet->readMap(mAccountName,map);
|
|
if(map.size()) {
|
|
mPassword = map[mAccountName];
|
|
initialize();
|
|
}
|
|
}
|
|
|
|
void OwnCloudSync::walletOpened(bool ok)
|
|
{
|
|
if( (ok && (mWallet->hasFolder("owncloud_sync"))
|
|
|| mWallet->createFolder("owncloud_sync"))
|
|
&& mWallet->setFolder("owncloud_sync")) {
|
|
//emit toLog("Wallet opened!");
|
|
//syncDebug() << "Wallet opened!" <<
|
|
// KWallet::Wallet::FormDataFolder() ;
|
|
if (mReadPassword ) {
|
|
requestPassword();
|
|
}
|
|
} else {
|
|
syncDebug() << "Error opening wallet";
|
|
}
|
|
}
|
|
|
|
void OwnCloudSync::saveWalletPassword()
|
|
{
|
|
QMap<QString,QString> map;
|
|
map[mAccountName] = mPassword;
|
|
if( mWallet )
|
|
mWallet->writeMap(mAccountName,map);
|
|
}
|
|
|
|
#endif
|
|
|
|
QString OwnCloudSync::stringRemoveBasePath(QString path, QString base)
|
|
{
|
|
if( base != "/" ) {
|
|
path.replace(QRegExp("^"+base),"");
|
|
}
|
|
return path;
|
|
}
|
|
|
|
void OwnCloudSync::serverDirectoryCreated(QString name)
|
|
{
|
|
emit toLog(tr("Created directory on server: %1").arg(name));
|
|
processNextStep();
|
|
}
|
|
|
|
void OwnCloudSync::copyLocalProcessing(QString fileName)
|
|
{
|
|
//syncDebug() << "Copying DB Process Local: " << fileName;
|
|
QSqlQuery queryProcessing(QSqlDatabase::database(mAccountName));
|
|
QSqlQuery query(QSqlDatabase::database(mAccountName));
|
|
queryProcessing.exec(QString("SELECT * FROM local_files_processing WHERE "
|
|
"file_name='%1';").arg(fileName));
|
|
if(queryProcessing.next()) {
|
|
query.exec(QString("DELETE FROM local_files WHERE file_name='%1';")
|
|
.arg(fileName));
|
|
query.exec(QString("INSERT INTO local_files (file_name,"
|
|
"file_size,file_type,last_modified,last_sync,"
|
|
"prev_modified,conflict) "
|
|
"values('%1','%2','%3','%4','%5','%6','%7');")
|
|
.arg(queryProcessing.value(1).toString())
|
|
.arg(queryProcessing.value(2).toString())
|
|
.arg(queryProcessing.value(3).toString())
|
|
.arg(queryProcessing.value(4).toString())
|
|
.arg(queryProcessing.value(5).toString())
|
|
.arg(queryProcessing.value(6).toString())
|
|
.arg(queryProcessing.value(7).toString())
|
|
);
|
|
}
|
|
queryProcessing.exec(QString("DELETE FROM local_files_processing WHERE "
|
|
"file_name='%1';").arg(fileName));
|
|
}
|
|
|
|
void OwnCloudSync::copyServerProcessing(QString fileName)
|
|
{
|
|
//syncDebug() << "Copying DB Process Server: " << fileName;
|
|
QSqlQuery queryProcessing(QSqlDatabase::database(mAccountName));
|
|
QSqlQuery query(QSqlDatabase::database(mAccountName));
|
|
queryProcessing.exec(QString("SELECT * FROM server_files_processing WHERE "
|
|
"file_name='%1';").arg(fileName));
|
|
if(queryProcessing.next()) {
|
|
query.exec(QString("DELETE FROM server_files WHERE file_name='%1';")
|
|
.arg(fileName));
|
|
query.exec(QString("INSERT INTO server_files (file_name,"
|
|
"file_size,file_type,last_modified,prev_modified,conflict) "
|
|
"values('%1','%2','%3','%4','%5','%6');")
|
|
.arg(queryProcessing.value(1).toString())
|
|
.arg(queryProcessing.value(2).toString())
|
|
.arg(queryProcessing.value(3).toString())
|
|
.arg(queryProcessing.value(4).toString())
|
|
.arg(queryProcessing.value(5).toString())
|
|
.arg(queryProcessing.value(6).toString())
|
|
);
|
|
}
|
|
queryProcessing.exec(QString("DELETE FROM server_files_processing WHERE "
|
|
"file_name='%1';").arg(fileName));
|
|
}
|