testsynccfapi.cpp 50 KB

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