ProjectEntityUpdateHandler.coffee 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464
  1. _ = require 'lodash'
  2. async = require 'async'
  3. logger = require('logger-sharelatex')
  4. path = require('path')
  5. Doc = require('../../models/Doc').Doc
  6. DocstoreManager = require('../Docstore/DocstoreManager')
  7. DocumentUpdaterHandler = require('../../Features/DocumentUpdater/DocumentUpdaterHandler')
  8. Errors = require '../Errors/Errors'
  9. File = require('../../models/File').File
  10. FileStoreHandler = require('../FileStore/FileStoreHandler')
  11. LockManager = require('../../infrastructure/LockManager')
  12. Project = require('../../models/Project').Project
  13. ProjectEntityHandler = require('./ProjectEntityHandler')
  14. ProjectGetter = require('./ProjectGetter')
  15. ProjectLocator = require('./ProjectLocator')
  16. ProjectUpdateHandler = require('./ProjectUpdateHandler')
  17. ProjectEntityMongoUpdateHandler = require('./ProjectEntityMongoUpdateHandler')
  18. SafePath = require './SafePath'
  19. TpdsUpdateSender = require('../ThirdPartyDataStore/TpdsUpdateSender')
  20. LOCK_NAMESPACE = "sequentialProjectStructureUpdateLock"
  21. wrapWithLock = (methodWithoutLock) ->
  22. # This lock is used to make sure that the project structure updates are made
  23. # sequentially. In particular the updates must be made in mongo and sent to
  24. # the doc-updater in the same order.
  25. if typeof methodWithoutLock is 'function'
  26. methodWithLock = (project_id, args..., callback) ->
  27. LockManager.runWithLock LOCK_NAMESPACE, project_id,
  28. (cb) -> methodWithoutLock project_id, args..., cb
  29. callback
  30. methodWithLock.withoutLock = methodWithoutLock
  31. methodWithLock
  32. else
  33. # handle case with separate setup and locked stages
  34. wrapWithSetup = methodWithoutLock.beforeLock # a function to set things up before the lock
  35. mainTask = methodWithoutLock.withLock # function to execute inside the lock
  36. methodWithLock = wrapWithSetup (project_id, args..., callback) ->
  37. LockManager.runWithLock(LOCK_NAMESPACE, project_id, (cb) ->
  38. mainTask(project_id, args..., cb)
  39. callback)
  40. methodWithLock.withoutLock = wrapWithSetup mainTask
  41. methodWithLock.beforeLock = methodWithoutLock.beforeLock
  42. methodWithLock.mainTask = methodWithoutLock.withLock
  43. methodWithLock
  44. module.exports = ProjectEntityUpdateHandler = self =
  45. # this doesn't need any locking because it's only called by ProjectDuplicator
  46. copyFileFromExistingProjectWithProject: (project, folder_id, originalProject_id, origonalFileRef, userId, callback = (error, fileRef, folder_id) ->)->
  47. project_id = project._id
  48. logger.log { project_id, folder_id, originalProject_id, origonalFileRef }, "copying file in s3 with project"
  49. return callback(err) if err?
  50. ProjectEntityMongoUpdateHandler._confirmFolder project, folder_id, (folder_id)=>
  51. if !origonalFileRef?
  52. logger.err { project_id, folder_id, originalProject_id, origonalFileRef }, "file trying to copy is null"
  53. return callback()
  54. # convert any invalid characters in original file to '_'
  55. fileRef = new File name : SafePath.clean(origonalFileRef.name)
  56. FileStoreHandler.copyFile originalProject_id, origonalFileRef._id, project._id, fileRef._id, (err, fileStoreUrl)->
  57. if err?
  58. logger.err { err, project_id, folder_id, originalProject_id, origonalFileRef }, "error coping file in s3"
  59. return callback(err)
  60. ProjectEntityMongoUpdateHandler._putElement project, folder_id, fileRef, "file", (err, result)=>
  61. if err?
  62. logger.err { err, project_id, folder_id }, "error putting element as part of copy"
  63. return callback(err)
  64. TpdsUpdateSender.addFile { project_id, file_id:fileRef._id, path:result?.path?.fileSystem, rev:fileRef.rev, project_name:project.name}, (err) ->
  65. if err?
  66. logger.err { err, project_id, folder_id, originalProject_id, origonalFileRef }, "error sending file to tpds worker"
  67. newFiles = [
  68. file: fileRef
  69. path: result?.path?.fileSystem
  70. url: fileStoreUrl
  71. ]
  72. DocumentUpdaterHandler.updateProjectStructure project_id, userId, {newFiles}, (error) ->
  73. return callback(error) if error?
  74. callback null, fileRef, folder_id
  75. updateDocLines: (project_id, doc_id, lines, version, ranges, callback = (error) ->)->
  76. ProjectGetter.getProjectWithoutDocLines project_id, (err, project)->
  77. return callback(err) if err?
  78. return callback(new Errors.NotFoundError("project not found")) if !project?
  79. logger.log project_id: project_id, doc_id: doc_id, "updating doc lines"
  80. ProjectLocator.findElement {project:project, element_id:doc_id, type:"docs"}, (err, doc, path)->
  81. isDeletedDoc = false
  82. if err?
  83. if err instanceof Errors.NotFoundError
  84. # We need to be able to update the doclines of deleted docs. This is
  85. # so the doc-updater can flush a doc's content to the doc-store after
  86. # the doc is deleted.
  87. isDeletedDoc = true
  88. doc = _.find project.deletedDocs, (doc) ->
  89. doc._id.toString() == doc_id.toString()
  90. else
  91. return callback(err)
  92. if !doc?
  93. # Do not allow an update to a doc which has never exist on this project
  94. logger.error {doc_id, project_id, lines}, "doc not found while updating doc lines"
  95. return callback(new Errors.NotFoundError('doc not found'))
  96. logger.log {project_id, doc_id}, "telling docstore manager to update doc"
  97. DocstoreManager.updateDoc project_id, doc_id, lines, version, ranges, (err, modified, rev) ->
  98. if err?
  99. logger.error {err, doc_id, project_id, lines}, "error sending doc to docstore"
  100. return callback(err)
  101. logger.log {project_id, doc_id, modified}, "finished updating doc lines"
  102. # path will only be present if the doc is not deleted
  103. if modified && !isDeletedDoc
  104. # Don't need to block for marking as updated
  105. ProjectUpdateHandler.markAsUpdated project_id
  106. TpdsUpdateSender.addDoc {project_id:project_id, path:path.fileSystem, doc_id:doc_id, project_name:project.name, rev:rev}, callback
  107. else
  108. callback()
  109. setRootDoc: (project_id, newRootDocID, callback = (error) ->)->
  110. logger.log project_id: project_id, rootDocId: newRootDocID, "setting root doc"
  111. Project.update {_id:project_id}, {rootDoc_id:newRootDocID}, {}, callback
  112. unsetRootDoc: (project_id, callback = (error) ->) ->
  113. logger.log project_id: project_id, "removing root doc"
  114. Project.update {_id:project_id}, {$unset: {rootDoc_id: true}}, {}, callback
  115. restoreDoc: (project_id, doc_id, name, callback = (error, doc, folder_id) ->) ->
  116. if not SafePath.isCleanFilename name
  117. return callback new Errors.InvalidNameError("invalid element name")
  118. # getDoc will return the deleted doc's lines, but we don't actually remove
  119. # the deleted doc, just create a new one from its lines.
  120. ProjectEntityHandler.getDoc project_id, doc_id, include_deleted: true, (error, lines) ->
  121. return callback(error) if error?
  122. self.addDoc project_id, null, name, lines, callback
  123. addDoc: wrapWithLock (project_id, folder_id, docName, docLines, userId, callback = (error, doc, folder_id) ->)=>
  124. self.addDocWithoutUpdatingHistory.withoutLock project_id, folder_id, docName, docLines, userId, (error, doc, folder_id, path) ->
  125. return callback(error) if error?
  126. newDocs = [
  127. doc: doc
  128. path: path
  129. docLines: docLines.join('\n')
  130. ]
  131. DocumentUpdaterHandler.updateProjectStructure project_id, userId, {newDocs}, (error) ->
  132. return callback(error) if error?
  133. callback null, doc, folder_id
  134. _uploadFile: (project_id, folder_id, fileName, fsPath, linkedFileData, userId, callback = (error, fileRef, fileStoreUrl) ->)->
  135. if not SafePath.isCleanFilename fileName
  136. return callback new Errors.InvalidNameError("invalid element name")
  137. fileRef = new File(
  138. name: fileName
  139. linkedFileData: linkedFileData
  140. )
  141. FileStoreHandler.uploadFileFromDisk project_id, fileRef._id, fsPath, (err, fileStoreUrl)->
  142. if err?
  143. logger.err err:err, project_id: project_id, folder_id: folder_id, file_name: fileName, fileRef:fileRef, "error uploading image to s3"
  144. return callback(err)
  145. callback(null, fileRef, fileStoreUrl)
  146. _addFileAndSendToTpds: (project_id, folder_id, fileName, fileRef, callback = (error) ->)->
  147. ProjectEntityMongoUpdateHandler.addFile project_id, folder_id, fileRef, (err, result, project) ->
  148. if err?
  149. logger.err err:err, project_id: project_id, folder_id: folder_id, file_name: fileName, fileRef:fileRef, "error adding file with project"
  150. return callback(err)
  151. TpdsUpdateSender.addFile {project_id:project_id, file_id:fileRef._id, path:result?.path?.fileSystem, project_name:project.name, rev:fileRef.rev}, (err) ->
  152. return callback(err) if err?
  153. callback(null, result, project)
  154. addFile: wrapWithLock
  155. beforeLock: (next) ->
  156. (project_id, folder_id, fileName, fsPath, linkedFileData, userId, callback) ->
  157. ProjectEntityUpdateHandler._uploadFile project_id, folder_id, fileName, fsPath, linkedFileData, userId, (error, fileRef, fileStoreUrl) ->
  158. return callback(error) if error?
  159. next(project_id, folder_id, fileName, fsPath, linkedFileData, userId, fileRef, fileStoreUrl, callback)
  160. withLock: (project_id, folder_id, fileName, fsPath, linkedFileData, userId, fileRef, fileStoreUrl, callback = (error, fileRef, folder_id) ->)->
  161. ProjectEntityUpdateHandler._addFileAndSendToTpds project_id, folder_id, fileName, fileRef, (err, result, project) ->
  162. return callback(err) if err?
  163. newFiles = [
  164. file: fileRef
  165. path: result?.path?.fileSystem
  166. url: fileStoreUrl
  167. ]
  168. DocumentUpdaterHandler.updateProjectStructure project_id, userId, {newFiles}, (error) ->
  169. return callback(error) if error?
  170. callback(null, fileRef, folder_id)
  171. replaceFile: wrapWithLock
  172. beforeLock: (next) ->
  173. (project_id, file_id, fsPath, linkedFileData, userId, callback)->
  174. # create a new file
  175. fileRef = new File(
  176. name: "dummy-upload-filename"
  177. linkedFileData: linkedFileData
  178. )
  179. FileStoreHandler.uploadFileFromDisk project_id, fileRef._id, fsPath, (err, fileStoreUrl)->
  180. return callback(err) if err?
  181. next project_id, file_id, fsPath, linkedFileData, userId, fileRef, fileStoreUrl, callback
  182. withLock: (project_id, file_id, fsPath, linkedFileData, userId, newFileRef, fileStoreUrl, callback)->
  183. ProjectEntityMongoUpdateHandler.replaceFileWithNew project_id, file_id, newFileRef, (err, oldFileRef, project, path) ->
  184. return callback(err) if err?
  185. oldFiles = [
  186. file: oldFileRef
  187. path: path.fileSystem
  188. ]
  189. newFiles = [
  190. file: newFileRef
  191. path: path.fileSystem
  192. url: fileStoreUrl
  193. ]
  194. TpdsUpdateSender.addFile {project_id:project._id, file_id:newFileRef._id, path:path.fileSystem, rev:newFileRef.rev+1, project_name:project.name}, (err) ->
  195. return callback(err) if err?
  196. DocumentUpdaterHandler.updateProjectStructure project_id, userId, {oldFiles, newFiles}, callback
  197. addDocWithoutUpdatingHistory: wrapWithLock (project_id, folder_id, docName, docLines, userId, callback = (error, doc, folder_id) ->)=>
  198. # This method should never be called directly, except when importing a project
  199. # from Overleaf. It skips sending updates to the project history, which will break
  200. # the history unless you are making sure it is updated in some other way.
  201. if not SafePath.isCleanFilename docName
  202. return callback new Errors.InvalidNameError("invalid element name")
  203. # Put doc in docstore first, so that if it errors, we don't have a doc_id in the project
  204. # which hasn't been created in docstore.
  205. doc = new Doc name: docName
  206. DocstoreManager.updateDoc project_id.toString(), doc._id.toString(), docLines, 0, {}, (err, modified, rev) ->
  207. return callback(err) if err?
  208. ProjectEntityMongoUpdateHandler.addDoc project_id, folder_id, doc, (err, result, project) ->
  209. return callback(err) if err?
  210. TpdsUpdateSender.addDoc {
  211. project_id: project_id,
  212. doc_id: doc?._id
  213. path: result?.path?.fileSystem,
  214. project_name: project.name,
  215. rev: 0
  216. }, (err) ->
  217. return callback(err) if err?
  218. callback(null, doc, folder_id, result?.path?.fileSystem)
  219. addFileWithoutUpdatingHistory: wrapWithLock
  220. # This method should never be called directly, except when importing a project
  221. # from Overleaf. It skips sending updates to the project history, which will break
  222. # the history unless you are making sure it is updated in some other way.
  223. beforeLock: (next) ->
  224. (project_id, folder_id, fileName, fsPath, linkedFileData, userId, callback) ->
  225. ProjectEntityUpdateHandler._uploadFile project_id, folder_id, fileName, fsPath, linkedFileData, userId, (error, fileRef, fileStoreUrl) ->
  226. return callback(error) if error?
  227. next(project_id, folder_id, fileName, fsPath, linkedFileData, userId, fileRef, fileStoreUrl, callback)
  228. withLock: (project_id, folder_id, fileName, fsPath, linkedFileData, userId, fileRef, fileStoreUrl, callback = (error, fileRef, folder_id, path, fileStoreUrl) ->)->
  229. ProjectEntityUpdateHandler._addFileAndSendToTpds project_id, folder_id, fileName, fileRef, (err, result, project) ->
  230. return callback(err) if err?
  231. callback(null, fileRef, folder_id, result?.path?.fileSystem, fileStoreUrl)
  232. upsertDoc: wrapWithLock (project_id, folder_id, docName, docLines, source, userId, callback = (err, doc, folder_id, isNewDoc)->)->
  233. ProjectLocator.findElement project_id: project_id, element_id: folder_id, type: "folder", (error, folder) ->
  234. return callback(error) if error?
  235. return callback(new Error("Couldn't find folder")) if !folder?
  236. existingDoc = null
  237. for doc in folder.docs
  238. if doc.name == docName
  239. existingDoc = doc
  240. break
  241. if existingDoc?
  242. DocumentUpdaterHandler.setDocument project_id, existingDoc._id, userId, docLines, source, (err)=>
  243. logger.log project_id:project_id, doc_id:existingDoc._id, "notifying users that the document has been updated"
  244. DocumentUpdaterHandler.flushDocToMongo project_id, existingDoc._id, (err) ->
  245. return callback(err) if err?
  246. callback null, existingDoc, !existingDoc?
  247. else
  248. self.addDoc.withoutLock project_id, folder_id, docName, docLines, userId, (err, doc) ->
  249. return callback(err) if err?
  250. callback null, doc, !existingDoc?
  251. upsertFile: wrapWithLock
  252. beforeLock: (next) ->
  253. (project_id, folder_id, fileName, fsPath, linkedFileData, userId, callback)->
  254. # create a new file
  255. fileRef = new File(
  256. name: fileName
  257. linkedFileData: linkedFileData
  258. )
  259. FileStoreHandler.uploadFileFromDisk project_id, fileRef._id, fsPath, (err, fileStoreUrl)->
  260. return callback(err) if err?
  261. next(project_id, folder_id, fileName, fsPath, linkedFileData, userId, fileRef, fileStoreUrl, callback)
  262. withLock: (project_id, folder_id, fileName, fsPath, linkedFileData, userId, newFileRef, fileStoreUrl, callback = (err, file, isNewFile, existingFile)->)->
  263. ProjectLocator.findElement project_id: project_id, element_id: folder_id, type: "folder", (error, folder) ->
  264. return callback(error) if error?
  265. return callback(new Error("Couldn't find folder")) if !folder?
  266. existingFile = null
  267. for fileRef in folder.fileRefs
  268. if fileRef.name == fileName
  269. existingFile = fileRef
  270. break
  271. if existingFile?
  272. # this calls directly into the replaceFile main task (without the beforeLock part)
  273. self.replaceFile.mainTask project_id, existingFile._id, fsPath, linkedFileData, userId, newFileRef, fileStoreUrl, (err) ->
  274. return callback(err) if err?
  275. callback null, newFileRef, !existingFile?, existingFile
  276. else
  277. # this calls directly into the addFile main task (without the beforeLock part)
  278. self.addFile.mainTask project_id, folder_id, fileName, fsPath, linkedFileData, userId, newFileRef, fileStoreUrl, (err) ->
  279. return callback(err) if err?
  280. callback null, newFileRef, !existingFile?, existingFile
  281. upsertDocWithPath: wrapWithLock (project_id, elementPath, docLines, source, userId, callback) ->
  282. docName = path.basename(elementPath)
  283. folderPath = path.dirname(elementPath)
  284. self.mkdirp.withoutLock project_id, folderPath, (err, newFolders, folder) ->
  285. return callback(err) if err?
  286. self.upsertDoc.withoutLock project_id, folder._id, docName, docLines, source, userId, (err, doc, isNewDoc) ->
  287. return callback(err) if err?
  288. callback null, doc, isNewDoc, newFolders, folder
  289. upsertFileWithPath: wrapWithLock
  290. beforeLock: (next) ->
  291. (project_id, elementPath, fsPath, linkedFileData, userId, callback)->
  292. fileName = path.basename(elementPath)
  293. folderPath = path.dirname(elementPath)
  294. # create a new file
  295. fileRef = new File(
  296. name: fileName
  297. linkedFileData: linkedFileData
  298. )
  299. FileStoreHandler.uploadFileFromDisk project_id, fileRef._id, fsPath, (err, fileStoreUrl)->
  300. return callback(err) if err?
  301. next project_id, folderPath, fileName, fsPath, linkedFileData, userId, fileRef, fileStoreUrl, callback
  302. withLock: (project_id, folderPath, fileName, fsPath, linkedFileData, userId, fileRef, fileStoreUrl, callback) ->
  303. self.mkdirp.withoutLock project_id, folderPath, (err, newFolders, folder) ->
  304. return callback(err) if err?
  305. # this calls directly into the upsertFile main task (without the beforeLock part)
  306. self.upsertFile.mainTask project_id, folder._id, fileName, fsPath, linkedFileData, userId, fileRef, fileStoreUrl, (err, newFile, isNewFile, existingFile) ->
  307. return callback(err) if err?
  308. callback null, newFile, isNewFile, existingFile, newFolders, folder
  309. deleteEntity: wrapWithLock (project_id, entity_id, entityType, userId, callback = (error) ->)->
  310. logger.log entity_id:entity_id, entityType:entityType, project_id:project_id, "deleting project entity"
  311. if !entityType?
  312. logger.err err: "No entityType set", project_id: project_id, entity_id: entity_id
  313. return callback("No entityType set")
  314. entityType = entityType.toLowerCase()
  315. ProjectEntityMongoUpdateHandler.deleteEntity project_id, entity_id, entityType, (error, entity, path, projectBeforeDeletion) ->
  316. return callback(error) if error?
  317. self._cleanUpEntity projectBeforeDeletion, entity, entityType, path.fileSystem, userId, (error) ->
  318. return callback(error) if error?
  319. TpdsUpdateSender.deleteEntity project_id:project_id, path:path.fileSystem, project_name:projectBeforeDeletion.name, (error) ->
  320. return callback(error) if error?
  321. callback null, entity_id
  322. deleteEntityWithPath: wrapWithLock (project_id, path, userId, callback) ->
  323. ProjectLocator.findElementByPath project_id: project_id, path: path, (err, element, type)->
  324. return callback(err) if err?
  325. return callback(new Errors.NotFoundError("project not found")) if !element?
  326. self.deleteEntity.withoutLock project_id, element._id, type, userId, callback
  327. mkdirp: wrapWithLock (project_id, path, callback = (err, newlyCreatedFolders, lastFolderInPath)->)->
  328. ProjectEntityMongoUpdateHandler.mkdirp project_id, path, callback
  329. addFolder: wrapWithLock (project_id, parentFolder_id, folderName, callback) ->
  330. if not SafePath.isCleanFilename folderName
  331. return callback new Errors.InvalidNameError("invalid element name")
  332. ProjectEntityMongoUpdateHandler.addFolder project_id, parentFolder_id, folderName, callback
  333. moveEntity: wrapWithLock (project_id, entity_id, destFolderId, entityType, userId, callback = (error) ->)->
  334. logger.log {entityType, entity_id, project_id, destFolderId}, "moving entity"
  335. if !entityType?
  336. logger.err {err: "No entityType set", project_id, entity_id}
  337. return callback("No entityType set")
  338. entityType = entityType.toLowerCase()
  339. ProjectEntityMongoUpdateHandler.moveEntity project_id, entity_id, destFolderId, entityType, (err, project_name, startPath, endPath, rev, changes) ->
  340. return callback(err) if err?
  341. TpdsUpdateSender.moveEntity { project_id, project_name, startPath, endPath, rev }
  342. DocumentUpdaterHandler.updateProjectStructure project_id, userId, changes, callback
  343. renameEntity: wrapWithLock (project_id, entity_id, entityType, newName, userId, callback)->
  344. if not SafePath.isCleanFilename newName
  345. return callback new Errors.InvalidNameError("invalid element name")
  346. logger.log(entity_id: entity_id, project_id: project_id, ('renaming '+entityType))
  347. if !entityType?
  348. logger.err err: "No entityType set", project_id: project_id, entity_id: entity_id
  349. return callback("No entityType set")
  350. entityType = entityType.toLowerCase()
  351. ProjectEntityMongoUpdateHandler.renameEntity project_id, entity_id, entityType, newName, (err, project_name, startPath, endPath, rev, changes) ->
  352. return callback(err) if err?
  353. TpdsUpdateSender.moveEntity({project_id, startPath, endPath, project_name, rev})
  354. DocumentUpdaterHandler.updateProjectStructure project_id, userId, changes, callback
  355. # This doesn't directly update project structure but we need to take the lock
  356. # to prevent anything else being queued before the resync update
  357. resyncProjectHistory: wrapWithLock (project_id, callback) ->
  358. ProjectGetter.getProject project_id, rootFolder: true, overleaf: true, (error, project) ->
  359. return callback(error) if error?
  360. if !project?.overleaf?.history?.id?
  361. error = new Errors.ProjectHistoryDisabledError("project history not enabled for #{project_id}")
  362. return callback(error)
  363. ProjectEntityHandler.getAllEntitiesFromProject project, (error, docs, files) ->
  364. return callback(error) if error?
  365. docs = _.map docs, (doc) ->
  366. doc: doc.doc._id
  367. path: doc.path
  368. files = _.map files, (file) ->
  369. file: file.file._id
  370. path: file.path
  371. url: FileStoreHandler._buildUrl(project_id, file.file._id)
  372. DocumentUpdaterHandler.resyncProjectHistory project_id, docs, files, callback
  373. _cleanUpEntity: (project, entity, entityType, path, userId, callback = (error) ->) ->
  374. if(entityType.indexOf("file") != -1)
  375. self._cleanUpFile project, entity, path, userId, callback
  376. else if (entityType.indexOf("doc") != -1)
  377. self._cleanUpDoc project, entity, path, userId, callback
  378. else if (entityType.indexOf("folder") != -1)
  379. self._cleanUpFolder project, entity, path, userId, callback
  380. else
  381. callback()
  382. _cleanUpDoc: (project, doc, path, userId, callback = (error) ->) ->
  383. project_id = project._id.toString()
  384. doc_id = doc._id.toString()
  385. unsetRootDocIfRequired = (callback) =>
  386. if project.rootDoc_id? and project.rootDoc_id.toString() == doc_id
  387. @unsetRootDoc project_id, callback
  388. else
  389. callback()
  390. unsetRootDocIfRequired (error) ->
  391. return callback(error) if error?
  392. ProjectEntityMongoUpdateHandler._insertDeletedDocReference project._id, doc, (error) ->
  393. return callback(error) if error?
  394. DocumentUpdaterHandler.deleteDoc project_id, doc_id, (error) ->
  395. return callback(error) if error?
  396. DocstoreManager.deleteDoc project_id, doc_id, (error) ->
  397. return callback(error) if error?
  398. changes = oldDocs: [ {doc, path} ]
  399. DocumentUpdaterHandler.updateProjectStructure project_id, userId, changes, callback
  400. _cleanUpFile: (project, file, path, userId, callback = (error) ->) ->
  401. ProjectEntityMongoUpdateHandler._insertDeletedFileReference project._id, file, (error) ->
  402. return callback(error) if error?
  403. project_id = project._id.toString()
  404. changes = oldFiles: [ {file, path} ]
  405. # we are now keeping a copy of every file versio so we no longer delete
  406. # the file from the filestore
  407. DocumentUpdaterHandler.updateProjectStructure project_id, userId, changes, callback
  408. _cleanUpFolder: (project, folder, folderPath, userId, callback = (error) ->) ->
  409. jobs = []
  410. for doc in folder.docs
  411. do (doc) ->
  412. docPath = path.join(folderPath, doc.name)
  413. jobs.push (callback) -> self._cleanUpDoc project, doc, docPath, userId, callback
  414. for file in folder.fileRefs
  415. do (file) ->
  416. filePath = path.join(folderPath, file.name)
  417. jobs.push (callback) -> self._cleanUpFile project, file, filePath, userId, callback
  418. for childFolder in folder.folders
  419. do (childFolder) ->
  420. folderPath = path.join(folderPath, childFolder.name)
  421. jobs.push (callback) -> self._cleanUpFolder project, childFolder, folderPath, userId, callback
  422. async.series jobs, callback