1 /* 2 * Copyright (C) by Olivier Goffart <ogoffart@woboq.com> 3 * 4 * This program is free software; you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation; either version 2 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, but 10 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 11 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * for more details. 13 */ 14 15 #pragma once 16 17 #include <QObject> 18 #include <QElapsedTimer> 19 #include <QStringList> 20 #include <csync.h> 21 #include <QMap> 22 #include <QSet> 23 #include "networkjobs.h" 24 #include <QMutex> 25 #include <QWaitCondition> 26 #include <QLinkedList> 27 #include <QRunnable> 28 #include <deque> 29 #include "syncoptions.h" 30 #include "syncfileitem.h" 31 32 class ExcludedFiles; 33 34 namespace OCC { 35 36 enum class LocalDiscoveryStyle { 37 FilesystemOnly, //< read all local data from the filesystem 38 DatabaseAndFilesystem, //< read from the db, except for listed paths 39 }; 40 41 42 class Account; 43 class SyncJournalDb; 44 class ProcessDirectoryJob; 45 46 /** 47 * Represent all the meta-data about a file in the server 48 */ 49 struct RemoteInfo 50 { 51 /** FileName of the entry (this does not contains any directory or path, just the plain name */ 52 QString name; 53 QByteArray etag; 54 QByteArray fileId; 55 QByteArray checksumHeader; 56 OCC::RemotePermissions remotePerm; 57 time_t modtime = 0; 58 int64_t size = 0; 59 bool isDirectory = false; isValidRemoteInfo60 bool isValid() const { return !name.isNull(); } 61 62 QString directDownloadUrl; 63 QString directDownloadCookies; 64 }; 65 66 struct LocalInfo 67 { 68 /** FileName of the entry (this does not contains any directory or path, just the plain name */ 69 QString name; 70 time_t modtime = 0; 71 int64_t size = 0; 72 uint64_t inode = 0; 73 ItemType type = ItemTypeSkip; 74 bool isDirectory = false; 75 bool isHidden = false; 76 bool isVirtualFile = false; 77 bool isSymLink = false; isValidLocalInfo78 bool isValid() const { return !name.isNull(); } 79 }; 80 81 /** 82 * @brief Run list on a local directory and process the results for Discovery 83 * 84 * @ingroup libsync 85 */ 86 class DiscoverySingleLocalDirectoryJob : public QObject, public QRunnable 87 { 88 Q_OBJECT 89 public: 90 explicit DiscoverySingleLocalDirectoryJob(const AccountPtr &account, const QString &localPath, OCC::Vfs *vfs, QObject *parent = nullptr); 91 92 void run() override; 93 signals: 94 void finished(QVector<LocalInfo> result); 95 void finishedFatalError(QString errorString); 96 void finishedNonFatalError(QString errorString); 97 98 void itemDiscovered(SyncFileItemPtr item); 99 void childIgnored(bool b); 100 private slots: 101 private: 102 QString _localPath; 103 AccountPtr _account; 104 OCC::Vfs* _vfs; 105 public: 106 }; 107 108 109 /** 110 * @brief Run a PROPFIND on a directory and process the results for Discovery 111 * 112 * @ingroup libsync 113 */ 114 class DiscoverySingleDirectoryJob : public QObject 115 { 116 Q_OBJECT 117 public: 118 explicit DiscoverySingleDirectoryJob(const AccountPtr &account, const QString &path, QObject *parent = nullptr); 119 // Specify that this is the root and we need to check the data-fingerprint setIsRootPath()120 void setIsRootPath() { _isRootPath = true; } 121 void start(); 122 void abort(); 123 124 // This is not actually a network job, it is just a job 125 signals: 126 void firstDirectoryPermissions(RemotePermissions); 127 void etag(const QString &); 128 void finished(const HttpResult<QVector<RemoteInfo>> &result); 129 130 private slots: 131 void directoryListingIteratedSlot(const QString &, const QMap<QString, QString> &); 132 void lsJobFinishedWithoutErrorSlot(); 133 void lsJobFinishedWithErrorSlot(QNetworkReply *); 134 135 private: 136 QVector<RemoteInfo> _results; 137 QString _subPath; 138 QString _firstEtag; 139 AccountPtr _account; 140 // The first result is for the directory itself and need to be ignored. 141 // This flag is true if it was already ignored. 142 bool _ignoredFirst; 143 // Set to true if this is the root path and we need to check the data-fingerprint 144 bool _isRootPath; 145 // If this directory is an external storage (The first item has 'M' in its permission) 146 bool _isExternalStorage; 147 // If set, the discovery will finish with an error 148 QString _error; 149 QPointer<LsColJob> _lsColJob; 150 151 public: 152 QByteArray _dataFingerprint; 153 }; 154 155 class DiscoveryPhase : public QObject 156 { 157 Q_OBJECT 158 159 friend class ProcessDirectoryJob; 160 161 QPointer<ProcessDirectoryJob> _currentRootJob; 162 163 /** Maps the db-path of a deleted item to its SyncFileItem. 164 * 165 * If it turns out the item was renamed after all, the instruction 166 * can be changed. See findAndCancelDeletedJob(). Note that 167 * itemDiscovered() will already have been emitted for the item. 168 */ 169 QMap<QString, SyncFileItemPtr> _deletedItem; 170 171 /** Maps the db-path of a deleted folder to its queued job. 172 * 173 * If a folder is deleted and must be recursed into, its job isn't 174 * executed immediately. Instead it's queued here and only run 175 * once the rest of the discovery has finished and we are certain 176 * that the folder wasn't just renamed. This avoids running the 177 * discovery on contents in the old location of renamed folders. 178 * 179 * See findAndCancelDeletedJob(). 180 */ 181 QMap<QString, ProcessDirectoryJob *> _queuedDeletedDirectories; 182 183 // map source (original path) -> destinations (current server or local path) 184 QMap<QString, QString> _renamedItemsRemote; 185 QMap<QString, QString> _renamedItemsLocal; 186 187 // set of paths that should not be removed even though they are removed locally: 188 // there was a move to an invalid destination and now the source should be restored 189 // 190 // This applies recursively to subdirectories. 191 // All entries should have a trailing slash (even files), so lookup with 192 // lowerBound() is reliable. 193 // 194 // The value of this map doesn't matter. 195 QMap<QString, bool> _forbiddenDeletes; 196 197 /** Returns whether the db-path has been renamed locally or on the remote. 198 * 199 * Useful for avoiding processing of items that have already been claimed in 200 * a rename (would otherwise be discovered as deletions). 201 */ isRenamed(const QString & p)202 bool isRenamed(const QString &p) const { return _renamedItemsLocal.contains(p) || _renamedItemsRemote.contains(p); } 203 204 int _currentlyActiveJobs = 0; 205 206 // both must contain a sorted list 207 QStringList _selectiveSyncBlackList; 208 QStringList _selectiveSyncWhiteList; 209 210 void scheduleMoreJobs(); 211 212 bool isInSelectiveSyncBlackList(const QString &path) const; 213 214 // Check if the new folder should be deselected or not. 215 // May be async. "Return" via the callback, true if the item is blacklisted 216 void checkSelectiveSyncNewFolder(const QString &path, RemotePermissions rp, 217 std::function<void(bool)> callback); 218 219 /** Given an original path, return the target path obtained when renaming is done. 220 * 221 * Note that it only considers parent directory renames. So if A/B got renamed to C/D, 222 * checking A/B/file would yield C/D/file, but checking A/B would yield A/B. 223 */ 224 QString adjustRenamedPath(const QString &original, SyncFileItem::Direction) const; 225 226 /** If the db-path is scheduled for deletion, abort it. 227 * 228 * Check if there is already a job to delete that item: 229 * If that's not the case, return { false, QByteArray() }. 230 * If there is such a job, cancel that job and return true and the old etag. 231 * 232 * Used when having detected a rename: The rename source may have been 233 * discovered before and would have looked like a delete. 234 * 235 * See _deletedItem and _queuedDeletedDirectories. 236 */ 237 QPair<bool, QByteArray> findAndCancelDeletedJob(const QString &originalPath); 238 239 public: 240 // input 241 QString _localDir; // absolute path to the local directory. ends with '/' 242 QString _remoteFolder; // remote folder, ends with '/' 243 SyncJournalDb *_statedb; 244 AccountPtr _account; 245 SyncOptions _syncOptions; 246 ExcludedFiles *_excludes; 247 QRegExp _invalidFilenameRx; // FIXME: maybe move in ExcludedFiles 248 QStringList _serverBlacklistedFiles; // The blacklist from the capabilities 249 bool _ignoreHiddenFiles = false; 250 std::function<bool(const QString &)> _shouldDiscoverLocaly; 251 252 void startJob(ProcessDirectoryJob *); 253 254 void setSelectiveSyncBlackList(const QStringList &list); 255 void setSelectiveSyncWhiteList(const QStringList &list); 256 257 // output 258 QByteArray _dataFingerprint; 259 bool _anotherSyncNeeded = false; 260 261 signals: 262 void fatalError(const QString &errorString); 263 void itemDiscovered(const SyncFileItemPtr &item); 264 void finished(); 265 266 // A new folder was discovered and was not synced because of the confirmation feature 267 void newBigFolder(const QString &folder, bool isExternal); 268 269 /** For excluded items that don't show up in itemDiscovered() 270 * 271 * The path is relative to the sync folder, similar to item->_file 272 */ 273 void silentlyExcluded(const QString &folderPath); 274 }; 275 276 /// Implementation of DiscoveryPhase::adjustRenamedPath 277 QString adjustRenamedPath(const QMap<QString, QString> &renamedItems, const QString &original); 278 } 279