propagatorjobs.cpp 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. /*
  2. * Copyright (C) by Olivier Goffart <ogoffart@owncloud.com>
  3. * Copyright (C) by Klaas Freitag <freitag@owncloud.com>
  4. *
  5. * This program is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU General Public License as published by
  7. * the Free Software Foundation; either version 2 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * This program is distributed in the hope that it will be useful, but
  11. * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
  12. * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
  13. * for more details.
  14. */
  15. #include "propagatorjobs.h"
  16. #include "owncloudpropagator_p.h"
  17. #include "propagateremotemove.h"
  18. #include "common/utility.h"
  19. #include "syncjournaldb.h"
  20. #include "syncjournalfilerecord.h"
  21. #include "filesystem.h"
  22. #include <qfile.h>
  23. #include <qdir.h>
  24. #include <qdiriterator.h>
  25. #include <qtemporaryfile.h>
  26. #include <qsavefile.h>
  27. #include <QDateTime>
  28. #include <qstack.h>
  29. #include <QCoreApplication>
  30. #include <time.h>
  31. namespace OCC {
  32. Q_LOGGING_CATEGORY(lcPropagateLocalRemove, "sync.propagator.localremove", QtInfoMsg)
  33. Q_LOGGING_CATEGORY(lcPropagateLocalMkdir, "sync.propagator.localmkdir", QtInfoMsg)
  34. Q_LOGGING_CATEGORY(lcPropagateLocalRename, "sync.propagator.localrename", QtInfoMsg)
  35. QByteArray localFileIdFromFullId(const QByteArray &id)
  36. {
  37. return id.left(8);
  38. }
  39. /**
  40. * Code inspired from Qt5's QDir::removeRecursively
  41. * The code will update the database in case of error.
  42. * If everything goes well (no error, returns true), the caller is responsible for removing the entries
  43. * in the database. But in case of error, we need to remove the entries from the database of the files
  44. * that were deleted.
  45. *
  46. * \a path is relative to propagator()->_localDir + _item->_file and should start with a slash
  47. */
  48. bool PropagateLocalRemove::removeRecursively(const QString &path)
  49. {
  50. bool success = true;
  51. QString absolute = propagator()->_localDir + _item->_file + path;
  52. QDirIterator di(absolute, QDir::AllEntries | QDir::Hidden | QDir::System | QDir::NoDotAndDotDot);
  53. QVector<QPair<QString, bool>> deleted;
  54. while (di.hasNext()) {
  55. di.next();
  56. const QFileInfo &fi = di.fileInfo();
  57. bool ok;
  58. // The use of isSymLink here is okay:
  59. // we never want to go into this branch for .lnk files
  60. bool isDir = fi.isDir() && !fi.isSymLink();
  61. if (isDir) {
  62. ok = removeRecursively(path + QLatin1Char('/') + di.fileName()); // recursive
  63. } else {
  64. QString removeError;
  65. ok = FileSystem::remove(di.filePath(), &removeError);
  66. if (!ok) {
  67. _error += PropagateLocalRemove::tr("Error removing '%1': %2;").arg(QDir::toNativeSeparators(di.filePath()), removeError) + " ";
  68. qCWarning(lcPropagateLocalRemove) << "Error removing " << di.filePath() << ':' << removeError;
  69. }
  70. }
  71. if (success && !ok) {
  72. // We need to delete the entries from the database now from the deleted vector
  73. foreach (const auto &it, deleted) {
  74. propagator()->_journal->deleteFileRecord(_item->_originalFile + path + QLatin1Char('/') + it.first,
  75. it.second);
  76. }
  77. success = false;
  78. deleted.clear();
  79. }
  80. if (success) {
  81. deleted.append(qMakePair(di.fileName(), isDir));
  82. }
  83. if (!success && ok) {
  84. // This succeeded, so we need to delete it from the database now because the caller won't
  85. propagator()->_journal->deleteFileRecord(_item->_originalFile + path + QLatin1Char('/') + di.fileName(),
  86. isDir);
  87. }
  88. }
  89. if (success) {
  90. success = QDir().rmdir(absolute);
  91. if (!success) {
  92. _error += PropagateLocalRemove::tr("Could not remove folder '%1'")
  93. .arg(QDir::toNativeSeparators(absolute))
  94. + " ";
  95. qCWarning(lcPropagateLocalRemove) << "Error removing folder" << absolute;
  96. }
  97. }
  98. return success;
  99. }
  100. void PropagateLocalRemove::start()
  101. {
  102. if (propagator()->_abortRequested.fetchAndAddRelaxed(0))
  103. return;
  104. QString filename = propagator()->_localDir + _item->_file;
  105. qCDebug(lcPropagateLocalRemove) << filename;
  106. if (propagator()->localFileNameClash(_item->_file)) {
  107. done(SyncFileItem::NormalError, tr("Could not remove %1 because of a local file name clash").arg(QDir::toNativeSeparators(filename)));
  108. return;
  109. }
  110. if (_item->_isDirectory) {
  111. if (QDir(filename).exists() && !removeRecursively(QString())) {
  112. done(SyncFileItem::NormalError, _error);
  113. return;
  114. }
  115. } else {
  116. QString removeError;
  117. if (FileSystem::fileExists(filename)
  118. && !FileSystem::remove(filename, &removeError)) {
  119. done(SyncFileItem::NormalError, removeError);
  120. return;
  121. }
  122. }
  123. propagator()->reportProgress(*_item, 0);
  124. propagator()->_journal->deleteFileRecord(_item->_originalFile, _item->_isDirectory);
  125. propagator()->_journal->commit("Local remove");
  126. done(SyncFileItem::Success);
  127. }
  128. void PropagateLocalMkdir::start()
  129. {
  130. if (propagator()->_abortRequested.fetchAndAddRelaxed(0))
  131. return;
  132. QDir newDir(propagator()->getFilePath(_item->_file));
  133. QString newDirStr = QDir::toNativeSeparators(newDir.path());
  134. // When turning something that used to be a file into a directory
  135. // we need to delete the file first.
  136. QFileInfo fi(newDirStr);
  137. if (_deleteExistingFile && fi.exists() && fi.isFile()) {
  138. QString removeError;
  139. if (!FileSystem::remove(newDirStr, &removeError)) {
  140. done(SyncFileItem::NormalError,
  141. tr("could not delete file %1, error: %2")
  142. .arg(newDirStr, removeError));
  143. return;
  144. }
  145. }
  146. if (Utility::fsCasePreserving() && propagator()->localFileNameClash(_item->_file)) {
  147. qCWarning(lcPropagateLocalMkdir) << "New folder to create locally already exists with different case:" << _item->_file;
  148. done(SyncFileItem::NormalError, tr("Attention, possible case sensitivity clash with %1").arg(newDirStr));
  149. return;
  150. }
  151. emit propagator()->touchedFile(newDirStr);
  152. QDir localDir(propagator()->_localDir);
  153. if (!localDir.mkpath(_item->_file)) {
  154. done(SyncFileItem::NormalError, tr("could not create folder %1").arg(newDirStr));
  155. return;
  156. }
  157. // Insert the directory into the database. The correct etag will be set later,
  158. // once all contents have been propagated, because should_update_metadata is true.
  159. // Adding an entry with a dummy etag to the database still makes sense here
  160. // so the database is aware that this folder exists even if the sync is aborted
  161. // before the correct etag is stored.
  162. SyncJournalFileRecord record(*_item, newDirStr);
  163. record._etag = "_invalid_";
  164. if (!propagator()->_journal->setFileRecord(record)) {
  165. done(SyncFileItem::FatalError, tr("Error writing metadata to the database"));
  166. return;
  167. }
  168. propagator()->_journal->commit("localMkdir");
  169. done(SyncFileItem::Success);
  170. }
  171. void PropagateLocalMkdir::setDeleteExistingFile(bool enabled)
  172. {
  173. _deleteExistingFile = enabled;
  174. }
  175. void PropagateLocalRename::start()
  176. {
  177. if (propagator()->_abortRequested.fetchAndAddRelaxed(0))
  178. return;
  179. QString existingFile = propagator()->getFilePath(_item->_file);
  180. QString targetFile = propagator()->getFilePath(_item->_renameTarget);
  181. // if the file is a file underneath a moved dir, the _item->file is equal
  182. // to _item->renameTarget and the file is not moved as a result.
  183. if (_item->_file != _item->_renameTarget) {
  184. propagator()->reportProgress(*_item, 0);
  185. qCDebug(lcPropagateLocalRename) << "MOVE " << existingFile << " => " << targetFile;
  186. if (QString::compare(_item->_file, _item->_renameTarget, Qt::CaseInsensitive) != 0
  187. && propagator()->localFileNameClash(_item->_renameTarget)) {
  188. // Only use localFileNameClash for the destination if we know that the source was not
  189. // the one conflicting (renaming A.txt -> a.txt is OK)
  190. // Fixme: the file that is the reason for the clash could be named here,
  191. // it would have to come out the localFileNameClash function
  192. done(SyncFileItem::NormalError,
  193. tr("File %1 can not be renamed to %2 because of a local file name clash")
  194. .arg(QDir::toNativeSeparators(_item->_file))
  195. .arg(QDir::toNativeSeparators(_item->_renameTarget)));
  196. return;
  197. }
  198. emit propagator()->touchedFile(existingFile);
  199. emit propagator()->touchedFile(targetFile);
  200. QString renameError;
  201. if (!FileSystem::rename(existingFile, targetFile, &renameError)) {
  202. done(SyncFileItem::NormalError, renameError);
  203. return;
  204. }
  205. }
  206. SyncJournalFileRecord oldRecord =
  207. propagator()->_journal->getFileRecord(_item->_originalFile);
  208. propagator()->_journal->deleteFileRecord(_item->_originalFile);
  209. // store the rename file name in the item.
  210. const auto oldFile = _item->_file;
  211. _item->_file = _item->_renameTarget;
  212. SyncJournalFileRecord record(*_item, targetFile);
  213. record._path = _item->_renameTarget;
  214. if (oldRecord.isValid()) {
  215. record._checksumHeader = oldRecord._checksumHeader;
  216. }
  217. if (!_item->_isDirectory) { // Directories are saved at the end
  218. if (!propagator()->_journal->setFileRecord(record)) {
  219. done(SyncFileItem::FatalError, tr("Error writing metadata to the database"));
  220. return;
  221. }
  222. } else {
  223. if (!PropagateRemoteMove::adjustSelectiveSync(propagator()->_journal, oldFile, _item->_renameTarget)) {
  224. done(SyncFileItem::FatalError, tr("Error writing metadata to the database"));
  225. return;
  226. }
  227. }
  228. propagator()->_journal->commit("localRename");
  229. done(SyncFileItem::Success);
  230. }
  231. }