testsyncvirtualfiles.cpp 54 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155
  1. /*
  2. * This software is in the public domain, furnished "as is", without technical
  3. * support, and with no warranty, express or implied, as to its usefulness for
  4. * any purpose.
  5. *
  6. */
  7. #include <QtTest>
  8. #include "syncenginetestutils.h"
  9. #include "common/vfs.h"
  10. #include <syncengine.h>
  11. using namespace OCC;
  12. SyncFileItemPtr findItem(const QSignalSpy &spy, const QString &path)
  13. {
  14. for (const QList<QVariant> &args : spy) {
  15. auto item = args[0].value<SyncFileItemPtr>();
  16. if (item->destination() == path)
  17. return item;
  18. }
  19. return SyncFileItemPtr(new SyncFileItem);
  20. }
  21. bool itemInstruction(const QSignalSpy &spy, const QString &path, const csync_instructions_e instr)
  22. {
  23. auto item = findItem(spy, path);
  24. return item->_instruction == instr;
  25. }
  26. SyncJournalFileRecord dbRecord(FakeFolder &folder, const QString &path)
  27. {
  28. SyncJournalFileRecord record;
  29. folder.syncJournal().getFileRecord(path, &record);
  30. return record;
  31. }
  32. void triggerDownload(FakeFolder &folder, const QByteArray &path)
  33. {
  34. auto &journal = folder.syncJournal();
  35. SyncJournalFileRecord record;
  36. journal.getFileRecord(path + ".nextcloud", &record);
  37. if (!record.isValid())
  38. return;
  39. record._type = ItemTypeVirtualFileDownload;
  40. journal.setFileRecord(record);
  41. journal.schedulePathForRemoteDiscovery(record._path);
  42. }
  43. void markForDehydration(FakeFolder &folder, const QByteArray &path)
  44. {
  45. auto &journal = folder.syncJournal();
  46. SyncJournalFileRecord record;
  47. journal.getFileRecord(path, &record);
  48. if (!record.isValid())
  49. return;
  50. record._type = ItemTypeVirtualFileDehydration;
  51. journal.setFileRecord(record);
  52. journal.schedulePathForRemoteDiscovery(record._path);
  53. }
  54. QSharedPointer<Vfs> setupVfs(FakeFolder &folder)
  55. {
  56. auto suffixVfs = QSharedPointer<Vfs>(createVfsFromPlugin(Vfs::WithSuffix).release());
  57. folder.switchToVfs(suffixVfs);
  58. // Using this directly doesn't recursively unpin everything and instead leaves
  59. // the files in the hydration that that they start with
  60. folder.syncJournal().internalPinStates().setForPath("", PinState::Unspecified);
  61. return suffixVfs;
  62. }
  63. class TestSyncVirtualFiles : public QObject
  64. {
  65. Q_OBJECT
  66. private slots:
  67. void testVirtualFileLifecycle_data()
  68. {
  69. QTest::addColumn<bool>("doLocalDiscovery");
  70. QTest::newRow("full local discovery") << true;
  71. QTest::newRow("skip local discovery") << false;
  72. }
  73. void testVirtualFileLifecycle()
  74. {
  75. QFETCH(bool, doLocalDiscovery);
  76. FakeFolder fakeFolder{ FileInfo() };
  77. setupVfs(fakeFolder);
  78. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  79. QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
  80. auto cleanup = [&]() {
  81. completeSpy.clear();
  82. if (!doLocalDiscovery)
  83. fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem);
  84. };
  85. cleanup();
  86. // Create a virtual file for a new remote file
  87. fakeFolder.remoteModifier().mkdir("A");
  88. fakeFolder.remoteModifier().insert("A/a1", 64);
  89. auto someDate = QDateTime(QDate(1984, 07, 30), QTime(1,3,2));
  90. fakeFolder.remoteModifier().setModTime("A/a1", someDate);
  91. QVERIFY(fakeFolder.syncOnce());
  92. QVERIFY(!fakeFolder.currentLocalState().find("A/a1"));
  93. QVERIFY(fakeFolder.currentLocalState().find("A/a1.nextcloud"));
  94. QCOMPARE(QFileInfo(fakeFolder.localPath() + "A/a1.nextcloud").lastModified(), someDate);
  95. QVERIFY(fakeFolder.currentRemoteState().find("A/a1"));
  96. QVERIFY(itemInstruction(completeSpy, "A/a1.nextcloud", CSYNC_INSTRUCTION_NEW));
  97. QCOMPARE(dbRecord(fakeFolder, "A/a1.nextcloud")._type, ItemTypeVirtualFile);
  98. cleanup();
  99. // Another sync doesn't actually lead to changes
  100. QVERIFY(fakeFolder.syncOnce());
  101. QVERIFY(!fakeFolder.currentLocalState().find("A/a1"));
  102. QVERIFY(fakeFolder.currentLocalState().find("A/a1.nextcloud"));
  103. QCOMPARE(QFileInfo(fakeFolder.localPath() + "A/a1.nextcloud").lastModified(), someDate);
  104. QVERIFY(fakeFolder.currentRemoteState().find("A/a1"));
  105. QCOMPARE(dbRecord(fakeFolder, "A/a1.nextcloud")._type, ItemTypeVirtualFile);
  106. QVERIFY(completeSpy.isEmpty());
  107. cleanup();
  108. // Not even when the remote is rediscovered
  109. fakeFolder.syncJournal().forceRemoteDiscoveryNextSync();
  110. QVERIFY(fakeFolder.syncOnce());
  111. QVERIFY(!fakeFolder.currentLocalState().find("A/a1"));
  112. QVERIFY(fakeFolder.currentLocalState().find("A/a1.nextcloud"));
  113. QCOMPARE(QFileInfo(fakeFolder.localPath() + "A/a1.nextcloud").lastModified(), someDate);
  114. QVERIFY(fakeFolder.currentRemoteState().find("A/a1"));
  115. QCOMPARE(dbRecord(fakeFolder, "A/a1.nextcloud")._type, ItemTypeVirtualFile);
  116. QVERIFY(completeSpy.isEmpty());
  117. cleanup();
  118. // Neither does a remote change
  119. fakeFolder.remoteModifier().appendByte("A/a1");
  120. QVERIFY(fakeFolder.syncOnce());
  121. QVERIFY(!fakeFolder.currentLocalState().find("A/a1"));
  122. QVERIFY(fakeFolder.currentLocalState().find("A/a1.nextcloud"));
  123. QVERIFY(fakeFolder.currentRemoteState().find("A/a1"));
  124. QVERIFY(itemInstruction(completeSpy, "A/a1.nextcloud", CSYNC_INSTRUCTION_UPDATE_METADATA));
  125. QCOMPARE(dbRecord(fakeFolder, "A/a1.nextcloud")._type, ItemTypeVirtualFile);
  126. QCOMPARE(dbRecord(fakeFolder, "A/a1.nextcloud")._fileSize, 65);
  127. cleanup();
  128. // If the local virtual file file is removed, it'll just be recreated
  129. if (!doLocalDiscovery)
  130. fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::DatabaseAndFilesystem, { "A" });
  131. fakeFolder.localModifier().remove("A/a1.nextcloud");
  132. QVERIFY(fakeFolder.syncOnce());
  133. QVERIFY(!fakeFolder.currentLocalState().find("A/a1"));
  134. QVERIFY(fakeFolder.currentLocalState().find("A/a1.nextcloud"));
  135. QVERIFY(fakeFolder.currentRemoteState().find("A/a1"));
  136. QVERIFY(itemInstruction(completeSpy, "A/a1.nextcloud", CSYNC_INSTRUCTION_NEW));
  137. QCOMPARE(dbRecord(fakeFolder, "A/a1.nextcloud")._type, ItemTypeVirtualFile);
  138. QCOMPARE(dbRecord(fakeFolder, "A/a1.nextcloud")._fileSize, 65);
  139. cleanup();
  140. // Remote rename is propagated
  141. fakeFolder.remoteModifier().rename("A/a1", "A/a1m");
  142. QVERIFY(fakeFolder.syncOnce());
  143. QVERIFY(!fakeFolder.currentLocalState().find("A/a1"));
  144. QVERIFY(!fakeFolder.currentLocalState().find("A/a1m"));
  145. QVERIFY(!fakeFolder.currentLocalState().find("A/a1.nextcloud"));
  146. QVERIFY(fakeFolder.currentLocalState().find("A/a1m.nextcloud"));
  147. QVERIFY(!fakeFolder.currentRemoteState().find("A/a1"));
  148. QVERIFY(fakeFolder.currentRemoteState().find("A/a1m"));
  149. QVERIFY(
  150. itemInstruction(completeSpy, "A/a1m.nextcloud", CSYNC_INSTRUCTION_RENAME)
  151. || (itemInstruction(completeSpy, "A/a1m.nextcloud", CSYNC_INSTRUCTION_NEW)
  152. && itemInstruction(completeSpy, "A/a1.nextcloud", CSYNC_INSTRUCTION_REMOVE)));
  153. QCOMPARE(dbRecord(fakeFolder, "A/a1m.nextcloud")._type, ItemTypeVirtualFile);
  154. cleanup();
  155. // Remote remove is propagated
  156. fakeFolder.remoteModifier().remove("A/a1m");
  157. QVERIFY(fakeFolder.syncOnce());
  158. QVERIFY(!fakeFolder.currentLocalState().find("A/a1m.nextcloud"));
  159. QVERIFY(!fakeFolder.currentRemoteState().find("A/a1m"));
  160. QVERIFY(itemInstruction(completeSpy, "A/a1m.nextcloud", CSYNC_INSTRUCTION_REMOVE));
  161. QVERIFY(!dbRecord(fakeFolder, "A/a1.nextcloud").isValid());
  162. QVERIFY(!dbRecord(fakeFolder, "A/a1m.nextcloud").isValid());
  163. cleanup();
  164. // Edge case: Local virtual file but no db entry for some reason
  165. fakeFolder.remoteModifier().insert("A/a2", 64);
  166. fakeFolder.remoteModifier().insert("A/a3", 64);
  167. QVERIFY(fakeFolder.syncOnce());
  168. QVERIFY(fakeFolder.currentLocalState().find("A/a2.nextcloud"));
  169. QVERIFY(fakeFolder.currentLocalState().find("A/a3.nextcloud"));
  170. cleanup();
  171. fakeFolder.syncEngine().journal()->deleteFileRecord("A/a2.nextcloud");
  172. fakeFolder.syncEngine().journal()->deleteFileRecord("A/a3.nextcloud");
  173. fakeFolder.remoteModifier().remove("A/a3");
  174. fakeFolder.syncEngine().setLocalDiscoveryOptions(LocalDiscoveryStyle::FilesystemOnly);
  175. QVERIFY(fakeFolder.syncOnce());
  176. QVERIFY(fakeFolder.currentLocalState().find("A/a2.nextcloud"));
  177. QVERIFY(itemInstruction(completeSpy, "A/a2.nextcloud", CSYNC_INSTRUCTION_UPDATE_METADATA));
  178. QVERIFY(dbRecord(fakeFolder, "A/a2.nextcloud").isValid());
  179. QVERIFY(!fakeFolder.currentLocalState().find("A/a3.nextcloud"));
  180. QVERIFY(itemInstruction(completeSpy, "A/a3.nextcloud", CSYNC_INSTRUCTION_REMOVE));
  181. QVERIFY(!dbRecord(fakeFolder, "A/a3.nextcloud").isValid());
  182. cleanup();
  183. }
  184. void testVirtualFileConflict()
  185. {
  186. FakeFolder fakeFolder{ FileInfo() };
  187. setupVfs(fakeFolder);
  188. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  189. QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
  190. auto cleanup = [&]() {
  191. completeSpy.clear();
  192. };
  193. cleanup();
  194. // Create a virtual file for a new remote file
  195. fakeFolder.remoteModifier().mkdir("A");
  196. fakeFolder.remoteModifier().insert("A/a1", 64);
  197. fakeFolder.remoteModifier().insert("A/a2", 64);
  198. fakeFolder.remoteModifier().mkdir("B");
  199. fakeFolder.remoteModifier().insert("B/b1", 64);
  200. fakeFolder.remoteModifier().insert("B/b2", 64);
  201. fakeFolder.remoteModifier().mkdir("C");
  202. fakeFolder.remoteModifier().insert("C/c1", 64);
  203. QVERIFY(fakeFolder.syncOnce());
  204. QVERIFY(fakeFolder.currentLocalState().find("A/a1.nextcloud"));
  205. QVERIFY(fakeFolder.currentLocalState().find("B/b2.nextcloud"));
  206. cleanup();
  207. // A: the correct file and a conflicting file are added, virtual files stay
  208. // B: same setup, but the virtual files are deleted by the user
  209. // C: user adds a *directory* locally
  210. fakeFolder.localModifier().insert("A/a1", 64);
  211. fakeFolder.localModifier().insert("A/a2", 30);
  212. fakeFolder.localModifier().insert("B/b1", 64);
  213. fakeFolder.localModifier().insert("B/b2", 30);
  214. fakeFolder.localModifier().remove("B/b1.nextcloud");
  215. fakeFolder.localModifier().remove("B/b2.nextcloud");
  216. fakeFolder.localModifier().mkdir("C/c1");
  217. fakeFolder.localModifier().insert("C/c1/foo");
  218. QVERIFY(fakeFolder.syncOnce());
  219. // Everything is CONFLICT since mtimes are different even for a1/b1
  220. QVERIFY(itemInstruction(completeSpy, "A/a1", CSYNC_INSTRUCTION_CONFLICT));
  221. QVERIFY(itemInstruction(completeSpy, "A/a2", CSYNC_INSTRUCTION_CONFLICT));
  222. QVERIFY(itemInstruction(completeSpy, "B/b1", CSYNC_INSTRUCTION_CONFLICT));
  223. QVERIFY(itemInstruction(completeSpy, "B/b2", CSYNC_INSTRUCTION_CONFLICT));
  224. QVERIFY(itemInstruction(completeSpy, "C/c1", CSYNC_INSTRUCTION_CONFLICT));
  225. // no virtual file files should remain
  226. QVERIFY(!fakeFolder.currentLocalState().find("A/a1.nextcloud"));
  227. QVERIFY(!fakeFolder.currentLocalState().find("A/a2.nextcloud"));
  228. QVERIFY(!fakeFolder.currentLocalState().find("B/b1.nextcloud"));
  229. QVERIFY(!fakeFolder.currentLocalState().find("B/b2.nextcloud"));
  230. QVERIFY(!fakeFolder.currentLocalState().find("C/c1.nextcloud"));
  231. // conflict files should exist
  232. QCOMPARE(fakeFolder.syncJournal().conflictRecordPaths().size(), 3);
  233. // nothing should have the virtual file tag
  234. QCOMPARE(dbRecord(fakeFolder, "A/a1")._type, ItemTypeFile);
  235. QCOMPARE(dbRecord(fakeFolder, "A/a2")._type, ItemTypeFile);
  236. QCOMPARE(dbRecord(fakeFolder, "B/b1")._type, ItemTypeFile);
  237. QCOMPARE(dbRecord(fakeFolder, "B/b2")._type, ItemTypeFile);
  238. QCOMPARE(dbRecord(fakeFolder, "C/c1")._type, ItemTypeFile);
  239. QVERIFY(!dbRecord(fakeFolder, "A/a1.nextcloud").isValid());
  240. QVERIFY(!dbRecord(fakeFolder, "A/a2.nextcloud").isValid());
  241. QVERIFY(!dbRecord(fakeFolder, "B/b1.nextcloud").isValid());
  242. QVERIFY(!dbRecord(fakeFolder, "B/b2.nextcloud").isValid());
  243. QVERIFY(!dbRecord(fakeFolder, "C/c1.nextcloud").isValid());
  244. cleanup();
  245. }
  246. void testWithNormalSync()
  247. {
  248. FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
  249. setupVfs(fakeFolder);
  250. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  251. QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
  252. auto cleanup = [&]() {
  253. completeSpy.clear();
  254. };
  255. cleanup();
  256. // No effect sync
  257. QVERIFY(fakeFolder.syncOnce());
  258. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  259. cleanup();
  260. // Existing files are propagated just fine in both directions
  261. fakeFolder.localModifier().appendByte("A/a1");
  262. fakeFolder.localModifier().insert("A/a3");
  263. fakeFolder.remoteModifier().appendByte("A/a2");
  264. QVERIFY(fakeFolder.syncOnce());
  265. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  266. cleanup();
  267. // New files on the remote create virtual files
  268. fakeFolder.remoteModifier().insert("A/new");
  269. QVERIFY(fakeFolder.syncOnce());
  270. QVERIFY(!fakeFolder.currentLocalState().find("A/new"));
  271. QVERIFY(fakeFolder.currentLocalState().find("A/new.nextcloud"));
  272. QVERIFY(fakeFolder.currentRemoteState().find("A/new"));
  273. QVERIFY(itemInstruction(completeSpy, "A/new.nextcloud", CSYNC_INSTRUCTION_NEW));
  274. QCOMPARE(dbRecord(fakeFolder, "A/new.nextcloud")._type, ItemTypeVirtualFile);
  275. cleanup();
  276. }
  277. void testVirtualFileDownload()
  278. {
  279. FakeFolder fakeFolder{ FileInfo() };
  280. setupVfs(fakeFolder);
  281. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  282. QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
  283. auto cleanup = [&]() {
  284. completeSpy.clear();
  285. };
  286. cleanup();
  287. // Create a virtual file for remote files
  288. fakeFolder.remoteModifier().mkdir("A");
  289. fakeFolder.remoteModifier().insert("A/a1");
  290. fakeFolder.remoteModifier().insert("A/a2");
  291. fakeFolder.remoteModifier().insert("A/a3");
  292. fakeFolder.remoteModifier().insert("A/a4");
  293. fakeFolder.remoteModifier().insert("A/a5");
  294. fakeFolder.remoteModifier().insert("A/a6");
  295. fakeFolder.remoteModifier().insert("A/a7");
  296. fakeFolder.remoteModifier().insert("A/b1");
  297. fakeFolder.remoteModifier().insert("A/b2");
  298. fakeFolder.remoteModifier().insert("A/b3");
  299. fakeFolder.remoteModifier().insert("A/b4");
  300. QVERIFY(fakeFolder.syncOnce());
  301. QVERIFY(fakeFolder.currentLocalState().find("A/a1.nextcloud"));
  302. QVERIFY(fakeFolder.currentLocalState().find("A/a2.nextcloud"));
  303. QVERIFY(fakeFolder.currentLocalState().find("A/a3.nextcloud"));
  304. QVERIFY(fakeFolder.currentLocalState().find("A/a4.nextcloud"));
  305. QVERIFY(fakeFolder.currentLocalState().find("A/a5.nextcloud"));
  306. QVERIFY(fakeFolder.currentLocalState().find("A/a6.nextcloud"));
  307. QVERIFY(fakeFolder.currentLocalState().find("A/a7.nextcloud"));
  308. QVERIFY(fakeFolder.currentLocalState().find("A/b1.nextcloud"));
  309. QVERIFY(fakeFolder.currentLocalState().find("A/b2.nextcloud"));
  310. QVERIFY(fakeFolder.currentLocalState().find("A/b3.nextcloud"));
  311. QVERIFY(fakeFolder.currentLocalState().find("A/b4.nextcloud"));
  312. cleanup();
  313. // Download by changing the db entry
  314. triggerDownload(fakeFolder, "A/a1");
  315. triggerDownload(fakeFolder, "A/a2");
  316. triggerDownload(fakeFolder, "A/a3");
  317. triggerDownload(fakeFolder, "A/a4");
  318. triggerDownload(fakeFolder, "A/a5");
  319. triggerDownload(fakeFolder, "A/a6");
  320. triggerDownload(fakeFolder, "A/a7");
  321. // Download by renaming locally
  322. fakeFolder.localModifier().rename("A/b1.nextcloud", "A/b1");
  323. fakeFolder.localModifier().rename("A/b2.nextcloud", "A/b2");
  324. fakeFolder.localModifier().rename("A/b3.nextcloud", "A/b3");
  325. fakeFolder.localModifier().rename("A/b4.nextcloud", "A/b4");
  326. // Remote complications
  327. fakeFolder.remoteModifier().appendByte("A/a2");
  328. fakeFolder.remoteModifier().remove("A/a3");
  329. fakeFolder.remoteModifier().rename("A/a4", "A/a4m");
  330. fakeFolder.remoteModifier().appendByte("A/b2");
  331. fakeFolder.remoteModifier().remove("A/b3");
  332. fakeFolder.remoteModifier().rename("A/b4", "A/b4m");
  333. // Local complications
  334. fakeFolder.localModifier().insert("A/a5");
  335. fakeFolder.localModifier().insert("A/a6");
  336. fakeFolder.localModifier().remove("A/a6.nextcloud");
  337. fakeFolder.localModifier().rename("A/a7.nextcloud", "A/a7");
  338. QVERIFY(fakeFolder.syncOnce());
  339. QVERIFY(itemInstruction(completeSpy, "A/a1", CSYNC_INSTRUCTION_SYNC));
  340. QCOMPARE(findItem(completeSpy, "A/a1")->_type, ItemTypeVirtualFileDownload);
  341. QVERIFY(itemInstruction(completeSpy, "A/a1.nextcloud", CSYNC_INSTRUCTION_NONE));
  342. QVERIFY(itemInstruction(completeSpy, "A/a2", CSYNC_INSTRUCTION_SYNC));
  343. QCOMPARE(findItem(completeSpy, "A/a2")->_type, ItemTypeVirtualFileDownload);
  344. QVERIFY(itemInstruction(completeSpy, "A/a2.nextcloud", CSYNC_INSTRUCTION_NONE));
  345. QVERIFY(itemInstruction(completeSpy, "A/a3.nextcloud", CSYNC_INSTRUCTION_REMOVE));
  346. QVERIFY(itemInstruction(completeSpy, "A/a4m", CSYNC_INSTRUCTION_NEW));
  347. QVERIFY(itemInstruction(completeSpy, "A/a4.nextcloud", CSYNC_INSTRUCTION_REMOVE));
  348. QVERIFY(itemInstruction(completeSpy, "A/a5", CSYNC_INSTRUCTION_CONFLICT));
  349. QVERIFY(itemInstruction(completeSpy, "A/a5.nextcloud", CSYNC_INSTRUCTION_NONE));
  350. QVERIFY(itemInstruction(completeSpy, "A/a6", CSYNC_INSTRUCTION_CONFLICT));
  351. QVERIFY(itemInstruction(completeSpy, "A/a7", CSYNC_INSTRUCTION_SYNC));
  352. QVERIFY(itemInstruction(completeSpy, "A/b1", CSYNC_INSTRUCTION_SYNC));
  353. QVERIFY(itemInstruction(completeSpy, "A/b2", CSYNC_INSTRUCTION_SYNC));
  354. QVERIFY(itemInstruction(completeSpy, "A/b3", CSYNC_INSTRUCTION_REMOVE));
  355. QVERIFY(itemInstruction(completeSpy, "A/b4m.nextcloud", CSYNC_INSTRUCTION_NEW));
  356. QVERIFY(itemInstruction(completeSpy, "A/b4", CSYNC_INSTRUCTION_REMOVE));
  357. QCOMPARE(dbRecord(fakeFolder, "A/a1")._type, ItemTypeFile);
  358. QCOMPARE(dbRecord(fakeFolder, "A/a2")._type, ItemTypeFile);
  359. QVERIFY(!dbRecord(fakeFolder, "A/a3").isValid());
  360. QCOMPARE(dbRecord(fakeFolder, "A/a4m")._type, ItemTypeFile);
  361. QCOMPARE(dbRecord(fakeFolder, "A/a5")._type, ItemTypeFile);
  362. QCOMPARE(dbRecord(fakeFolder, "A/a6")._type, ItemTypeFile);
  363. QCOMPARE(dbRecord(fakeFolder, "A/a7")._type, ItemTypeFile);
  364. QCOMPARE(dbRecord(fakeFolder, "A/b1")._type, ItemTypeFile);
  365. QCOMPARE(dbRecord(fakeFolder, "A/b2")._type, ItemTypeFile);
  366. QVERIFY(!dbRecord(fakeFolder, "A/b3").isValid());
  367. QCOMPARE(dbRecord(fakeFolder, "A/b4m.nextcloud")._type, ItemTypeVirtualFile);
  368. QVERIFY(!dbRecord(fakeFolder, "A/a1.nextcloud").isValid());
  369. QVERIFY(!dbRecord(fakeFolder, "A/a2.nextcloud").isValid());
  370. QVERIFY(!dbRecord(fakeFolder, "A/a3.nextcloud").isValid());
  371. QVERIFY(!dbRecord(fakeFolder, "A/a4.nextcloud").isValid());
  372. QVERIFY(!dbRecord(fakeFolder, "A/a5.nextcloud").isValid());
  373. QVERIFY(!dbRecord(fakeFolder, "A/a6.nextcloud").isValid());
  374. QVERIFY(!dbRecord(fakeFolder, "A/a7.nextcloud").isValid());
  375. QVERIFY(!dbRecord(fakeFolder, "A/b1.nextcloud").isValid());
  376. QVERIFY(!dbRecord(fakeFolder, "A/b2.nextcloud").isValid());
  377. QVERIFY(!dbRecord(fakeFolder, "A/b3.nextcloud").isValid());
  378. QVERIFY(!dbRecord(fakeFolder, "A/b4.nextcloud").isValid());
  379. triggerDownload(fakeFolder, "A/b4m");
  380. QVERIFY(fakeFolder.syncOnce());
  381. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  382. }
  383. void testVirtualFileDownloadResume()
  384. {
  385. FakeFolder fakeFolder{ FileInfo() };
  386. setupVfs(fakeFolder);
  387. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  388. QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
  389. auto cleanup = [&]() {
  390. completeSpy.clear();
  391. fakeFolder.syncJournal().wipeErrorBlacklist();
  392. };
  393. cleanup();
  394. // Create a virtual file for remote files
  395. fakeFolder.remoteModifier().mkdir("A");
  396. fakeFolder.remoteModifier().insert("A/a1");
  397. QVERIFY(fakeFolder.syncOnce());
  398. QVERIFY(fakeFolder.currentLocalState().find("A/a1.nextcloud"));
  399. cleanup();
  400. // Download by changing the db entry
  401. triggerDownload(fakeFolder, "A/a1");
  402. fakeFolder.serverErrorPaths().append("A/a1", 500);
  403. QVERIFY(!fakeFolder.syncOnce());
  404. QVERIFY(itemInstruction(completeSpy, "A/a1", CSYNC_INSTRUCTION_SYNC));
  405. QVERIFY(itemInstruction(completeSpy, "A/a1.nextcloud", CSYNC_INSTRUCTION_NONE));
  406. QVERIFY(fakeFolder.currentLocalState().find("A/a1.nextcloud"));
  407. QVERIFY(!fakeFolder.currentLocalState().find("A/a1"));
  408. QCOMPARE(dbRecord(fakeFolder, "A/a1.nextcloud")._type, ItemTypeVirtualFileDownload);
  409. QVERIFY(!dbRecord(fakeFolder, "A/a1").isValid());
  410. cleanup();
  411. fakeFolder.serverErrorPaths().clear();
  412. QVERIFY(fakeFolder.syncOnce());
  413. QVERIFY(itemInstruction(completeSpy, "A/a1", CSYNC_INSTRUCTION_SYNC));
  414. QVERIFY(itemInstruction(completeSpy, "A/a1.nextcloud", CSYNC_INSTRUCTION_NONE));
  415. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  416. QCOMPARE(dbRecord(fakeFolder, "A/a1")._type, ItemTypeFile);
  417. QVERIFY(!dbRecord(fakeFolder, "A/a1.nextcloud").isValid());
  418. }
  419. void testNewFilesNotVirtual()
  420. {
  421. FakeFolder fakeFolder{ FileInfo() };
  422. setupVfs(fakeFolder);
  423. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  424. fakeFolder.remoteModifier().mkdir("A");
  425. fakeFolder.remoteModifier().insert("A/a1");
  426. QVERIFY(fakeFolder.syncOnce());
  427. QVERIFY(fakeFolder.currentLocalState().find("A/a1.nextcloud"));
  428. fakeFolder.syncJournal().internalPinStates().setForPath("", PinState::AlwaysLocal);
  429. // Create a new remote file, it'll not be virtual
  430. fakeFolder.remoteModifier().insert("A/a2");
  431. QVERIFY(fakeFolder.syncOnce());
  432. QVERIFY(fakeFolder.currentLocalState().find("A/a2"));
  433. QVERIFY(!fakeFolder.currentLocalState().find("A/a2.nextcloud"));
  434. }
  435. void testDownloadRecursive()
  436. {
  437. FakeFolder fakeFolder{ FileInfo() };
  438. setupVfs(fakeFolder);
  439. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  440. // Create a virtual file for remote files
  441. fakeFolder.remoteModifier().mkdir("A");
  442. fakeFolder.remoteModifier().mkdir("A/Sub");
  443. fakeFolder.remoteModifier().mkdir("A/Sub/SubSub");
  444. fakeFolder.remoteModifier().mkdir("A/Sub2");
  445. fakeFolder.remoteModifier().mkdir("B");
  446. fakeFolder.remoteModifier().mkdir("B/Sub");
  447. fakeFolder.remoteModifier().insert("A/a1");
  448. fakeFolder.remoteModifier().insert("A/a2");
  449. fakeFolder.remoteModifier().insert("A/Sub/a3");
  450. fakeFolder.remoteModifier().insert("A/Sub/a4");
  451. fakeFolder.remoteModifier().insert("A/Sub/SubSub/a5");
  452. fakeFolder.remoteModifier().insert("A/Sub2/a6");
  453. fakeFolder.remoteModifier().insert("B/b1");
  454. fakeFolder.remoteModifier().insert("B/Sub/b2");
  455. QVERIFY(fakeFolder.syncOnce());
  456. QVERIFY(fakeFolder.currentLocalState().find("A/a1.nextcloud"));
  457. QVERIFY(fakeFolder.currentLocalState().find("A/a2.nextcloud"));
  458. QVERIFY(fakeFolder.currentLocalState().find("A/Sub/a3.nextcloud"));
  459. QVERIFY(fakeFolder.currentLocalState().find("A/Sub/a4.nextcloud"));
  460. QVERIFY(fakeFolder.currentLocalState().find("A/Sub/SubSub/a5.nextcloud"));
  461. QVERIFY(fakeFolder.currentLocalState().find("A/Sub2/a6.nextcloud"));
  462. QVERIFY(fakeFolder.currentLocalState().find("B/b1.nextcloud"));
  463. QVERIFY(fakeFolder.currentLocalState().find("B/Sub/b2.nextcloud"));
  464. QVERIFY(!fakeFolder.currentLocalState().find("A/a1"));
  465. QVERIFY(!fakeFolder.currentLocalState().find("A/a2"));
  466. QVERIFY(!fakeFolder.currentLocalState().find("A/Sub/a3"));
  467. QVERIFY(!fakeFolder.currentLocalState().find("A/Sub/a4"));
  468. QVERIFY(!fakeFolder.currentLocalState().find("A/Sub/SubSub/a5"));
  469. QVERIFY(!fakeFolder.currentLocalState().find("A/Sub2/a6"));
  470. QVERIFY(!fakeFolder.currentLocalState().find("B/b1"));
  471. QVERIFY(!fakeFolder.currentLocalState().find("B/Sub/b2"));
  472. // Download All file in the directory A/Sub
  473. // (as in Folder::downloadVirtualFile)
  474. fakeFolder.syncJournal().markVirtualFileForDownloadRecursively("A/Sub");
  475. QVERIFY(fakeFolder.syncOnce());
  476. QVERIFY(fakeFolder.currentLocalState().find("A/a1.nextcloud"));
  477. QVERIFY(fakeFolder.currentLocalState().find("A/a2.nextcloud"));
  478. QVERIFY(!fakeFolder.currentLocalState().find("A/Sub/a3.nextcloud"));
  479. QVERIFY(!fakeFolder.currentLocalState().find("A/Sub/a4.nextcloud"));
  480. QVERIFY(!fakeFolder.currentLocalState().find("A/Sub/SubSub/a5.nextcloud"));
  481. QVERIFY(fakeFolder.currentLocalState().find("A/Sub2/a6.nextcloud"));
  482. QVERIFY(fakeFolder.currentLocalState().find("B/b1.nextcloud"));
  483. QVERIFY(fakeFolder.currentLocalState().find("B/Sub/b2.nextcloud"));
  484. QVERIFY(!fakeFolder.currentLocalState().find("A/a1"));
  485. QVERIFY(!fakeFolder.currentLocalState().find("A/a2"));
  486. QVERIFY(fakeFolder.currentLocalState().find("A/Sub/a3"));
  487. QVERIFY(fakeFolder.currentLocalState().find("A/Sub/a4"));
  488. QVERIFY(fakeFolder.currentLocalState().find("A/Sub/SubSub/a5"));
  489. QVERIFY(!fakeFolder.currentLocalState().find("A/Sub2/a6"));
  490. QVERIFY(!fakeFolder.currentLocalState().find("B/b1"));
  491. QVERIFY(!fakeFolder.currentLocalState().find("B/Sub/b2"));
  492. // Add a file in a subfolder that was downloaded
  493. // Currently, this continue to add it as a virtual file.
  494. fakeFolder.remoteModifier().insert("A/Sub/SubSub/a7");
  495. QVERIFY(fakeFolder.syncOnce());
  496. QVERIFY(fakeFolder.currentLocalState().find("A/Sub/SubSub/a7.nextcloud"));
  497. QVERIFY(!fakeFolder.currentLocalState().find("A/Sub/SubSub/a7"));
  498. // Now download all files in "A"
  499. fakeFolder.syncJournal().markVirtualFileForDownloadRecursively("A");
  500. QVERIFY(fakeFolder.syncOnce());
  501. QVERIFY(!fakeFolder.currentLocalState().find("A/a1.nextcloud"));
  502. QVERIFY(!fakeFolder.currentLocalState().find("A/a2.nextcloud"));
  503. QVERIFY(!fakeFolder.currentLocalState().find("A/Sub/a3.nextcloud"));
  504. QVERIFY(!fakeFolder.currentLocalState().find("A/Sub/a4.nextcloud"));
  505. QVERIFY(!fakeFolder.currentLocalState().find("A/Sub/SubSub/a5.nextcloud"));
  506. QVERIFY(!fakeFolder.currentLocalState().find("A/Sub2/a6.nextcloud"));
  507. QVERIFY(!fakeFolder.currentLocalState().find("A/Sub/SubSub/a7.nextcloud"));
  508. QVERIFY(fakeFolder.currentLocalState().find("B/b1.nextcloud"));
  509. QVERIFY(fakeFolder.currentLocalState().find("B/Sub/b2.nextcloud"));
  510. QVERIFY(fakeFolder.currentLocalState().find("A/a1"));
  511. QVERIFY(fakeFolder.currentLocalState().find("A/a2"));
  512. QVERIFY(fakeFolder.currentLocalState().find("A/Sub/a3"));
  513. QVERIFY(fakeFolder.currentLocalState().find("A/Sub/a4"));
  514. QVERIFY(fakeFolder.currentLocalState().find("A/Sub/SubSub/a5"));
  515. QVERIFY(fakeFolder.currentLocalState().find("A/Sub2/a6"));
  516. QVERIFY(fakeFolder.currentLocalState().find("A/Sub/SubSub/a7"));
  517. QVERIFY(!fakeFolder.currentLocalState().find("B/b1"));
  518. QVERIFY(!fakeFolder.currentLocalState().find("B/Sub/b2"));
  519. // Now download remaining files in "B"
  520. fakeFolder.syncJournal().markVirtualFileForDownloadRecursively("B");
  521. QVERIFY(fakeFolder.syncOnce());
  522. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  523. }
  524. void testRenameToVirtual()
  525. {
  526. FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
  527. setupVfs(fakeFolder);
  528. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  529. QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
  530. auto cleanup = [&]() {
  531. completeSpy.clear();
  532. };
  533. cleanup();
  534. // If a file is renamed to <name>.nextcloud, it becomes virtual
  535. fakeFolder.localModifier().rename("A/a1", "A/a1.nextcloud");
  536. // If a file is renamed to <random>.nextcloud, the rename propagates but the
  537. // file isn't made virtual the first sync run.
  538. fakeFolder.localModifier().rename("A/a2", "A/rand.nextcloud");
  539. // dangling virtual files are removed
  540. fakeFolder.localModifier().insert("A/dangling.nextcloud", 1, ' ');
  541. QVERIFY(fakeFolder.syncOnce());
  542. QVERIFY(!fakeFolder.currentLocalState().find("A/a1"));
  543. QVERIFY(fakeFolder.currentLocalState().find("A/a1.nextcloud"));
  544. QVERIFY(fakeFolder.currentRemoteState().find("A/a1"));
  545. QVERIFY(itemInstruction(completeSpy, "A/a1.nextcloud", CSYNC_INSTRUCTION_NEW));
  546. QCOMPARE(dbRecord(fakeFolder, "A/a1.nextcloud")._type, ItemTypeVirtualFile);
  547. QVERIFY(!fakeFolder.currentLocalState().find("A/a2"));
  548. QVERIFY(!fakeFolder.currentLocalState().find("A/a2.nextcloud"));
  549. QVERIFY(fakeFolder.currentLocalState().find("A/rand"));
  550. QVERIFY(!fakeFolder.currentRemoteState().find("A/a2"));
  551. QVERIFY(fakeFolder.currentRemoteState().find("A/rand"));
  552. QVERIFY(itemInstruction(completeSpy, "A/rand", CSYNC_INSTRUCTION_RENAME));
  553. QVERIFY(dbRecord(fakeFolder, "A/rand")._type == ItemTypeFile);
  554. QVERIFY(!fakeFolder.currentLocalState().find("A/dangling.nextcloud"));
  555. cleanup();
  556. }
  557. void testRenameVirtual()
  558. {
  559. FakeFolder fakeFolder{ FileInfo() };
  560. setupVfs(fakeFolder);
  561. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  562. QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
  563. auto cleanup = [&]() {
  564. completeSpy.clear();
  565. };
  566. cleanup();
  567. fakeFolder.remoteModifier().insert("file1", 128, 'C');
  568. fakeFolder.remoteModifier().insert("file2", 256, 'C');
  569. fakeFolder.remoteModifier().insert("file3", 256, 'C');
  570. QVERIFY(fakeFolder.syncOnce());
  571. QVERIFY(fakeFolder.currentLocalState().find("file1.nextcloud"));
  572. QVERIFY(fakeFolder.currentLocalState().find("file2.nextcloud"));
  573. QVERIFY(fakeFolder.currentLocalState().find("file3.nextcloud"));
  574. cleanup();
  575. fakeFolder.localModifier().rename("file1.nextcloud", "renamed1.nextcloud");
  576. fakeFolder.localModifier().rename("file2.nextcloud", "renamed2.nextcloud");
  577. triggerDownload(fakeFolder, "file2");
  578. triggerDownload(fakeFolder, "file3");
  579. QVERIFY(fakeFolder.syncOnce());
  580. QVERIFY(!fakeFolder.currentLocalState().find("file1.nextcloud"));
  581. QVERIFY(fakeFolder.currentLocalState().find("renamed1.nextcloud"));
  582. QVERIFY(!fakeFolder.currentRemoteState().find("file1"));
  583. QVERIFY(fakeFolder.currentRemoteState().find("renamed1"));
  584. QVERIFY(itemInstruction(completeSpy, "renamed1.nextcloud", CSYNC_INSTRUCTION_RENAME));
  585. QVERIFY(dbRecord(fakeFolder, "renamed1.nextcloud").isValid());
  586. // file2 has a conflict between the download request and the rename:
  587. // the rename wins, the download is ignored
  588. QVERIFY(!fakeFolder.currentLocalState().find("file2"));
  589. QVERIFY(!fakeFolder.currentLocalState().find("file2.nextcloud"));
  590. QVERIFY(fakeFolder.currentLocalState().find("renamed2.nextcloud"));
  591. QVERIFY(fakeFolder.currentRemoteState().find("renamed2"));
  592. QVERIFY(itemInstruction(completeSpy, "renamed2.nextcloud", CSYNC_INSTRUCTION_RENAME));
  593. QVERIFY(dbRecord(fakeFolder, "renamed2.nextcloud")._type == ItemTypeVirtualFile);
  594. QVERIFY(itemInstruction(completeSpy, "file3", CSYNC_INSTRUCTION_SYNC));
  595. QVERIFY(dbRecord(fakeFolder, "file3")._type == ItemTypeFile);
  596. cleanup();
  597. // Test rename while adding/removing vfs suffix
  598. fakeFolder.localModifier().rename("renamed1.nextcloud", "R1");
  599. // Contents of file2 could also change at the same time...
  600. fakeFolder.localModifier().rename("file3", "R3.nextcloud");
  601. QVERIFY(fakeFolder.syncOnce());
  602. cleanup();
  603. }
  604. void testRenameVirtual2()
  605. {
  606. FakeFolder fakeFolder{ FileInfo() };
  607. setupVfs(fakeFolder);
  608. QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
  609. auto cleanup = [&]() {
  610. completeSpy.clear();
  611. };
  612. cleanup();
  613. fakeFolder.remoteModifier().insert("case3", 128, 'C');
  614. fakeFolder.remoteModifier().insert("case4", 256, 'C');
  615. fakeFolder.remoteModifier().insert("case5", 256, 'C');
  616. fakeFolder.remoteModifier().insert("case6", 256, 'C');
  617. QVERIFY(fakeFolder.syncOnce());
  618. triggerDownload(fakeFolder, "case4");
  619. triggerDownload(fakeFolder, "case6");
  620. QVERIFY(fakeFolder.syncOnce());
  621. QVERIFY(fakeFolder.currentLocalState().find("case3.nextcloud"));
  622. QVERIFY(fakeFolder.currentLocalState().find("case4"));
  623. QVERIFY(fakeFolder.currentLocalState().find("case5.nextcloud"));
  624. QVERIFY(fakeFolder.currentLocalState().find("case6"));
  625. cleanup();
  626. // Case 1: foo -> bar (tested elsewhere)
  627. // Case 2: foo.oc -> bar.oc (tested elsewhere)
  628. // Case 3: foo.oc -> bar (db unchanged)
  629. fakeFolder.localModifier().rename("case3.nextcloud", "case3-rename");
  630. // Case 4: foo -> bar.oc (db unchanged)
  631. fakeFolder.localModifier().rename("case4", "case4-rename.nextcloud");
  632. // Case 5: foo -> bar (db dehydrate)
  633. fakeFolder.localModifier().rename("case5.nextcloud", "case5-rename.nextcloud");
  634. triggerDownload(fakeFolder, "case5");
  635. // Case 6: foo.oc -> bar.oc (db hydrate)
  636. fakeFolder.localModifier().rename("case6", "case6-rename");
  637. markForDehydration(fakeFolder, "case6");
  638. QVERIFY(fakeFolder.syncOnce());
  639. // Case 3: the rename went though, hydration is forgotten
  640. QVERIFY(!fakeFolder.currentLocalState().find("case3"));
  641. QVERIFY(!fakeFolder.currentLocalState().find("case3.nextcloud"));
  642. QVERIFY(!fakeFolder.currentLocalState().find("case3-rename"));
  643. QVERIFY(fakeFolder.currentLocalState().find("case3-rename.nextcloud"));
  644. QVERIFY(!fakeFolder.currentRemoteState().find("case3"));
  645. QVERIFY(fakeFolder.currentRemoteState().find("case3-rename"));
  646. QVERIFY(itemInstruction(completeSpy, "case3-rename.nextcloud", CSYNC_INSTRUCTION_RENAME));
  647. QVERIFY(dbRecord(fakeFolder, "case3-rename.nextcloud")._type == ItemTypeVirtualFile);
  648. // Case 4: the rename went though, dehydration is forgotten
  649. QVERIFY(!fakeFolder.currentLocalState().find("case4"));
  650. QVERIFY(!fakeFolder.currentLocalState().find("case4.nextcloud"));
  651. QVERIFY(fakeFolder.currentLocalState().find("case4-rename"));
  652. QVERIFY(!fakeFolder.currentLocalState().find("case4-rename.nextcloud"));
  653. QVERIFY(!fakeFolder.currentRemoteState().find("case4"));
  654. QVERIFY(fakeFolder.currentRemoteState().find("case4-rename"));
  655. QVERIFY(itemInstruction(completeSpy, "case4-rename", CSYNC_INSTRUCTION_RENAME));
  656. QVERIFY(dbRecord(fakeFolder, "case4-rename")._type == ItemTypeFile);
  657. // Case 5: the rename went though, hydration is forgotten
  658. QVERIFY(!fakeFolder.currentLocalState().find("case5"));
  659. QVERIFY(!fakeFolder.currentLocalState().find("case5.nextcloud"));
  660. QVERIFY(!fakeFolder.currentLocalState().find("case5-rename"));
  661. QVERIFY(fakeFolder.currentLocalState().find("case5-rename.nextcloud"));
  662. QVERIFY(!fakeFolder.currentRemoteState().find("case5"));
  663. QVERIFY(fakeFolder.currentRemoteState().find("case5-rename"));
  664. QVERIFY(itemInstruction(completeSpy, "case5-rename.nextcloud", CSYNC_INSTRUCTION_RENAME));
  665. QVERIFY(dbRecord(fakeFolder, "case5-rename.nextcloud")._type == ItemTypeVirtualFile);
  666. // Case 6: the rename went though, dehydration is forgotten
  667. QVERIFY(!fakeFolder.currentLocalState().find("case6"));
  668. QVERIFY(!fakeFolder.currentLocalState().find("case6.nextcloud"));
  669. QVERIFY(fakeFolder.currentLocalState().find("case6-rename"));
  670. QVERIFY(!fakeFolder.currentLocalState().find("case6-rename.nextcloud"));
  671. QVERIFY(!fakeFolder.currentRemoteState().find("case6"));
  672. QVERIFY(fakeFolder.currentRemoteState().find("case6-rename"));
  673. QVERIFY(itemInstruction(completeSpy, "case6-rename", CSYNC_INSTRUCTION_RENAME));
  674. QVERIFY(dbRecord(fakeFolder, "case6-rename")._type == ItemTypeFile);
  675. }
  676. // Dehydration via sync works
  677. void testSyncDehydration()
  678. {
  679. FakeFolder fakeFolder{ FileInfo::A12_B12_C12_S12() };
  680. setupVfs(fakeFolder);
  681. QVERIFY(fakeFolder.syncOnce());
  682. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  683. QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
  684. auto cleanup = [&]() {
  685. completeSpy.clear();
  686. };
  687. cleanup();
  688. //
  689. // Mark for dehydration and check
  690. //
  691. markForDehydration(fakeFolder, "A/a1");
  692. markForDehydration(fakeFolder, "A/a2");
  693. fakeFolder.remoteModifier().appendByte("A/a2");
  694. // expect: normal dehydration
  695. markForDehydration(fakeFolder, "B/b1");
  696. fakeFolder.remoteModifier().remove("B/b1");
  697. // expect: local removal
  698. markForDehydration(fakeFolder, "B/b2");
  699. fakeFolder.remoteModifier().rename("B/b2", "B/b3");
  700. // expect: B/b2 is gone, B/b3 is NEW placeholder
  701. markForDehydration(fakeFolder, "C/c1");
  702. fakeFolder.localModifier().appendByte("C/c1");
  703. // expect: no dehydration, upload of c1
  704. markForDehydration(fakeFolder, "C/c2");
  705. fakeFolder.localModifier().appendByte("C/c2");
  706. fakeFolder.remoteModifier().appendByte("C/c2");
  707. fakeFolder.remoteModifier().appendByte("C/c2");
  708. // expect: no dehydration, conflict
  709. QVERIFY(fakeFolder.syncOnce());
  710. auto isDehydrated = [&](const QString &path) {
  711. QString placeholder = path + ".nextcloud";
  712. return !fakeFolder.currentLocalState().find(path)
  713. && fakeFolder.currentLocalState().find(placeholder);
  714. };
  715. auto hasDehydratedDbEntries = [&](const QString &path) {
  716. SyncJournalFileRecord normal, suffix;
  717. fakeFolder.syncJournal().getFileRecord(path, &normal);
  718. fakeFolder.syncJournal().getFileRecord(path + ".nextcloud", &suffix);
  719. return !normal.isValid() && suffix.isValid() && suffix._type == ItemTypeVirtualFile;
  720. };
  721. QVERIFY(isDehydrated("A/a1"));
  722. QVERIFY(hasDehydratedDbEntries("A/a1"));
  723. QVERIFY(itemInstruction(completeSpy, "A/a1.nextcloud", CSYNC_INSTRUCTION_SYNC));
  724. QCOMPARE(findItem(completeSpy, "A/a1.nextcloud")->_type, ItemTypeVirtualFileDehydration);
  725. QCOMPARE(findItem(completeSpy, "A/a1.nextcloud")->_file, QStringLiteral("A/a1"));
  726. QCOMPARE(findItem(completeSpy, "A/a1.nextcloud")->_renameTarget, QStringLiteral("A/a1.nextcloud"));
  727. QVERIFY(isDehydrated("A/a2"));
  728. QVERIFY(hasDehydratedDbEntries("A/a2"));
  729. QVERIFY(itemInstruction(completeSpy, "A/a2.nextcloud", CSYNC_INSTRUCTION_SYNC));
  730. QCOMPARE(findItem(completeSpy, "A/a2.nextcloud")->_type, ItemTypeVirtualFileDehydration);
  731. QVERIFY(!fakeFolder.currentLocalState().find("B/b1"));
  732. QVERIFY(!fakeFolder.currentRemoteState().find("B/b1"));
  733. QVERIFY(itemInstruction(completeSpy, "B/b1", CSYNC_INSTRUCTION_REMOVE));
  734. QVERIFY(!fakeFolder.currentLocalState().find("B/b2"));
  735. QVERIFY(!fakeFolder.currentRemoteState().find("B/b2"));
  736. QVERIFY(isDehydrated("B/b3"));
  737. QVERIFY(hasDehydratedDbEntries("B/b3"));
  738. QVERIFY(itemInstruction(completeSpy, "B/b2", CSYNC_INSTRUCTION_REMOVE));
  739. QVERIFY(itemInstruction(completeSpy, "B/b3.nextcloud", CSYNC_INSTRUCTION_NEW));
  740. QCOMPARE(fakeFolder.currentRemoteState().find("C/c1")->size, 25);
  741. QVERIFY(itemInstruction(completeSpy, "C/c1", CSYNC_INSTRUCTION_SYNC));
  742. QCOMPARE(fakeFolder.currentRemoteState().find("C/c2")->size, 26);
  743. QVERIFY(itemInstruction(completeSpy, "C/c2", CSYNC_INSTRUCTION_CONFLICT));
  744. cleanup();
  745. auto expectedLocalState = fakeFolder.currentLocalState();
  746. auto expectedRemoteState = fakeFolder.currentRemoteState();
  747. QVERIFY(fakeFolder.syncOnce());
  748. QCOMPARE(fakeFolder.currentLocalState(), expectedLocalState);
  749. QCOMPARE(fakeFolder.currentRemoteState(), expectedRemoteState);
  750. }
  751. void testWipeVirtualSuffixFiles()
  752. {
  753. FakeFolder fakeFolder{ FileInfo{} };
  754. setupVfs(fakeFolder);
  755. // Create a suffix-vfs baseline
  756. fakeFolder.remoteModifier().mkdir("A");
  757. fakeFolder.remoteModifier().mkdir("A/B");
  758. fakeFolder.remoteModifier().insert("f1");
  759. fakeFolder.remoteModifier().insert("A/a1");
  760. fakeFolder.remoteModifier().insert("A/a3");
  761. fakeFolder.remoteModifier().insert("A/B/b1");
  762. fakeFolder.localModifier().mkdir("A");
  763. fakeFolder.localModifier().mkdir("A/B");
  764. fakeFolder.localModifier().insert("f2");
  765. fakeFolder.localModifier().insert("A/a2");
  766. fakeFolder.localModifier().insert("A/B/b2");
  767. QVERIFY(fakeFolder.syncOnce());
  768. QVERIFY(fakeFolder.currentLocalState().find("f1.nextcloud"));
  769. QVERIFY(fakeFolder.currentLocalState().find("A/a1.nextcloud"));
  770. QVERIFY(fakeFolder.currentLocalState().find("A/a3.nextcloud"));
  771. QVERIFY(fakeFolder.currentLocalState().find("A/B/b1.nextcloud"));
  772. // Make local changes to a3
  773. fakeFolder.localModifier().remove("A/a3.nextcloud");
  774. fakeFolder.localModifier().insert("A/a3.nextcloud", 100);
  775. // Now wipe the virtuals
  776. SyncEngine::wipeVirtualFiles(fakeFolder.localPath(), fakeFolder.syncJournal(), *fakeFolder.syncEngine().syncOptions()._vfs);
  777. QVERIFY(!fakeFolder.currentLocalState().find("f1.nextcloud"));
  778. QVERIFY(!fakeFolder.currentLocalState().find("A/a1.nextcloud"));
  779. QVERIFY(fakeFolder.currentLocalState().find("A/a3.nextcloud"));
  780. QVERIFY(!fakeFolder.currentLocalState().find("A/B/b1.nextcloud"));
  781. fakeFolder.switchToVfs(QSharedPointer<Vfs>(new VfsOff));
  782. QVERIFY(fakeFolder.syncOnce());
  783. QVERIFY(fakeFolder.currentRemoteState().find("A/a3.nextcloud")); // regular upload
  784. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  785. }
  786. void testNewVirtuals()
  787. {
  788. FakeFolder fakeFolder{ FileInfo() };
  789. setupVfs(fakeFolder);
  790. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  791. auto setPin = [&] (const QByteArray &path, PinState state) {
  792. fakeFolder.syncJournal().internalPinStates().setForPath(path, state);
  793. };
  794. fakeFolder.remoteModifier().mkdir("local");
  795. fakeFolder.remoteModifier().mkdir("online");
  796. fakeFolder.remoteModifier().mkdir("unspec");
  797. QVERIFY(fakeFolder.syncOnce());
  798. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  799. setPin("local", PinState::AlwaysLocal);
  800. setPin("online", PinState::OnlineOnly);
  801. setPin("unspec", PinState::Unspecified);
  802. // Test 1: root is Unspecified
  803. fakeFolder.remoteModifier().insert("file1");
  804. fakeFolder.remoteModifier().insert("online/file1");
  805. fakeFolder.remoteModifier().insert("local/file1");
  806. fakeFolder.remoteModifier().insert("unspec/file1");
  807. QVERIFY(fakeFolder.syncOnce());
  808. QVERIFY(fakeFolder.currentLocalState().find("file1.nextcloud"));
  809. QVERIFY(fakeFolder.currentLocalState().find("online/file1.nextcloud"));
  810. QVERIFY(fakeFolder.currentLocalState().find("local/file1"));
  811. QVERIFY(fakeFolder.currentLocalState().find("unspec/file1.nextcloud"));
  812. // Test 2: change root to AlwaysLocal
  813. setPin("", PinState::AlwaysLocal);
  814. fakeFolder.remoteModifier().insert("file2");
  815. fakeFolder.remoteModifier().insert("online/file2");
  816. fakeFolder.remoteModifier().insert("local/file2");
  817. fakeFolder.remoteModifier().insert("unspec/file2");
  818. QVERIFY(fakeFolder.syncOnce());
  819. QVERIFY(fakeFolder.currentLocalState().find("file2"));
  820. QVERIFY(fakeFolder.currentLocalState().find("online/file2.nextcloud"));
  821. QVERIFY(fakeFolder.currentLocalState().find("local/file2"));
  822. QVERIFY(fakeFolder.currentLocalState().find("unspec/file2.nextcloud"));
  823. // root file1 was hydrated due to its new pin state
  824. QVERIFY(fakeFolder.currentLocalState().find("file1"));
  825. // file1 is unchanged in the explicitly pinned subfolders
  826. QVERIFY(fakeFolder.currentLocalState().find("online/file1.nextcloud"));
  827. QVERIFY(fakeFolder.currentLocalState().find("local/file1"));
  828. QVERIFY(fakeFolder.currentLocalState().find("unspec/file1.nextcloud"));
  829. // Test 3: change root to OnlineOnly
  830. setPin("", PinState::OnlineOnly);
  831. fakeFolder.remoteModifier().insert("file3");
  832. fakeFolder.remoteModifier().insert("online/file3");
  833. fakeFolder.remoteModifier().insert("local/file3");
  834. fakeFolder.remoteModifier().insert("unspec/file3");
  835. QVERIFY(fakeFolder.syncOnce());
  836. QVERIFY(fakeFolder.currentLocalState().find("file3.nextcloud"));
  837. QVERIFY(fakeFolder.currentLocalState().find("online/file3.nextcloud"));
  838. QVERIFY(fakeFolder.currentLocalState().find("local/file3"));
  839. QVERIFY(fakeFolder.currentLocalState().find("unspec/file3.nextcloud"));
  840. // root file1 was dehydrated due to its new pin state
  841. QVERIFY(fakeFolder.currentLocalState().find("file1.nextcloud"));
  842. // file1 is unchanged in the explicitly pinned subfolders
  843. QVERIFY(fakeFolder.currentLocalState().find("online/file1.nextcloud"));
  844. QVERIFY(fakeFolder.currentLocalState().find("local/file1"));
  845. QVERIFY(fakeFolder.currentLocalState().find("unspec/file1.nextcloud"));
  846. }
  847. // Check what happens if vfs-suffixed files exist on the server or in the db
  848. void testSuffixOnServerOrDb()
  849. {
  850. FakeFolder fakeFolder{ FileInfo() };
  851. QSignalSpy completeSpy(&fakeFolder.syncEngine(), SIGNAL(itemCompleted(const SyncFileItemPtr &)));
  852. auto cleanup = [&]() {
  853. completeSpy.clear();
  854. };
  855. cleanup();
  856. // file1.nextcloud is happily synced with Vfs::Off
  857. fakeFolder.remoteModifier().mkdir("A");
  858. fakeFolder.remoteModifier().insert("A/file1.nextcloud");
  859. QVERIFY(fakeFolder.syncOnce());
  860. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  861. cleanup();
  862. // Enable suffix vfs
  863. setupVfs(fakeFolder);
  864. // Local changes of suffixed file do nothing
  865. fakeFolder.localModifier().appendByte("A/file1.nextcloud");
  866. QVERIFY(fakeFolder.syncOnce());
  867. QVERIFY(itemInstruction(completeSpy, "A/file1.nextcloud", CSYNC_INSTRUCTION_IGNORE));
  868. cleanup();
  869. // Remote don't do anything either
  870. fakeFolder.remoteModifier().appendByte("A/file1.nextcloud");
  871. QVERIFY(fakeFolder.syncOnce());
  872. QVERIFY(itemInstruction(completeSpy, "A/file1.nextcloud", CSYNC_INSTRUCTION_IGNORE));
  873. cleanup();
  874. // New files with a suffix aren't propagated downwards in the first place
  875. fakeFolder.remoteModifier().insert("A/file2.nextcloud");
  876. QVERIFY(fakeFolder.syncOnce());
  877. QVERIFY(itemInstruction(completeSpy, "A/file2.nextcloud", CSYNC_INSTRUCTION_IGNORE));
  878. QVERIFY(fakeFolder.currentRemoteState().find("A/file2.nextcloud"));
  879. QVERIFY(!fakeFolder.currentLocalState().find("A/file2"));
  880. QVERIFY(!fakeFolder.currentLocalState().find("A/file2.nextcloud"));
  881. QVERIFY(!fakeFolder.currentLocalState().find("A/file2.nextcloud.nextcloud"));
  882. cleanup();
  883. }
  884. void testAvailability()
  885. {
  886. FakeFolder fakeFolder{ FileInfo() };
  887. auto vfs = setupVfs(fakeFolder);
  888. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  889. auto setPin = [&] (const QByteArray &path, PinState state) {
  890. fakeFolder.syncJournal().internalPinStates().setForPath(path, state);
  891. };
  892. fakeFolder.remoteModifier().mkdir("local");
  893. fakeFolder.remoteModifier().mkdir("local/sub");
  894. fakeFolder.remoteModifier().mkdir("online");
  895. fakeFolder.remoteModifier().mkdir("online/sub");
  896. fakeFolder.remoteModifier().mkdir("unspec");
  897. QVERIFY(fakeFolder.syncOnce());
  898. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  899. setPin("local", PinState::AlwaysLocal);
  900. setPin("online", PinState::OnlineOnly);
  901. setPin("unspec", PinState::Unspecified);
  902. fakeFolder.remoteModifier().insert("file1");
  903. fakeFolder.remoteModifier().insert("online/file1");
  904. fakeFolder.remoteModifier().insert("online/file2");
  905. fakeFolder.remoteModifier().insert("local/file1");
  906. fakeFolder.remoteModifier().insert("local/file2");
  907. fakeFolder.remoteModifier().insert("unspec/file1");
  908. QVERIFY(fakeFolder.syncOnce());
  909. // root is unspecified
  910. QCOMPARE(*vfs->availability("file1.nextcloud"), VfsItemAvailability::AllDehydrated);
  911. QCOMPARE(*vfs->availability("local"), VfsItemAvailability::AlwaysLocal);
  912. QCOMPARE(*vfs->availability("local/file1"), VfsItemAvailability::AlwaysLocal);
  913. QCOMPARE(*vfs->availability("online"), VfsItemAvailability::OnlineOnly);
  914. QCOMPARE(*vfs->availability("online/file1.nextcloud"), VfsItemAvailability::OnlineOnly);
  915. QCOMPARE(*vfs->availability("unspec"), VfsItemAvailability::AllDehydrated);
  916. QCOMPARE(*vfs->availability("unspec/file1.nextcloud"), VfsItemAvailability::AllDehydrated);
  917. // Subitem pin states can ruin "pure" availabilities
  918. setPin("local/sub", PinState::OnlineOnly);
  919. QCOMPARE(*vfs->availability("local"), VfsItemAvailability::AllHydrated);
  920. setPin("online/sub", PinState::Unspecified);
  921. QCOMPARE(*vfs->availability("online"), VfsItemAvailability::AllDehydrated);
  922. triggerDownload(fakeFolder, "unspec/file1");
  923. setPin("local/file2", PinState::OnlineOnly);
  924. setPin("online/file2", PinState::AlwaysLocal);
  925. QVERIFY(fakeFolder.syncOnce());
  926. QCOMPARE(*vfs->availability("unspec"), VfsItemAvailability::AllHydrated);
  927. QCOMPARE(*vfs->availability("local"), VfsItemAvailability::Mixed);
  928. QCOMPARE(*vfs->availability("online"), VfsItemAvailability::Mixed);
  929. vfs->setPinState("local", PinState::AlwaysLocal);
  930. vfs->setPinState("online", PinState::OnlineOnly);
  931. QVERIFY(fakeFolder.syncOnce());
  932. QCOMPARE(*vfs->availability("online"), VfsItemAvailability::OnlineOnly);
  933. QCOMPARE(*vfs->availability("local"), VfsItemAvailability::AlwaysLocal);
  934. auto r = vfs->availability("nonexistant");
  935. QVERIFY(!r);
  936. QCOMPARE(r.error(), Vfs::AvailabilityError::NoSuchItem);
  937. }
  938. void testPinStateLocals()
  939. {
  940. FakeFolder fakeFolder{ FileInfo() };
  941. auto vfs = setupVfs(fakeFolder);
  942. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  943. auto setPin = [&] (const QByteArray &path, PinState state) {
  944. fakeFolder.syncJournal().internalPinStates().setForPath(path, state);
  945. };
  946. fakeFolder.remoteModifier().mkdir("local");
  947. fakeFolder.remoteModifier().mkdir("online");
  948. fakeFolder.remoteModifier().mkdir("unspec");
  949. QVERIFY(fakeFolder.syncOnce());
  950. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  951. setPin("local", PinState::AlwaysLocal);
  952. setPin("online", PinState::OnlineOnly);
  953. setPin("unspec", PinState::Unspecified);
  954. fakeFolder.localModifier().insert("file1");
  955. fakeFolder.localModifier().insert("online/file1");
  956. fakeFolder.localModifier().insert("online/file2");
  957. fakeFolder.localModifier().insert("local/file1");
  958. fakeFolder.localModifier().insert("unspec/file1");
  959. QVERIFY(fakeFolder.syncOnce());
  960. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  961. // root is unspecified
  962. QCOMPARE(*vfs->pinState("file1"), PinState::Unspecified);
  963. QCOMPARE(*vfs->pinState("local/file1"), PinState::AlwaysLocal);
  964. QCOMPARE(*vfs->pinState("online/file1"), PinState::Unspecified);
  965. QCOMPARE(*vfs->pinState("unspec/file1"), PinState::Unspecified);
  966. // Sync again: bad pin states of new local files usually take effect on second sync
  967. QVERIFY(fakeFolder.syncOnce());
  968. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  969. // When a file in an online-only folder is renamed, it retains its pin
  970. fakeFolder.localModifier().rename("online/file1", "online/file1rename");
  971. fakeFolder.remoteModifier().rename("online/file2", "online/file2rename");
  972. QVERIFY(fakeFolder.syncOnce());
  973. QCOMPARE(*vfs->pinState("online/file1rename"), PinState::Unspecified);
  974. QCOMPARE(*vfs->pinState("online/file2rename"), PinState::Unspecified);
  975. // When a folder is renamed, the pin states inside should be retained
  976. fakeFolder.localModifier().rename("online", "onlinerenamed1");
  977. QVERIFY(fakeFolder.syncOnce());
  978. QCOMPARE(*vfs->pinState("onlinerenamed1"), PinState::OnlineOnly);
  979. QCOMPARE(*vfs->pinState("onlinerenamed1/file1rename"), PinState::Unspecified);
  980. fakeFolder.remoteModifier().rename("onlinerenamed1", "onlinerenamed2");
  981. QVERIFY(fakeFolder.syncOnce());
  982. QCOMPARE(*vfs->pinState("onlinerenamed2"), PinState::OnlineOnly);
  983. QCOMPARE(*vfs->pinState("onlinerenamed2/file1rename"), PinState::Unspecified);
  984. QCOMPARE(fakeFolder.currentLocalState(), fakeFolder.currentRemoteState());
  985. }
  986. };
  987. QTEST_GUILESS_MAIN(TestSyncVirtualFiles)
  988. #include "testsyncvirtualfiles.moc"