testsynccfapi.cpp 54 KB

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