QByteArray remUrl = options.target_url.toUtf8();
// Find the folder and the original owncloud url
- QStringList splitted = url.path().split(account->davPath());
+ QStringList splitted = url.path().split("/" + account->davPath());
url.setPath(splitted.value(0));
url.setScheme(url.scheme().replace("owncloud", "http"));
- QString folder = splitted.value(1);
+
+ QUrl credentialFreeUrl = url;
+ credentialFreeUrl.setUserName(QString());
+ credentialFreeUrl.setPassword(QString());
+
+ // Remote folders typically start with a / and don't end with one
+ QString folder = "/" + splitted.value(1);
+ if (folder.endsWith("/") && folder != "/") {
+ folder.chop(1);
+ }
SimpleSslErrorHandler *sslErrorHandler = new SimpleSslErrorHandler;
}
Cmd cmd;
- SyncJournalDb db;
+ QString dbPath = options.source_dir + SyncJournalDb::makeDbName(credentialFreeUrl, folder, user);
+ SyncJournalDb db(dbPath);
if (!selectiveSyncList.empty()) {
selectiveSyncFixup(&db, selectiveSyncList);
FolderDefinition definition;
definition.localPath = FolderDefinition::prepareLocalPath(
folderWizard->field(QLatin1String("sourceFolder")).toString());
- definition.targetPath = folderWizard->property("targetPath").toString();
+ definition.targetPath = FolderDefinition::prepareTargetPath(
+ folderWizard->property("targetPath").toString());
{
QDir dir(definition.localPath);
namespace OCC {
+const char oldJournalPath[] = ".csync_journal.db";
+
Folder::Folder(const FolderDefinition& definition,
AccountState* accountState,
QObject* parent)
, _lastSyncDuration(0)
, _consecutiveFailingSyncs(0)
, _consecutiveFollowUpSyncs(0)
+ , _journal(_definition.absoluteJournalPath())
, _fileLog(new SyncRunFileLog)
+ , _saveBackwardsCompatible(false)
{
qRegisterMetaType<SyncFileItemVector>("SyncFileItemVector");
qRegisterMetaType<SyncFileItem::Direction>("SyncFileItem::Direction");
checkLocalPath();
_syncResult.setFolder(_definition.alias);
- _journal.setAccountParameterForFilePath(path(), remoteUrl(), remotePath());
_engine.reset(new SyncEngine(_accountState->account(), path(), remotePath(), &_journal));
// pass the setting if hidden files are to be ignored, will be read in csync_update
}
}
- bool inFolders = _journal.mayMigrateDbLocation() || oneAccountOnly;
+ bool compatible = _saveBackwardsCompatible || oneAccountOnly;
- if (inFolders) {
+ if (compatible) {
settings->beginGroup(QLatin1String("Folders"));
} else {
settings->beginGroup(QLatin1String("Multifolders"));
}
}
+void Folder::setSaveBackwardsCompatible(bool save)
+{
+ _saveBackwardsCompatible = save;
+}
+
void Folder::slotAboutToRemoveAllFiles(SyncFileItem::Direction, bool *cancel)
{
ConfigFile cfgFile;
{
settings.beginGroup(FolderMan::escapeAlias(folder.alias));
settings.setValue(QLatin1String("localPath"), folder.localPath);
+ settings.setValue(QLatin1String("journalPath"), folder.journalPath);
settings.setValue(QLatin1String("targetPath"), folder.targetPath);
settings.setValue(QLatin1String("paused"), folder.paused);
settings.setValue(QLatin1String("ignoreHiddenFiles"), folder.ignoreHiddenFiles);
settings.beginGroup(alias);
folder->alias = FolderMan::unescapeAlias(alias);
folder->localPath = settings.value(QLatin1String("localPath")).toString();
+ folder->journalPath = settings.value(QLatin1String("journalPath")).toString();
folder->targetPath = settings.value(QLatin1String("targetPath")).toString();
folder->paused = settings.value(QLatin1String("paused")).toBool();
folder->ignoreHiddenFiles = settings.value(QLatin1String("ignoreHiddenFiles"), QVariant(true)).toBool();
// code we assum /, so clean it up now.
folder->localPath = prepareLocalPath(folder->localPath);
+ // Target paths also have a convention
+ folder->targetPath = prepareTargetPath(folder->targetPath);
+
return true;
}
return p;
}
+QString FolderDefinition::prepareTargetPath(const QString &path)
+{
+ QString p = path;
+ if (p.endsWith(QLatin1Char('/'))) {
+ p.chop(1);
+ }
+ // Doing this second ensures the empty string or "/" come
+ // out as "/".
+ if (!p.startsWith(QLatin1Char('/'))) {
+ p.prepend(QLatin1Char('/'));
+ }
+ return p;
+}
+
+QString FolderDefinition::absoluteJournalPath() const
+{
+ return QDir(localPath).filePath(journalPath);
+}
+
+QString FolderDefinition::defaultJournalPath(AccountPtr account)
+{
+ return SyncJournalDb::makeDbName(account->url(), targetPath, account->credentials()->user());
+}
+
} // namespace OCC
QString alias;
/// path on local machine
QString localPath;
+ /// path to the journal, usually relative to localPath
+ QString journalPath;
/// path on remote
QString targetPath;
/// whether the folder is paused
/// Ensure / as separator and trailing /.
static QString prepareLocalPath(const QString& path);
+
+ /// Ensure starting / and no ending /.
+ static QString prepareTargetPath(const QString& path);
+
+ /// journalPath relative to localPath.
+ QString absoluteJournalPath() const;
+
+ /// Returns the relative journal path that's appropriate for this folder and account.
+ QString defaultJournalPath(AccountPtr account);
};
/**
*/
void scheduleThisFolderSoon();
+ /**
+ * Migration: When this flag is true, this folder will save to
+ * the backwards-compatible 'Folders' section in the config file.
+ */
+ void setSaveBackwardsCompatible(bool save);
+
signals:
void syncStateChange();
void syncStarted();
QScopedPointer<SyncRunFileLog> _fileLog;
QTimer _scheduleSelfTimer;
+
+ /**
+ * When the same local path is synced to multiple accounts, only one
+ * of them can be stored in the settings in a way that's compatible
+ * with old clients that don't support it. This flag marks folders
+ * that shall be written in a backwards-compatible way, by being set
+ * on the *first* Folder instance that was configured for each local
+ * path.
+ */
+ bool _saveBackwardsCompatible;
};
}
return _folderMap.size();
}
-void FolderMan::setupFoldersHelper(QSettings &settings, AccountStatePtr account, bool mayMigrateOldDb)
+void FolderMan::setupFoldersHelper(QSettings &settings, AccountStatePtr account, bool backwardsCompatible)
{
foreach (const auto& folderAlias, settings.childGroups()) {
FolderDefinition folderDefinition;
if (FolderDefinition::load(settings, folderAlias, &folderDefinition)) {
- Folder* f = addFolderInternal(std::move(folderDefinition), account.data(), false);
+ // Migration: Old settings don't have journalPath
+ if (folderDefinition.journalPath.isEmpty()) {
+ folderDefinition.journalPath = folderDefinition.defaultJournalPath(account->account());
+ }
+ folderDefinition.defaultJournalPath(account->account());
+ // Migration: If an old db is found, move it to the new name.
+ if (backwardsCompatible) {
+ SyncJournalDb::maybeMigrateDb(folderDefinition.localPath, folderDefinition.absoluteJournalPath());
+ }
+
+ Folder* f = addFolderInternal(std::move(folderDefinition), account.data());
if (f) {
- f->journalDb()->setMayMigrateDbLocation(mayMigrateOldDb);
+ // Migration: Mark folders that shall be saved in a backwards-compatible way
+ if (backwardsCompatible) {
+ f->setSaveBackwardsCompatible(true);
+ }
scheduleFolder(f);
emit folderSyncStateChange(f);
}
folderDefinition.paused = paused;
folderDefinition.ignoreHiddenFiles = ignoreHiddenFiles();
- folder = addFolderInternal(folderDefinition, accountState, false);
+ folder = addFolderInternal(folderDefinition, accountState);
if (folder) {
QStringList blackList = settings.value( QLatin1String("blackList")).toStringList();
if (!blackList.empty()) {
Folder* FolderMan::addFolder(AccountState* accountState, const FolderDefinition& folderDefinition)
{
- auto folder = addFolderInternal(folderDefinition, accountState, true);
+ // Choose a db filename
+ auto definition = folderDefinition;
+ definition.journalPath = definition.defaultJournalPath(accountState->account());
+
+ if (!ensureJournalGone(definition.absoluteJournalPath())) {
+ return 0;
+ }
+
+ auto folder = addFolderInternal(definition, accountState);
+
+ // Migration: The first account that's configured for a local folder shall
+ // be saved in a backwards-compatible way.
+ bool oneAccountOnly = true;
+ foreach (Folder* other, FolderMan::instance()->map()) {
+ if (other != folder && other->cleanPath() == folder->cleanPath()) {
+ oneAccountOnly = false;
+ break;
+ }
+ }
+ folder->setSaveBackwardsCompatible(oneAccountOnly);
if(folder) {
folder->saveToSettings();
}
Folder* FolderMan::addFolderInternal(FolderDefinition folderDefinition,
- AccountState* accountState,
- bool wipeJournal)
+ AccountState* accountState)
{
auto alias = folderDefinition.alias;
int count = 0;
auto folder = new Folder(folderDefinition, accountState, this );
- if (wipeJournal) {
- if (!ensureJournalGone(folder->journalDb()->databaseFilePath())) {
- delete folder;
- return 0;
- }
- }
-
qDebug() << "Adding folder to Folder Map " << folder << folder->alias();
_folderMap[folder->alias()] = folder;
if (folder->syncPaused()) {
* does not set an account on the new folder.
*/
Folder* addFolderInternal(FolderDefinition folderDefinition,
- AccountState* accountState,
- bool wipeJournal);
+ AccountState* accountState);
/* unloads a folder object, does not delete it */
void unloadFolder( Folder * );
// restarts the application (Linux only)
void restartApplication();
- void setupFoldersHelper(QSettings& settings, AccountStatePtr account, bool mayMigrateOldDb);
+ void setupFoldersHelper(QSettings& settings, AccountStatePtr account, bool backwardsCompatible);
QSet<Folder*> _disabledFolders;
Folder::Map _folderMap;
qDebug() << "Adding folder definition for" << localFolder << _remoteFolder;
FolderDefinition folderDefinition;
folderDefinition.localPath = localFolder;
- folderDefinition.targetPath = _remoteFolder;
+ folderDefinition.targetPath = FolderDefinition::prepareTargetPath(_remoteFolder);
folderDefinition.ignoreHiddenFiles = folderMan->ignoreHiddenFiles();
auto f = folderMan->addFolder(account, folderDefinition);
namespace OCC {
-SyncJournalDb::SyncJournalDb( QObject *parent) :
+SyncJournalDb::SyncJournalDb(const QString& dbFilePath, QObject *parent) :
QObject(parent),
- _transaction(0),
- _mayMigrateDbLocation(false)
+ _dbFile(dbFilePath),
+ _transaction(0)
{
}
-bool SyncJournalDb::exists()
+QString SyncJournalDb::makeDbName(const QUrl& remoteUrl,
+ const QString& remotePath,
+ const QString& user)
{
- QMutexLocker locker(&_mutex);
- return (!_dbFile.isEmpty() && QFile::exists(_dbFile));
-}
+ QString journalPath = QLatin1String("._sync_");
-void SyncJournalDb::setAccountParameterForFilePath( const QString& localPath, const QUrl& remoteUrl, const QString& remotePath )
-{
- // localPath always has a trailing slash
- _dbFile = localPath;
- _dbFile.append( QLatin1String("._sync_"));
- // FIXME: Maybe it is better to only allow different hosts, without path component.
- QString remoteUrlPath = remoteUrl.toString();
- if( remotePath != QLatin1String("/") ) {
- remoteUrlPath.append(remotePath);
- }
- QByteArray ba = QCryptographicHash::hash( remoteUrlPath.toUtf8(), QCryptographicHash::Md5);
- _dbFile.append( ba.left(6).toHex() );
- _dbFile.append(".db");
-}
+ QString key = QString::fromUtf8("%1@%2:%3").arg(
+ user,
+ remoteUrl.toString(),
+ remotePath);
-bool SyncJournalDb::mayMigrateDbLocation() const
-{
- return _mayMigrateDbLocation;
+ QByteArray ba = QCryptographicHash::hash(key.toUtf8(), QCryptographicHash::Md5);
+ journalPath.append( ba.left(6).toHex() );
+ journalPath.append(".db");
+
+ return journalPath;
}
-void SyncJournalDb::setMayMigrateDbLocation(bool migrate)
+bool SyncJournalDb::maybeMigrateDb(const QString& localPath, const QString& absoluteJournalPath)
{
- _mayMigrateDbLocation = migrate;
+ const QString oldDbName = localPath + QLatin1String(".csync_journal.db");
+ if( !FileSystem::fileExists(oldDbName) ) {
+ return true;
+ }
+
+ const QString newDbName = absoluteJournalPath;
+
+ // Whenever there is an old db file, migrate it to the new db path.
+ // This is done to make switching from older versions to newer versions
+ // work correctly even if the user had previously used a new version
+ // and therefore already has an (outdated) new-style db file.
+ QString error;
+
+ if( FileSystem::fileExists( newDbName ) ) {
+ if( !FileSystem::remove(newDbName, &error) ) {
+ qDebug() << "Database migration: Could not remove db file" << newDbName
+ << "due to" << error;
+ return false;
+ }
+ }
+
+ if( !FileSystem::rename(oldDbName, newDbName, &error) ) {
+ qDebug() << "Database migration: could not rename " << oldDbName
+ << "to" << newDbName << ":" << error;
+ return false;
+ }
+
+ qDebug() << "Journal successfully migrated from" << oldDbName << "to" << newDbName;
+ return true;
}
-#ifndef NDEBUG
-void SyncJournalDb::setDatabaseFilePath( const QString& dbFile)
+bool SyncJournalDb::exists()
{
- _dbFile = dbFile;
+ QMutexLocker locker(&_mutex);
+ return (!_dbFile.isEmpty() && QFile::exists(_dbFile));
}
-#endif
-QString SyncJournalDb::databaseFilePath()
+
+QString SyncJournalDb::databaseFilePath() const
{
return _dbFile;
}
return false;
}
- const QString dir = _dbFile.left( _dbFile.lastIndexOf(QChar('/')) );
- const QString oldDbName = dir + QLatin1String("/.csync_journal.db");
-
- bool migrateOldDb = _mayMigrateDbLocation && FileSystem::fileExists(oldDbName);
-
- // Whenever there is an old db file, migrate it to the new db path.
- // This is done to make switching from older versions to newer versions
- // work correctly even if the user had previously used a new version
- // and therefore already has an (outdated) new-style db file.
- if( migrateOldDb ) {
- QString error;
-
- if( FileSystem::fileExists( _dbFile ) ) {
- if( !FileSystem::remove(_dbFile, &error) ) {
- qDebug() << "Database migration: Could not remove db file" << _dbFile
- << "due to" << error;
- return false;
- }
- }
-
- if( !FileSystem::rename(oldDbName, _dbFile, &error) ) {
- qDebug() << "Database migration: could not rename " << oldDbName
- << "to" << _dbFile << ":" << error;
- return false;
- }
-
- qDebug() << "Journal successfully migrated from" << oldDbName << "to" << _dbFile;
- }
-
// The database file is created by this call (SQLITE_OPEN_CREATE)
if( !_db.openOrCreateReadWrite(_dbFile) ) {
QString error = _db.error();
SqlQuery versionQuery("SELECT major, minor, patch FROM version;", _db);
if (!versionQuery.next()) {
// If there was no entry in the table, it means we are likely upgrading from 1.5
- if (migrateOldDb) {
- qDebug() << Q_FUNC_INFO << "possibleUpgradeFromMirall_1_5 detected!";
- forceRemoteDiscovery = true;
- }
+ qDebug() << Q_FUNC_INFO << "possibleUpgradeFromMirall_1_5 detected!";
+ forceRemoteDiscovery = true;
+
createQuery.prepare("INSERT INTO version VALUES (?1, ?2, ?3, ?4);");
createQuery.bindValue(1, MIRALL_VERSION_MAJOR);
createQuery.bindValue(2, MIRALL_VERSION_MINOR);
{
Q_OBJECT
public:
- explicit SyncJournalDb(QObject *parent = 0);
+ explicit SyncJournalDb(const QString& dbFilePath, QObject *parent = 0);
virtual ~SyncJournalDb();
+ /// Create a journal path for a specific configuration
+ static QString makeDbName(const QUrl& remoteUrl,
+ const QString& remotePath,
+ const QString& user);
+
+ /// Migrate a csync_journal to the new path, if necessary. Returns false on error
+ static bool maybeMigrateDb(const QString& localPath, const QString& absoluteJournalPath);
+
// to verify that the record could be queried successfully check
// with SyncJournalFileRecord::isValid()
SyncJournalFileRecord getFileRecord(const QString& filename);
bool exists();
void walCheckpoint();
- QString databaseFilePath();
-#ifndef NDEBUG
- void setDatabaseFilePath( const QString& dbFile);
-#endif
- void setAccountParameterForFilePath(const QString& localPath, const QUrl &remoteUrl, const QString& remotePath );
-
- bool mayMigrateDbLocation() const;
- void setMayMigrateDbLocation(bool migrate);
+ QString databaseFilePath() const;
static qint64 getPHash(const QString& );
* that would write the etag and would void the purpose of avoidReadFromDbOnNextSync
*/
QList<QString> _avoidReadFromDbOnNextSyncFilter;
-
- /**
- * Whether to check old journal path (csync_journal.db) and move the file
- * to its new location, if it exists.
- */
- bool _mayMigrateDbLocation;
};
bool OWNCLOUDSYNC_EXPORT
_account->setUrl(QUrl(QStringLiteral("http://admin:admin@localhost/owncloud")));
_account->setCredentials(new FakeCredentials{_fakeQnam});
- _journalDb.reset(new OCC::SyncJournalDb());
- _journalDb->setDatabaseFilePath(localPath() + "._sync_test.db");
+ _journalDb.reset(new OCC::SyncJournalDb(localPath() + "._sync_test.db"));
_syncEngine.reset(new OCC::SyncEngine(_account, localPath(), "", _journalDb.get()));
// A new folder will update the local file state database on first sync.
public:
TestSyncJournalDB()
+ : _db("/tmp/csync-test.db")
{
- const QString testdb("/tmp/csync-test.db");
- _db.setDatabaseFilePath( testdb );
}
QDateTime dropMsecs(QDateTime time)