ProjectManager.js 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. /* eslint-disable
  2. camelcase,
  3. handle-callback-err,
  4. no-unused-vars,
  5. */
  6. // TODO: This file was created by bulk-decaffeinate.
  7. // Fix any style issues and re-enable lint.
  8. /*
  9. * decaffeinate suggestions:
  10. * DS207: Consider shorter variations of null checks
  11. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  12. */
  13. let ProjectManager
  14. const RedisManager = require('./RedisManager')
  15. const ProjectHistoryRedisManager = require('./ProjectHistoryRedisManager')
  16. const DocumentManager = require('./DocumentManager')
  17. const HistoryManager = require('./HistoryManager')
  18. const async = require('async')
  19. const logger = require('logger-sharelatex')
  20. const Metrics = require('./Metrics')
  21. const Errors = require('./Errors')
  22. module.exports = ProjectManager = {
  23. flushProjectWithLocks(project_id, _callback) {
  24. const timer = new Metrics.Timer('projectManager.flushProjectWithLocks')
  25. const callback = function (...args) {
  26. timer.done()
  27. _callback(...args)
  28. }
  29. RedisManager.getDocIdsInProject(project_id, function (error, doc_ids) {
  30. if (error != null) {
  31. return callback(error)
  32. }
  33. const jobs = []
  34. const errors = []
  35. for (const doc_id of doc_ids) {
  36. ;((doc_id) =>
  37. jobs.push((callback) =>
  38. DocumentManager.flushDocIfLoadedWithLock(
  39. project_id,
  40. doc_id,
  41. function (error) {
  42. if (error != null && error instanceof Errors.NotFoundError) {
  43. logger.warn(
  44. { err: error, project_id, doc_id },
  45. 'found deleted doc when flushing'
  46. )
  47. callback()
  48. } else if (error != null) {
  49. logger.error(
  50. { err: error, project_id, doc_id },
  51. 'error flushing doc'
  52. )
  53. errors.push(error)
  54. callback()
  55. } else {
  56. callback()
  57. }
  58. }
  59. )
  60. ))(doc_id)
  61. }
  62. logger.log({ project_id, doc_ids }, 'flushing docs')
  63. async.series(jobs, function () {
  64. if (errors.length > 0) {
  65. callback(new Error('Errors flushing docs. See log for details'))
  66. } else {
  67. callback(null)
  68. }
  69. })
  70. })
  71. },
  72. flushAndDeleteProjectWithLocks(project_id, options, _callback) {
  73. const timer = new Metrics.Timer(
  74. 'projectManager.flushAndDeleteProjectWithLocks'
  75. )
  76. const callback = function (...args) {
  77. timer.done()
  78. _callback(...args)
  79. }
  80. RedisManager.getDocIdsInProject(project_id, function (error, doc_ids) {
  81. if (error != null) {
  82. return callback(error)
  83. }
  84. const jobs = []
  85. const errors = []
  86. for (const doc_id of doc_ids) {
  87. ;((doc_id) =>
  88. jobs.push((callback) =>
  89. DocumentManager.flushAndDeleteDocWithLock(
  90. project_id,
  91. doc_id,
  92. {},
  93. function (error) {
  94. if (error != null) {
  95. logger.error(
  96. { err: error, project_id, doc_id },
  97. 'error deleting doc'
  98. )
  99. errors.push(error)
  100. }
  101. callback()
  102. }
  103. )
  104. ))(doc_id)
  105. }
  106. logger.log({ project_id, doc_ids }, 'deleting docs')
  107. async.series(jobs, () =>
  108. // When deleting the project here we want to ensure that project
  109. // history is completely flushed because the project may be
  110. // deleted in web after this call completes, and so further
  111. // attempts to flush would fail after that.
  112. HistoryManager.flushProjectChanges(project_id, options, function (
  113. error
  114. ) {
  115. if (errors.length > 0) {
  116. callback(new Error('Errors deleting docs. See log for details'))
  117. } else if (error != null) {
  118. callback(error)
  119. } else {
  120. callback(null)
  121. }
  122. })
  123. )
  124. })
  125. },
  126. queueFlushAndDeleteProject(project_id, callback) {
  127. RedisManager.queueFlushAndDeleteProject(project_id, function (error) {
  128. if (error != null) {
  129. logger.error(
  130. { project_id, error },
  131. 'error adding project to flush and delete queue'
  132. )
  133. return callback(error)
  134. }
  135. Metrics.inc('queued-delete')
  136. callback()
  137. })
  138. },
  139. getProjectDocsTimestamps(project_id, callback) {
  140. RedisManager.getDocIdsInProject(project_id, function (error, doc_ids) {
  141. if (error != null) {
  142. return callback(error)
  143. }
  144. if (!(doc_ids != null ? doc_ids.length : undefined)) {
  145. return callback(null, [])
  146. }
  147. RedisManager.getDocTimestamps(doc_ids, function (error, timestamps) {
  148. if (error != null) {
  149. return callback(error)
  150. }
  151. callback(null, timestamps)
  152. })
  153. })
  154. },
  155. getProjectDocsAndFlushIfOld(
  156. project_id,
  157. projectStateHash,
  158. excludeVersions,
  159. _callback
  160. ) {
  161. const timer = new Metrics.Timer(
  162. 'projectManager.getProjectDocsAndFlushIfOld'
  163. )
  164. const callback = function (...args) {
  165. timer.done()
  166. _callback(...args)
  167. }
  168. RedisManager.checkOrSetProjectState(project_id, projectStateHash, function (
  169. error,
  170. projectStateChanged
  171. ) {
  172. if (error != null) {
  173. logger.error(
  174. { err: error, project_id },
  175. 'error getting/setting project state in getProjectDocsAndFlushIfOld'
  176. )
  177. return callback(error)
  178. }
  179. // we can't return docs if project structure has changed
  180. if (projectStateChanged) {
  181. return callback(
  182. Errors.ProjectStateChangedError('project state changed')
  183. )
  184. }
  185. // project structure hasn't changed, return doc content from redis
  186. RedisManager.getDocIdsInProject(project_id, function (error, doc_ids) {
  187. if (error != null) {
  188. logger.error(
  189. { err: error, project_id },
  190. 'error getting doc ids in getProjectDocs'
  191. )
  192. return callback(error)
  193. }
  194. const jobs = []
  195. for (const doc_id of doc_ids) {
  196. ;((doc_id) =>
  197. jobs.push((
  198. cb // get the doc lines from redis
  199. ) =>
  200. DocumentManager.getDocAndFlushIfOldWithLock(
  201. project_id,
  202. doc_id,
  203. function (err, lines, version) {
  204. if (err != null) {
  205. logger.error(
  206. { err, project_id, doc_id },
  207. 'error getting project doc lines in getProjectDocsAndFlushIfOld'
  208. )
  209. return cb(err)
  210. }
  211. const doc = { _id: doc_id, lines, v: version } // create a doc object to return
  212. cb(null, doc)
  213. }
  214. )
  215. ))(doc_id)
  216. }
  217. async.series(jobs, function (error, docs) {
  218. if (error != null) {
  219. return callback(error)
  220. }
  221. callback(null, docs)
  222. })
  223. })
  224. })
  225. },
  226. clearProjectState(project_id, callback) {
  227. RedisManager.clearProjectState(project_id, callback)
  228. },
  229. updateProjectWithLocks(
  230. project_id,
  231. projectHistoryId,
  232. user_id,
  233. docUpdates,
  234. fileUpdates,
  235. version,
  236. _callback
  237. ) {
  238. const timer = new Metrics.Timer('projectManager.updateProject')
  239. const callback = function (...args) {
  240. timer.done()
  241. _callback(...args)
  242. }
  243. const project_version = version
  244. let project_subversion = 0 // project versions can have multiple operations
  245. let project_ops_length = 0
  246. const handleDocUpdate = function (projectUpdate, cb) {
  247. const doc_id = projectUpdate.id
  248. projectUpdate.version = `${project_version}.${project_subversion++}`
  249. if (projectUpdate.docLines != null) {
  250. ProjectHistoryRedisManager.queueAddEntity(
  251. project_id,
  252. projectHistoryId,
  253. 'doc',
  254. doc_id,
  255. user_id,
  256. projectUpdate,
  257. function (error, count) {
  258. project_ops_length = count
  259. cb(error)
  260. }
  261. )
  262. } else {
  263. DocumentManager.renameDocWithLock(
  264. project_id,
  265. doc_id,
  266. user_id,
  267. projectUpdate,
  268. projectHistoryId,
  269. function (error, count) {
  270. project_ops_length = count
  271. cb(error)
  272. }
  273. )
  274. }
  275. }
  276. const handleFileUpdate = function (projectUpdate, cb) {
  277. const file_id = projectUpdate.id
  278. projectUpdate.version = `${project_version}.${project_subversion++}`
  279. if (projectUpdate.url != null) {
  280. ProjectHistoryRedisManager.queueAddEntity(
  281. project_id,
  282. projectHistoryId,
  283. 'file',
  284. file_id,
  285. user_id,
  286. projectUpdate,
  287. function (error, count) {
  288. project_ops_length = count
  289. cb(error)
  290. }
  291. )
  292. } else {
  293. ProjectHistoryRedisManager.queueRenameEntity(
  294. project_id,
  295. projectHistoryId,
  296. 'file',
  297. file_id,
  298. user_id,
  299. projectUpdate,
  300. function (error, count) {
  301. project_ops_length = count
  302. cb(error)
  303. }
  304. )
  305. }
  306. }
  307. async.eachSeries(docUpdates, handleDocUpdate, function (error) {
  308. if (error != null) {
  309. return callback(error)
  310. }
  311. async.eachSeries(fileUpdates, handleFileUpdate, function (error) {
  312. if (error != null) {
  313. return callback(error)
  314. }
  315. if (
  316. HistoryManager.shouldFlushHistoryOps(
  317. project_ops_length,
  318. docUpdates.length + fileUpdates.length,
  319. HistoryManager.FLUSH_PROJECT_EVERY_N_OPS
  320. )
  321. ) {
  322. HistoryManager.flushProjectChangesAsync(project_id)
  323. }
  324. callback()
  325. })
  326. })
  327. },
  328. }