ProjectEntityUpdateHandlerCanary.js 44 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634
  1. /* NOTE: this file is an almost exact copy of ProjectEntityUpdateHandler.js.
  2. * The only difference is that it imports
  3. * ProjectEntityMongoUpdateHandlerCanary.js. It's meant to be a short-lived
  4. * module, so that we can test the async/await code in production for some code
  5. * paths only.
  6. */
  7. /* eslint-disable
  8. camelcase,
  9. handle-callback-err,
  10. max-len,
  11. one-var,
  12. standard/no-callback-literal,
  13. */
  14. // TODO: This file was created by bulk-decaffeinate.
  15. // Fix any style issues and re-enable lint.
  16. /*
  17. * decaffeinate suggestions:
  18. * DS101: Remove unnecessary use of Array.from
  19. * DS102: Remove unnecessary code created because of implicit returns
  20. * DS103: Rewrite code to no longer use __guard__
  21. * DS201: Simplify complex destructure assignments
  22. * DS207: Consider shorter variations of null checks
  23. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  24. */
  25. let ProjectEntityUpdateHandler, self
  26. const _ = require('lodash')
  27. const async = require('async')
  28. const logger = require('logger-sharelatex')
  29. const Settings = require('settings-sharelatex')
  30. const path = require('path')
  31. const { Doc } = require('../../models/Doc')
  32. const DocstoreManager = require('../Docstore/DocstoreManager')
  33. const DocumentUpdaterHandler = require('../../Features/DocumentUpdater/DocumentUpdaterHandler')
  34. const Errors = require('../Errors/Errors')
  35. const { File } = require('../../models/File')
  36. const FileStoreHandler = require('../FileStore/FileStoreHandler')
  37. const LockManager = require('../../infrastructure/LockManager')
  38. const { Project } = require('../../models/Project')
  39. const ProjectEntityHandler = require('./ProjectEntityHandler')
  40. const ProjectGetter = require('./ProjectGetter')
  41. const ProjectLocator = require('./ProjectLocator')
  42. const ProjectUpdateHandler = require('./ProjectUpdateHandler')
  43. const ProjectEntityMongoUpdateHandler = require('./ProjectEntityMongoUpdateHandlerCanary')
  44. const SafePath = require('./SafePath')
  45. const TpdsUpdateSender = require('../ThirdPartyDataStore/TpdsUpdateSender')
  46. const LOCK_NAMESPACE = 'sequentialProjectStructureUpdateLock'
  47. const validRootDocExtensions = Settings.validRootDocExtensions
  48. const validRootDocRegExp = new RegExp(
  49. `^\\.(${validRootDocExtensions.join('|')})$`,
  50. 'i'
  51. )
  52. const wrapWithLock = function(methodWithoutLock) {
  53. // This lock is used to make sure that the project structure updates are made
  54. // sequentially. In particular the updates must be made in mongo and sent to
  55. // the doc-updater in the same order.
  56. let methodWithLock
  57. if (typeof methodWithoutLock === 'function') {
  58. methodWithLock = function(project_id, ...rest) {
  59. const adjustedLength = Math.max(rest.length, 1),
  60. args = rest.slice(0, adjustedLength - 1),
  61. callback = rest[adjustedLength - 1]
  62. return LockManager.runWithLock(
  63. LOCK_NAMESPACE,
  64. project_id,
  65. cb => methodWithoutLock(project_id, ...Array.from(args), cb),
  66. callback
  67. )
  68. }
  69. methodWithLock.withoutLock = methodWithoutLock
  70. return methodWithLock
  71. } else {
  72. // handle case with separate setup and locked stages
  73. const wrapWithSetup = methodWithoutLock.beforeLock // a function to set things up before the lock
  74. const mainTask = methodWithoutLock.withLock // function to execute inside the lock
  75. methodWithLock = wrapWithSetup(function(project_id, ...rest) {
  76. const adjustedLength = Math.max(rest.length, 1),
  77. args = rest.slice(0, adjustedLength - 1),
  78. callback = rest[adjustedLength - 1]
  79. return LockManager.runWithLock(
  80. LOCK_NAMESPACE,
  81. project_id,
  82. cb => mainTask(project_id, ...Array.from(args), cb),
  83. callback
  84. )
  85. })
  86. methodWithLock.withoutLock = wrapWithSetup(mainTask)
  87. methodWithLock.beforeLock = methodWithoutLock.beforeLock
  88. methodWithLock.mainTask = methodWithoutLock.withLock
  89. return methodWithLock
  90. }
  91. }
  92. module.exports = ProjectEntityUpdateHandler = self = {
  93. copyFileFromExistingProjectWithProject: wrapWithLock({
  94. beforeLock(next) {
  95. return function(
  96. project_id,
  97. project,
  98. folder_id,
  99. originalProject_id,
  100. origonalFileRef,
  101. userId,
  102. callback
  103. ) {
  104. if (callback == null) {
  105. callback = function(error, fileRef, folder_id) {}
  106. }
  107. logger.log(
  108. { project_id, folder_id, originalProject_id, origonalFileRef },
  109. 'copying file in s3 with project'
  110. )
  111. folder_id = ProjectEntityMongoUpdateHandler._confirmFolder(
  112. project,
  113. folder_id
  114. )
  115. if (origonalFileRef == null) {
  116. logger.err(
  117. { project_id, folder_id, originalProject_id, origonalFileRef },
  118. 'file trying to copy is null'
  119. )
  120. return callback()
  121. }
  122. // convert any invalid characters in original file to '_'
  123. const fileProperties = {
  124. name: SafePath.clean(origonalFileRef.name)
  125. }
  126. if (origonalFileRef.linkedFileData != null) {
  127. fileProperties.linkedFileData = origonalFileRef.linkedFileData
  128. }
  129. if (origonalFileRef.hash != null) {
  130. fileProperties.hash = origonalFileRef.hash
  131. }
  132. const fileRef = new File(fileProperties)
  133. return FileStoreHandler.copyFile(
  134. originalProject_id,
  135. origonalFileRef._id,
  136. project._id,
  137. fileRef._id,
  138. function(err, fileStoreUrl) {
  139. if (err != null) {
  140. logger.warn(
  141. {
  142. err,
  143. project_id,
  144. folder_id,
  145. originalProject_id,
  146. origonalFileRef
  147. },
  148. 'error coping file in s3'
  149. )
  150. return callback(err)
  151. }
  152. return next(
  153. project_id,
  154. project,
  155. folder_id,
  156. originalProject_id,
  157. origonalFileRef,
  158. userId,
  159. fileRef,
  160. fileStoreUrl,
  161. callback
  162. )
  163. }
  164. )
  165. }
  166. },
  167. withLock(
  168. project_id,
  169. project,
  170. folder_id,
  171. originalProject_id,
  172. origonalFileRef,
  173. userId,
  174. fileRef,
  175. fileStoreUrl,
  176. callback
  177. ) {
  178. if (callback == null) {
  179. callback = function(error, fileRef, folder_id) {}
  180. }
  181. const projectHistoryId = __guard__(
  182. project.overleaf != null ? project.overleaf.history : undefined,
  183. x => x.id
  184. )
  185. return ProjectEntityMongoUpdateHandler._putElement(
  186. project,
  187. folder_id,
  188. fileRef,
  189. 'file',
  190. function(err, result, newProject) {
  191. if (err != null) {
  192. logger.warn(
  193. { err, project_id, folder_id },
  194. 'error putting element as part of copy'
  195. )
  196. return callback(err)
  197. }
  198. return TpdsUpdateSender.addFile(
  199. {
  200. project_id,
  201. file_id: fileRef._id,
  202. path: __guard__(
  203. result != null ? result.path : undefined,
  204. x1 => x1.fileSystem
  205. ),
  206. rev: fileRef.rev,
  207. project_name: project.name
  208. },
  209. function(err) {
  210. if (err != null) {
  211. logger.err(
  212. {
  213. err,
  214. project_id,
  215. folder_id,
  216. originalProject_id,
  217. origonalFileRef
  218. },
  219. 'error sending file to tpds worker'
  220. )
  221. }
  222. const newFiles = [
  223. {
  224. file: fileRef,
  225. path: __guard__(
  226. result != null ? result.path : undefined,
  227. x2 => x2.fileSystem
  228. ),
  229. url: fileStoreUrl
  230. }
  231. ]
  232. return DocumentUpdaterHandler.updateProjectStructure(
  233. project_id,
  234. projectHistoryId,
  235. userId,
  236. { newFiles, newProject },
  237. function(error) {
  238. if (error != null) {
  239. return callback(error)
  240. }
  241. return callback(null, fileRef, folder_id)
  242. }
  243. )
  244. }
  245. )
  246. }
  247. )
  248. }
  249. }),
  250. updateDocLines(
  251. project_id,
  252. doc_id,
  253. lines,
  254. version,
  255. ranges,
  256. lastUpdatedAt,
  257. lastUpdatedBy,
  258. callback
  259. ) {
  260. if (callback == null) {
  261. callback = function(error) {}
  262. }
  263. return ProjectGetter.getProjectWithoutDocLines(project_id, function(
  264. err,
  265. project
  266. ) {
  267. if (err != null) {
  268. return callback(err)
  269. }
  270. if (project == null) {
  271. return callback(new Errors.NotFoundError('project not found'))
  272. }
  273. logger.log({ project_id, doc_id }, 'updating doc lines')
  274. return ProjectLocator.findElement(
  275. { project, element_id: doc_id, type: 'docs' },
  276. function(err, doc, path) {
  277. let isDeletedDoc = false
  278. if (err != null) {
  279. if (err instanceof Errors.NotFoundError) {
  280. // We need to be able to update the doclines of deleted docs. This is
  281. // so the doc-updater can flush a doc's content to the doc-store after
  282. // the doc is deleted.
  283. isDeletedDoc = true
  284. doc = _.find(
  285. project.deletedDocs,
  286. doc => doc._id.toString() === doc_id.toString()
  287. )
  288. } else {
  289. return callback(err)
  290. }
  291. }
  292. if (doc == null) {
  293. // Do not allow an update to a doc which has never exist on this project
  294. logger.warn(
  295. { doc_id, project_id, lines },
  296. 'doc not found while updating doc lines'
  297. )
  298. return callback(new Errors.NotFoundError('doc not found'))
  299. }
  300. logger.log(
  301. { project_id, doc_id },
  302. 'telling docstore manager to update doc'
  303. )
  304. return DocstoreManager.updateDoc(
  305. project_id,
  306. doc_id,
  307. lines,
  308. version,
  309. ranges,
  310. function(err, modified, rev) {
  311. if (err != null) {
  312. logger.warn(
  313. { err, doc_id, project_id, lines },
  314. 'error sending doc to docstore'
  315. )
  316. return callback(err)
  317. }
  318. logger.log(
  319. { project_id, doc_id, modified },
  320. 'finished updating doc lines'
  321. )
  322. // path will only be present if the doc is not deleted
  323. if (modified && !isDeletedDoc) {
  324. // Don't need to block for marking as updated
  325. ProjectUpdateHandler.markAsUpdated(
  326. project_id,
  327. lastUpdatedAt,
  328. lastUpdatedBy
  329. )
  330. return TpdsUpdateSender.addDoc(
  331. {
  332. project_id,
  333. path: path.fileSystem,
  334. doc_id,
  335. project_name: project.name,
  336. rev
  337. },
  338. callback
  339. )
  340. } else {
  341. return callback()
  342. }
  343. }
  344. )
  345. }
  346. )
  347. })
  348. },
  349. setRootDoc(project_id, newRootDocID, callback) {
  350. if (callback == null) {
  351. callback = function(error) {}
  352. }
  353. logger.log({ project_id, rootDocId: newRootDocID }, 'setting root doc')
  354. if (project_id == null || newRootDocID == null) {
  355. return callback(
  356. new Errors.InvalidError('missing arguments (project or doc)')
  357. )
  358. }
  359. ProjectEntityHandler.getDocPathByProjectIdAndDocId(
  360. project_id,
  361. newRootDocID,
  362. function(err, docPath) {
  363. if (err != null) {
  364. return callback(err)
  365. }
  366. if (ProjectEntityUpdateHandler.isPathValidForRootDoc(docPath)) {
  367. return Project.update(
  368. { _id: project_id },
  369. { rootDoc_id: newRootDocID },
  370. {},
  371. callback
  372. )
  373. } else {
  374. return callback(
  375. new Errors.UnsupportedFileTypeError(
  376. 'invalid file extension for root doc'
  377. )
  378. )
  379. }
  380. }
  381. )
  382. },
  383. unsetRootDoc(project_id, callback) {
  384. if (callback == null) {
  385. callback = function(error) {}
  386. }
  387. logger.log({ project_id }, 'removing root doc')
  388. return Project.update(
  389. { _id: project_id },
  390. { $unset: { rootDoc_id: true } },
  391. {},
  392. callback
  393. )
  394. },
  395. _addDocAndSendToTpds(project_id, folder_id, doc, callback) {
  396. if (callback == null) {
  397. callback = function(error, result, project) {}
  398. }
  399. return ProjectEntityMongoUpdateHandler.addDoc(
  400. project_id,
  401. folder_id,
  402. doc,
  403. function(err, result, project) {
  404. if (err != null) {
  405. logger.warn(
  406. {
  407. err,
  408. project_id,
  409. folder_id,
  410. doc_name: doc != null ? doc.name : undefined,
  411. doc_id: doc != null ? doc._id : undefined
  412. },
  413. 'error adding file with project'
  414. )
  415. return callback(err)
  416. }
  417. return TpdsUpdateSender.addDoc(
  418. {
  419. project_id,
  420. doc_id: doc != null ? doc._id : undefined,
  421. path: __guard__(
  422. result != null ? result.path : undefined,
  423. x => x.fileSystem
  424. ),
  425. project_name: project.name,
  426. rev: 0
  427. },
  428. function(err) {
  429. if (err != null) {
  430. return callback(err)
  431. }
  432. return callback(null, result, project)
  433. }
  434. )
  435. }
  436. )
  437. },
  438. addDoc(project_id, folder_id, docName, docLines, userId, callback) {
  439. return self.addDocWithRanges(
  440. project_id,
  441. folder_id,
  442. docName,
  443. docLines,
  444. {},
  445. userId,
  446. callback
  447. )
  448. },
  449. addDocWithRanges: wrapWithLock({
  450. beforeLock(next) {
  451. return function(
  452. project_id,
  453. folder_id,
  454. docName,
  455. docLines,
  456. ranges,
  457. userId,
  458. callback
  459. ) {
  460. if (callback == null) {
  461. callback = function(error, doc, folder_id) {}
  462. }
  463. if (!SafePath.isCleanFilename(docName)) {
  464. return callback(new Errors.InvalidNameError('invalid element name'))
  465. }
  466. // Put doc in docstore first, so that if it errors, we don't have a doc_id in the project
  467. // which hasn't been created in docstore.
  468. const doc = new Doc({ name: docName })
  469. return DocstoreManager.updateDoc(
  470. project_id.toString(),
  471. doc._id.toString(),
  472. docLines,
  473. 0,
  474. ranges,
  475. function(err, modified, rev) {
  476. if (err != null) {
  477. return callback(err)
  478. }
  479. return next(
  480. project_id,
  481. folder_id,
  482. doc,
  483. docName,
  484. docLines,
  485. ranges,
  486. userId,
  487. callback
  488. )
  489. }
  490. )
  491. }
  492. },
  493. withLock(
  494. project_id,
  495. folder_id,
  496. doc,
  497. docName,
  498. docLines,
  499. ranges,
  500. userId,
  501. callback
  502. ) {
  503. if (callback == null) {
  504. callback = function(error, doc, folder_id) {}
  505. }
  506. return ProjectEntityUpdateHandler._addDocAndSendToTpds(
  507. project_id,
  508. folder_id,
  509. doc,
  510. function(err, result, project) {
  511. if (err != null) {
  512. return callback(err)
  513. }
  514. const docPath = __guard__(
  515. result != null ? result.path : undefined,
  516. x => x.fileSystem
  517. )
  518. const projectHistoryId = __guard__(
  519. project.overleaf != null ? project.overleaf.history : undefined,
  520. x1 => x1.id
  521. )
  522. const newDocs = [
  523. {
  524. doc,
  525. path: docPath,
  526. docLines: docLines.join('\n')
  527. }
  528. ]
  529. return DocumentUpdaterHandler.updateProjectStructure(
  530. project_id,
  531. projectHistoryId,
  532. userId,
  533. { newDocs, newProject: project },
  534. function(error) {
  535. if (error != null) {
  536. return callback(error)
  537. }
  538. return callback(null, doc, folder_id)
  539. }
  540. )
  541. }
  542. )
  543. }
  544. }),
  545. _uploadFile(
  546. project_id,
  547. folder_id,
  548. fileName,
  549. fsPath,
  550. linkedFileData,
  551. callback
  552. ) {
  553. if (callback == null) {
  554. callback = function(error, fileStoreUrl, fileRef) {}
  555. }
  556. if (!SafePath.isCleanFilename(fileName)) {
  557. return callback(new Errors.InvalidNameError('invalid element name'))
  558. }
  559. const fileArgs = {
  560. name: fileName,
  561. linkedFileData
  562. }
  563. return FileStoreHandler.uploadFileFromDisk(
  564. project_id,
  565. fileArgs,
  566. fsPath,
  567. function(err, fileStoreUrl, fileRef) {
  568. if (err != null) {
  569. logger.warn(
  570. { err, project_id, folder_id, file_name: fileName, fileRef },
  571. 'error uploading image to s3'
  572. )
  573. return callback(err)
  574. }
  575. return callback(null, fileStoreUrl, fileRef)
  576. }
  577. )
  578. },
  579. _addFileAndSendToTpds(project_id, folder_id, fileRef, callback) {
  580. if (callback == null) {
  581. callback = function(error) {}
  582. }
  583. return ProjectEntityMongoUpdateHandler.addFile(
  584. project_id,
  585. folder_id,
  586. fileRef,
  587. function(err, result, project) {
  588. if (err != null) {
  589. logger.warn(
  590. { err, project_id, folder_id, file_name: fileRef.name, fileRef },
  591. 'error adding file with project'
  592. )
  593. return callback(err)
  594. }
  595. return TpdsUpdateSender.addFile(
  596. {
  597. project_id,
  598. file_id: fileRef._id,
  599. path: __guard__(
  600. result != null ? result.path : undefined,
  601. x => x.fileSystem
  602. ),
  603. project_name: project.name,
  604. rev: fileRef.rev
  605. },
  606. function(err) {
  607. if (err != null) {
  608. return callback(err)
  609. }
  610. return callback(null, result, project)
  611. }
  612. )
  613. }
  614. )
  615. },
  616. addFile: wrapWithLock({
  617. beforeLock(next) {
  618. return function(
  619. project_id,
  620. folder_id,
  621. fileName,
  622. fsPath,
  623. linkedFileData,
  624. userId,
  625. callback
  626. ) {
  627. if (!SafePath.isCleanFilename(fileName)) {
  628. return callback(new Errors.InvalidNameError('invalid element name'))
  629. }
  630. return ProjectEntityUpdateHandler._uploadFile(
  631. project_id,
  632. folder_id,
  633. fileName,
  634. fsPath,
  635. linkedFileData,
  636. function(error, fileStoreUrl, fileRef) {
  637. if (error != null) {
  638. return callback(error)
  639. }
  640. return next(
  641. project_id,
  642. folder_id,
  643. fileName,
  644. fsPath,
  645. linkedFileData,
  646. userId,
  647. fileRef,
  648. fileStoreUrl,
  649. callback
  650. )
  651. }
  652. )
  653. }
  654. },
  655. withLock(
  656. project_id,
  657. folder_id,
  658. fileName,
  659. fsPath,
  660. linkedFileData,
  661. userId,
  662. fileRef,
  663. fileStoreUrl,
  664. callback
  665. ) {
  666. if (callback == null) {
  667. callback = function(error, fileRef, folder_id) {}
  668. }
  669. return ProjectEntityUpdateHandler._addFileAndSendToTpds(
  670. project_id,
  671. folder_id,
  672. fileRef,
  673. function(err, result, project) {
  674. if (err != null) {
  675. return callback(err)
  676. }
  677. const projectHistoryId = __guard__(
  678. project.overleaf != null ? project.overleaf.history : undefined,
  679. x => x.id
  680. )
  681. const newFiles = [
  682. {
  683. file: fileRef,
  684. path: __guard__(
  685. result != null ? result.path : undefined,
  686. x1 => x1.fileSystem
  687. ),
  688. url: fileStoreUrl
  689. }
  690. ]
  691. return DocumentUpdaterHandler.updateProjectStructure(
  692. project_id,
  693. projectHistoryId,
  694. userId,
  695. { newFiles, newProject: project },
  696. function(error) {
  697. if (error != null) {
  698. return callback(error)
  699. }
  700. return callback(null, fileRef, folder_id)
  701. }
  702. )
  703. }
  704. )
  705. }
  706. }),
  707. replaceFile: wrapWithLock({
  708. beforeLock(next) {
  709. return function(
  710. project_id,
  711. file_id,
  712. fsPath,
  713. linkedFileData,
  714. userId,
  715. callback
  716. ) {
  717. // create a new file
  718. const fileArgs = {
  719. name: 'dummy-upload-filename',
  720. linkedFileData
  721. }
  722. return FileStoreHandler.uploadFileFromDisk(
  723. project_id,
  724. fileArgs,
  725. fsPath,
  726. function(err, fileStoreUrl, fileRef) {
  727. if (err != null) {
  728. return callback(err)
  729. }
  730. return next(
  731. project_id,
  732. file_id,
  733. fsPath,
  734. linkedFileData,
  735. userId,
  736. fileRef,
  737. fileStoreUrl,
  738. callback
  739. )
  740. }
  741. )
  742. }
  743. },
  744. withLock(
  745. project_id,
  746. file_id,
  747. fsPath,
  748. linkedFileData,
  749. userId,
  750. newFileRef,
  751. fileStoreUrl,
  752. callback
  753. ) {
  754. return ProjectEntityMongoUpdateHandler.replaceFileWithNew(
  755. project_id,
  756. file_id,
  757. newFileRef,
  758. function(err, oldFileRef, project, path, newProject) {
  759. if (err != null) {
  760. return callback(err)
  761. }
  762. const oldFiles = [
  763. {
  764. file: oldFileRef,
  765. path: path.fileSystem
  766. }
  767. ]
  768. const newFiles = [
  769. {
  770. file: newFileRef,
  771. path: path.fileSystem,
  772. url: fileStoreUrl
  773. }
  774. ]
  775. const projectHistoryId = __guard__(
  776. project.overleaf != null ? project.overleaf.history : undefined,
  777. x => x.id
  778. )
  779. // Increment the rev for an in-place update (with the same path) so the third-party-datastore
  780. // knows this is a new file.
  781. // Ideally we would get this from ProjectEntityMongoUpdateHandler.replaceFileWithNew
  782. // but it returns the original oldFileRef (after incrementing the rev value in mongo),
  783. // so we add 1 to the rev from that. This isn't atomic and relies on the lock
  784. // but it is acceptable for now.
  785. return TpdsUpdateSender.addFile(
  786. {
  787. project_id: project._id,
  788. file_id: newFileRef._id,
  789. path: path.fileSystem,
  790. rev: oldFileRef.rev + 1,
  791. project_name: project.name
  792. },
  793. function(err) {
  794. if (err != null) {
  795. return callback(err)
  796. }
  797. return DocumentUpdaterHandler.updateProjectStructure(
  798. project_id,
  799. projectHistoryId,
  800. userId,
  801. { oldFiles, newFiles, newProject },
  802. callback
  803. )
  804. }
  805. )
  806. }
  807. )
  808. }
  809. }),
  810. upsertDoc: wrapWithLock(function(
  811. project_id,
  812. folder_id,
  813. docName,
  814. docLines,
  815. source,
  816. userId,
  817. callback
  818. ) {
  819. if (callback == null) {
  820. callback = function(err, doc, folder_id, isNewDoc) {}
  821. }
  822. if (!SafePath.isCleanFilename(docName)) {
  823. return callback(new Errors.InvalidNameError('invalid element name'))
  824. }
  825. return ProjectLocator.findElement(
  826. { project_id, element_id: folder_id, type: 'folder' },
  827. function(error, folder) {
  828. if (error != null) {
  829. return callback(error)
  830. }
  831. if (folder == null) {
  832. return callback(new Error("Couldn't find folder"))
  833. }
  834. let existingDoc = null
  835. for (let doc of Array.from(folder.docs)) {
  836. if (doc.name === docName) {
  837. existingDoc = doc
  838. break
  839. }
  840. }
  841. if (existingDoc != null) {
  842. return DocumentUpdaterHandler.setDocument(
  843. project_id,
  844. existingDoc._id,
  845. userId,
  846. docLines,
  847. source,
  848. err => {
  849. logger.log(
  850. { project_id, doc_id: existingDoc._id },
  851. 'notifying users that the document has been updated'
  852. )
  853. return DocumentUpdaterHandler.flushDocToMongo(
  854. project_id,
  855. existingDoc._id,
  856. function(err) {
  857. if (err != null) {
  858. return callback(err)
  859. }
  860. return callback(null, existingDoc, existingDoc == null)
  861. }
  862. )
  863. }
  864. )
  865. } else {
  866. return self.addDocWithRanges.withoutLock(
  867. project_id,
  868. folder_id,
  869. docName,
  870. docLines,
  871. {},
  872. userId,
  873. function(err, doc) {
  874. if (err != null) {
  875. return callback(err)
  876. }
  877. return callback(null, doc, existingDoc == null)
  878. }
  879. )
  880. }
  881. }
  882. )
  883. }),
  884. upsertFile: wrapWithLock({
  885. beforeLock(next) {
  886. return function(
  887. project_id,
  888. folder_id,
  889. fileName,
  890. fsPath,
  891. linkedFileData,
  892. userId,
  893. callback
  894. ) {
  895. if (!SafePath.isCleanFilename(fileName)) {
  896. return callback(new Errors.InvalidNameError('invalid element name'))
  897. }
  898. // create a new file
  899. const fileArgs = {
  900. name: fileName,
  901. linkedFileData
  902. }
  903. return FileStoreHandler.uploadFileFromDisk(
  904. project_id,
  905. fileArgs,
  906. fsPath,
  907. function(err, fileStoreUrl, fileRef) {
  908. if (err != null) {
  909. return callback(err)
  910. }
  911. return next(
  912. project_id,
  913. folder_id,
  914. fileName,
  915. fsPath,
  916. linkedFileData,
  917. userId,
  918. fileRef,
  919. fileStoreUrl,
  920. callback
  921. )
  922. }
  923. )
  924. }
  925. },
  926. withLock(
  927. project_id,
  928. folder_id,
  929. fileName,
  930. fsPath,
  931. linkedFileData,
  932. userId,
  933. newFileRef,
  934. fileStoreUrl,
  935. callback
  936. ) {
  937. if (callback == null) {
  938. callback = function(err, file, isNewFile, existingFile) {}
  939. }
  940. return ProjectLocator.findElement(
  941. { project_id, element_id: folder_id, type: 'folder' },
  942. function(error, folder) {
  943. if (error != null) {
  944. return callback(error)
  945. }
  946. if (folder == null) {
  947. return callback(new Error("Couldn't find folder"))
  948. }
  949. let existingFile = null
  950. for (let fileRef of Array.from(folder.fileRefs)) {
  951. if (fileRef.name === fileName) {
  952. existingFile = fileRef
  953. break
  954. }
  955. }
  956. if (existingFile != null) {
  957. // this calls directly into the replaceFile main task (without the beforeLock part)
  958. return self.replaceFile.mainTask(
  959. project_id,
  960. existingFile._id,
  961. fsPath,
  962. linkedFileData,
  963. userId,
  964. newFileRef,
  965. fileStoreUrl,
  966. function(err) {
  967. if (err != null) {
  968. return callback(err)
  969. }
  970. return callback(
  971. null,
  972. newFileRef,
  973. existingFile == null,
  974. existingFile
  975. )
  976. }
  977. )
  978. } else {
  979. // this calls directly into the addFile main task (without the beforeLock part)
  980. return self.addFile.mainTask(
  981. project_id,
  982. folder_id,
  983. fileName,
  984. fsPath,
  985. linkedFileData,
  986. userId,
  987. newFileRef,
  988. fileStoreUrl,
  989. function(err) {
  990. if (err != null) {
  991. return callback(err)
  992. }
  993. return callback(
  994. null,
  995. newFileRef,
  996. existingFile == null,
  997. existingFile
  998. )
  999. }
  1000. )
  1001. }
  1002. }
  1003. )
  1004. }
  1005. }),
  1006. upsertDocWithPath: wrapWithLock(function(
  1007. project_id,
  1008. elementPath,
  1009. docLines,
  1010. source,
  1011. userId,
  1012. callback
  1013. ) {
  1014. if (!SafePath.isCleanPath(elementPath)) {
  1015. return callback(new Errors.InvalidNameError('invalid element name'))
  1016. }
  1017. const docName = path.basename(elementPath)
  1018. const folderPath = path.dirname(elementPath)
  1019. return self.mkdirp.withoutLock(project_id, folderPath, function(
  1020. err,
  1021. newFolders,
  1022. folder
  1023. ) {
  1024. if (err != null) {
  1025. return callback(err)
  1026. }
  1027. return self.upsertDoc.withoutLock(
  1028. project_id,
  1029. folder._id,
  1030. docName,
  1031. docLines,
  1032. source,
  1033. userId,
  1034. function(err, doc, isNewDoc) {
  1035. if (err != null) {
  1036. return callback(err)
  1037. }
  1038. return callback(null, doc, isNewDoc, newFolders, folder)
  1039. }
  1040. )
  1041. })
  1042. }),
  1043. upsertFileWithPath: wrapWithLock({
  1044. beforeLock(next) {
  1045. return function(
  1046. project_id,
  1047. elementPath,
  1048. fsPath,
  1049. linkedFileData,
  1050. userId,
  1051. callback
  1052. ) {
  1053. if (!SafePath.isCleanPath(elementPath)) {
  1054. return callback(new Errors.InvalidNameError('invalid element name'))
  1055. }
  1056. const fileName = path.basename(elementPath)
  1057. const folderPath = path.dirname(elementPath)
  1058. // create a new file
  1059. const fileArgs = {
  1060. name: fileName,
  1061. linkedFileData
  1062. }
  1063. return FileStoreHandler.uploadFileFromDisk(
  1064. project_id,
  1065. fileArgs,
  1066. fsPath,
  1067. function(err, fileStoreUrl, fileRef) {
  1068. if (err != null) {
  1069. return callback(err)
  1070. }
  1071. return next(
  1072. project_id,
  1073. folderPath,
  1074. fileName,
  1075. fsPath,
  1076. linkedFileData,
  1077. userId,
  1078. fileRef,
  1079. fileStoreUrl,
  1080. callback
  1081. )
  1082. }
  1083. )
  1084. }
  1085. },
  1086. withLock(
  1087. project_id,
  1088. folderPath,
  1089. fileName,
  1090. fsPath,
  1091. linkedFileData,
  1092. userId,
  1093. fileRef,
  1094. fileStoreUrl,
  1095. callback
  1096. ) {
  1097. return self.mkdirp.withoutLock(project_id, folderPath, function(
  1098. err,
  1099. newFolders,
  1100. folder
  1101. ) {
  1102. if (err != null) {
  1103. return callback(err)
  1104. }
  1105. // this calls directly into the upsertFile main task (without the beforeLock part)
  1106. return self.upsertFile.mainTask(
  1107. project_id,
  1108. folder._id,
  1109. fileName,
  1110. fsPath,
  1111. linkedFileData,
  1112. userId,
  1113. fileRef,
  1114. fileStoreUrl,
  1115. function(err, newFile, isNewFile, existingFile) {
  1116. if (err != null) {
  1117. return callback(err)
  1118. }
  1119. return callback(
  1120. null,
  1121. newFile,
  1122. isNewFile,
  1123. existingFile,
  1124. newFolders,
  1125. folder
  1126. )
  1127. }
  1128. )
  1129. })
  1130. }
  1131. }),
  1132. deleteEntity: wrapWithLock(function(
  1133. project_id,
  1134. entity_id,
  1135. entityType,
  1136. userId,
  1137. callback
  1138. ) {
  1139. if (callback == null) {
  1140. callback = function(error) {}
  1141. }
  1142. logger.log({ entity_id, entityType, project_id }, 'deleting project entity')
  1143. if (entityType == null) {
  1144. logger.warn({ err: 'No entityType set', project_id, entity_id })
  1145. return callback(new Error('No entityType set'))
  1146. }
  1147. entityType = entityType.toLowerCase()
  1148. return ProjectEntityMongoUpdateHandler.deleteEntity(
  1149. project_id,
  1150. entity_id,
  1151. entityType,
  1152. function(error, entity, path, projectBeforeDeletion, newProject) {
  1153. if (error != null) {
  1154. return callback(error)
  1155. }
  1156. return self._cleanUpEntity(
  1157. projectBeforeDeletion,
  1158. newProject,
  1159. entity,
  1160. entityType,
  1161. path.fileSystem,
  1162. userId,
  1163. function(error) {
  1164. if (error != null) {
  1165. return callback(error)
  1166. }
  1167. return TpdsUpdateSender.deleteEntity(
  1168. {
  1169. project_id,
  1170. path: path.fileSystem,
  1171. project_name: projectBeforeDeletion.name
  1172. },
  1173. function(error) {
  1174. if (error != null) {
  1175. return callback(error)
  1176. }
  1177. return callback(null, entity_id)
  1178. }
  1179. )
  1180. }
  1181. )
  1182. }
  1183. )
  1184. }),
  1185. deleteEntityWithPath: wrapWithLock((project_id, path, userId, callback) =>
  1186. ProjectLocator.findElementByPath({ project_id, path }, function(
  1187. err,
  1188. element,
  1189. type
  1190. ) {
  1191. if (err != null) {
  1192. return callback(err)
  1193. }
  1194. if (element == null) {
  1195. return callback(new Errors.NotFoundError('project not found'))
  1196. }
  1197. return self.deleteEntity.withoutLock(
  1198. project_id,
  1199. element._id,
  1200. type,
  1201. userId,
  1202. callback
  1203. )
  1204. })
  1205. ),
  1206. mkdirp: wrapWithLock(function(project_id, path, callback) {
  1207. if (callback == null) {
  1208. callback = function(err, newlyCreatedFolders, lastFolderInPath) {}
  1209. }
  1210. for (let folder of Array.from(path.split('/'))) {
  1211. if (folder.length > 0 && !SafePath.isCleanFilename(folder)) {
  1212. return callback(new Errors.InvalidNameError('invalid element name'))
  1213. }
  1214. }
  1215. return ProjectEntityMongoUpdateHandler.mkdirp(
  1216. project_id,
  1217. path,
  1218. { exactCaseMatch: false },
  1219. callback
  1220. )
  1221. }),
  1222. mkdirpWithExactCase: wrapWithLock(function(project_id, path, callback) {
  1223. if (callback == null) {
  1224. callback = function(err, newlyCreatedFolders, lastFolderInPath) {}
  1225. }
  1226. for (let folder of Array.from(path.split('/'))) {
  1227. if (folder.length > 0 && !SafePath.isCleanFilename(folder)) {
  1228. return callback(new Errors.InvalidNameError('invalid element name'))
  1229. }
  1230. }
  1231. return ProjectEntityMongoUpdateHandler.mkdirp(
  1232. project_id,
  1233. path,
  1234. { exactCaseMatch: true },
  1235. callback
  1236. )
  1237. }),
  1238. addFolder: wrapWithLock(function(
  1239. project_id,
  1240. parentFolder_id,
  1241. folderName,
  1242. callback
  1243. ) {
  1244. if (!SafePath.isCleanFilename(folderName)) {
  1245. return callback(new Errors.InvalidNameError('invalid element name'))
  1246. }
  1247. return ProjectEntityMongoUpdateHandler.addFolder(
  1248. project_id,
  1249. parentFolder_id,
  1250. folderName,
  1251. callback
  1252. )
  1253. }),
  1254. moveEntity: wrapWithLock(function(
  1255. project_id,
  1256. entity_id,
  1257. destFolderId,
  1258. entityType,
  1259. userId,
  1260. callback
  1261. ) {
  1262. if (callback == null) {
  1263. callback = function(error) {}
  1264. }
  1265. logger.log(
  1266. { entityType, entity_id, project_id, destFolderId },
  1267. 'moving entity'
  1268. )
  1269. if (entityType == null) {
  1270. logger.warn({ err: 'No entityType set', project_id, entity_id })
  1271. return callback(new Error('No entityType set'))
  1272. }
  1273. entityType = entityType.toLowerCase()
  1274. return ProjectEntityMongoUpdateHandler.moveEntity(
  1275. project_id,
  1276. entity_id,
  1277. destFolderId,
  1278. entityType,
  1279. function(err, project, startPath, endPath, rev, changes) {
  1280. if (err != null) {
  1281. return callback(err)
  1282. }
  1283. const projectHistoryId = __guard__(
  1284. project.overleaf != null ? project.overleaf.history : undefined,
  1285. x => x.id
  1286. )
  1287. TpdsUpdateSender.moveEntity({
  1288. project_id,
  1289. project_name: project.name,
  1290. startPath,
  1291. endPath,
  1292. rev
  1293. })
  1294. return DocumentUpdaterHandler.updateProjectStructure(
  1295. project_id,
  1296. projectHistoryId,
  1297. userId,
  1298. changes,
  1299. callback
  1300. )
  1301. }
  1302. )
  1303. }),
  1304. renameEntity: wrapWithLock(function(
  1305. project_id,
  1306. entity_id,
  1307. entityType,
  1308. newName,
  1309. userId,
  1310. callback
  1311. ) {
  1312. if (!SafePath.isCleanFilename(newName)) {
  1313. return callback(new Errors.InvalidNameError('invalid element name'))
  1314. }
  1315. logger.log({ entity_id, project_id }, `renaming ${entityType}`)
  1316. if (entityType == null) {
  1317. logger.warn({ err: 'No entityType set', project_id, entity_id })
  1318. return callback(new Error('No entityType set'))
  1319. }
  1320. entityType = entityType.toLowerCase()
  1321. return ProjectEntityMongoUpdateHandler.renameEntity(
  1322. project_id,
  1323. entity_id,
  1324. entityType,
  1325. newName,
  1326. function(err, project, startPath, endPath, rev, changes) {
  1327. if (err != null) {
  1328. return callback(err)
  1329. }
  1330. const projectHistoryId = __guard__(
  1331. project.overleaf != null ? project.overleaf.history : undefined,
  1332. x => x.id
  1333. )
  1334. TpdsUpdateSender.moveEntity({
  1335. project_id,
  1336. project_name: project.name,
  1337. startPath,
  1338. endPath,
  1339. rev
  1340. })
  1341. return DocumentUpdaterHandler.updateProjectStructure(
  1342. project_id,
  1343. projectHistoryId,
  1344. userId,
  1345. changes,
  1346. callback
  1347. )
  1348. }
  1349. )
  1350. }),
  1351. // This doesn't directly update project structure but we need to take the lock
  1352. // to prevent anything else being queued before the resync update
  1353. resyncProjectHistory: wrapWithLock((project_id, callback) =>
  1354. ProjectGetter.getProject(
  1355. project_id,
  1356. { rootFolder: true, overleaf: true },
  1357. function(error, project) {
  1358. if (error != null) {
  1359. return callback(error)
  1360. }
  1361. const projectHistoryId = __guard__(
  1362. __guard__(
  1363. project != null ? project.overleaf : undefined,
  1364. x1 => x1.history
  1365. ),
  1366. x => x.id
  1367. )
  1368. if (projectHistoryId == null) {
  1369. error = new Errors.ProjectHistoryDisabledError(
  1370. `project history not enabled for ${project_id}`
  1371. )
  1372. return callback(error)
  1373. }
  1374. return ProjectEntityHandler.getAllEntitiesFromProject(project, function(
  1375. error,
  1376. docs,
  1377. files
  1378. ) {
  1379. if (error != null) {
  1380. return callback(error)
  1381. }
  1382. docs = _.map(docs, doc => ({
  1383. doc: doc.doc._id,
  1384. path: doc.path
  1385. }))
  1386. files = _.map(files, file => ({
  1387. file: file.file._id,
  1388. path: file.path,
  1389. url: FileStoreHandler._buildUrl(project_id, file.file._id)
  1390. }))
  1391. return DocumentUpdaterHandler.resyncProjectHistory(
  1392. project_id,
  1393. projectHistoryId,
  1394. docs,
  1395. files,
  1396. callback
  1397. )
  1398. })
  1399. }
  1400. )
  1401. ),
  1402. isPathValidForRootDoc(docPath) {
  1403. let docExtension = path.extname(docPath)
  1404. return validRootDocRegExp.test(docExtension)
  1405. },
  1406. _cleanUpEntity(
  1407. project,
  1408. newProject,
  1409. entity,
  1410. entityType,
  1411. path,
  1412. userId,
  1413. callback
  1414. ) {
  1415. if (callback == null) {
  1416. callback = function(error) {}
  1417. }
  1418. return self._updateProjectStructureWithDeletedEntity(
  1419. project,
  1420. newProject,
  1421. entity,
  1422. entityType,
  1423. path,
  1424. userId,
  1425. function(error) {
  1426. if (error != null) {
  1427. return callback(error)
  1428. }
  1429. if (entityType.indexOf('file') !== -1) {
  1430. return self._cleanUpFile(project, entity, path, userId, callback)
  1431. } else if (entityType.indexOf('doc') !== -1) {
  1432. return self._cleanUpDoc(project, entity, path, userId, callback)
  1433. } else if (entityType.indexOf('folder') !== -1) {
  1434. return self._cleanUpFolder(project, entity, path, userId, callback)
  1435. } else {
  1436. return callback()
  1437. }
  1438. }
  1439. )
  1440. },
  1441. // Note: the _cleanUpEntity code and _updateProjectStructureWithDeletedEntity
  1442. // methods both need to recursively iterate over the entities in folder.
  1443. // These are currently using separate implementations of the recursion. In
  1444. // future, these could be simplified using a common project entity iterator.
  1445. _updateProjectStructureWithDeletedEntity(
  1446. project,
  1447. newProject,
  1448. entity,
  1449. entityType,
  1450. entityPath,
  1451. userId,
  1452. callback
  1453. ) {
  1454. // compute the changes to the project structure
  1455. let changes
  1456. if (callback == null) {
  1457. callback = function(error) {}
  1458. }
  1459. if (entityType.indexOf('file') !== -1) {
  1460. changes = { oldFiles: [{ file: entity, path: entityPath }] }
  1461. } else if (entityType.indexOf('doc') !== -1) {
  1462. changes = { oldDocs: [{ doc: entity, path: entityPath }] }
  1463. } else if (entityType.indexOf('folder') !== -1) {
  1464. changes = { oldDocs: [], oldFiles: [] }
  1465. var _recurseFolder = function(folder, folderPath) {
  1466. for (let doc of Array.from(folder.docs)) {
  1467. changes.oldDocs.push({ doc, path: path.join(folderPath, doc.name) })
  1468. }
  1469. for (let file of Array.from(folder.fileRefs)) {
  1470. changes.oldFiles.push({
  1471. file,
  1472. path: path.join(folderPath, file.name)
  1473. })
  1474. }
  1475. return Array.from(folder.folders).map(childFolder =>
  1476. _recurseFolder(childFolder, path.join(folderPath, childFolder.name))
  1477. )
  1478. }
  1479. _recurseFolder(entity, entityPath)
  1480. }
  1481. // now send the project structure changes to the docupdater
  1482. changes.newProject = newProject
  1483. const project_id = project._id.toString()
  1484. const projectHistoryId = __guard__(
  1485. project.overleaf != null ? project.overleaf.history : undefined,
  1486. x => x.id
  1487. )
  1488. return DocumentUpdaterHandler.updateProjectStructure(
  1489. project_id,
  1490. projectHistoryId,
  1491. userId,
  1492. changes,
  1493. callback
  1494. )
  1495. },
  1496. _cleanUpDoc(project, doc, path, userId, callback) {
  1497. if (callback == null) {
  1498. callback = function(error) {}
  1499. }
  1500. const project_id = project._id.toString()
  1501. const doc_id = doc._id.toString()
  1502. const unsetRootDocIfRequired = callback => {
  1503. if (
  1504. project.rootDoc_id != null &&
  1505. project.rootDoc_id.toString() === doc_id
  1506. ) {
  1507. return this.unsetRootDoc(project_id, callback)
  1508. } else {
  1509. return callback()
  1510. }
  1511. }
  1512. return unsetRootDocIfRequired(function(error) {
  1513. if (error != null) {
  1514. return callback(error)
  1515. }
  1516. return ProjectEntityMongoUpdateHandler._insertDeletedDocReference(
  1517. project._id,
  1518. doc,
  1519. function(error) {
  1520. if (error != null) {
  1521. return callback(error)
  1522. }
  1523. return DocumentUpdaterHandler.deleteDoc(project_id, doc_id, function(
  1524. error
  1525. ) {
  1526. if (error != null) {
  1527. return callback(error)
  1528. }
  1529. return DocstoreManager.deleteDoc(project_id, doc_id, callback)
  1530. })
  1531. }
  1532. )
  1533. })
  1534. },
  1535. _cleanUpFile(project, file, path, userId, callback) {
  1536. if (callback == null) {
  1537. callback = function(error) {}
  1538. }
  1539. return ProjectEntityMongoUpdateHandler._insertDeletedFileReference(
  1540. project._id,
  1541. file,
  1542. callback
  1543. )
  1544. },
  1545. _cleanUpFolder(project, folder, folderPath, userId, callback) {
  1546. if (callback == null) {
  1547. callback = function(error) {}
  1548. }
  1549. const jobs = []
  1550. for (let doc of Array.from(folder.docs)) {
  1551. ;(function(doc) {
  1552. const docPath = path.join(folderPath, doc.name)
  1553. return jobs.push(callback =>
  1554. self._cleanUpDoc(project, doc, docPath, userId, callback)
  1555. )
  1556. })(doc)
  1557. }
  1558. for (let file of Array.from(folder.fileRefs)) {
  1559. ;(function(file) {
  1560. const filePath = path.join(folderPath, file.name)
  1561. return jobs.push(callback =>
  1562. self._cleanUpFile(project, file, filePath, userId, callback)
  1563. )
  1564. })(file)
  1565. }
  1566. for (let childFolder of Array.from(folder.folders)) {
  1567. ;(function(childFolder) {
  1568. folderPath = path.join(folderPath, childFolder.name)
  1569. return jobs.push(callback =>
  1570. self._cleanUpFolder(
  1571. project,
  1572. childFolder,
  1573. folderPath,
  1574. userId,
  1575. callback
  1576. )
  1577. )
  1578. })(childFolder)
  1579. }
  1580. return async.series(jobs, callback)
  1581. }
  1582. }
  1583. function __guard__(value, transform) {
  1584. return typeof value !== 'undefined' && value !== null
  1585. ? transform(value)
  1586. : undefined
  1587. }