DocumentManager.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829
  1. const { callbackifyAll } = require('@overleaf/promise-utils')
  2. const RedisManager = require('./RedisManager')
  3. const ProjectHistoryRedisManager = require('./ProjectHistoryRedisManager')
  4. const PersistenceManager = require('./PersistenceManager')
  5. const DiffCodec = require('./DiffCodec')
  6. const logger = require('@overleaf/logger')
  7. const Metrics = require('./Metrics')
  8. const HistoryManager = require('./HistoryManager')
  9. const Errors = require('./Errors')
  10. const RangesManager = require('./RangesManager')
  11. const { extractOriginOrSource } = require('./Utils')
  12. const { getTotalSizeOfLines } = require('./Limits')
  13. const Settings = require('@overleaf/settings')
  14. const { StringFileData } = require('overleaf-editor-core')
  15. const MAX_UNFLUSHED_AGE = Settings.maxUnflushedAgeMs // document should be flushed to mongo this time after a change
  16. const DocumentManager = {
  17. /**
  18. * @param {string} projectId
  19. * @param {string} docId
  20. * @return {Promise<{lines: (string[] | StringFileRawData), version: number, ranges: Ranges, resolvedCommentIds: any[], pathname: string, projectHistoryId: string, unflushedTime: any, alreadyLoaded: boolean, historyRangesSupport: boolean, type: OTType}>}
  21. */
  22. async getDoc(projectId, docId) {
  23. const {
  24. lines,
  25. version,
  26. ranges,
  27. resolvedCommentIds,
  28. pathname,
  29. projectHistoryId,
  30. unflushedTime,
  31. historyRangesSupport,
  32. } = await RedisManager.promises.getDoc(projectId, docId)
  33. if (lines == null || version == null) {
  34. logger.debug(
  35. { projectId, docId },
  36. 'doc not in redis so getting from persistence API'
  37. )
  38. const {
  39. lines,
  40. version,
  41. ranges,
  42. resolvedCommentIds,
  43. pathname,
  44. projectHistoryId,
  45. historyRangesSupport,
  46. } = await PersistenceManager.promises.getDoc(projectId, docId)
  47. logger.debug(
  48. {
  49. projectId,
  50. docId,
  51. lines,
  52. ranges,
  53. resolvedCommentIds,
  54. version,
  55. pathname,
  56. projectHistoryId,
  57. historyRangesSupport,
  58. },
  59. 'got doc from persistence API'
  60. )
  61. await RedisManager.promises.putDocInMemory(
  62. projectId,
  63. docId,
  64. lines,
  65. version,
  66. ranges,
  67. resolvedCommentIds,
  68. pathname,
  69. projectHistoryId,
  70. historyRangesSupport
  71. )
  72. return {
  73. lines,
  74. version,
  75. ranges: ranges || {},
  76. resolvedCommentIds,
  77. pathname,
  78. projectHistoryId,
  79. unflushedTime: null,
  80. alreadyLoaded: false,
  81. historyRangesSupport,
  82. type: Array.isArray(lines) ? 'sharejs-text-ot' : 'history-ot',
  83. }
  84. } else {
  85. return {
  86. lines,
  87. version,
  88. ranges,
  89. pathname,
  90. projectHistoryId,
  91. resolvedCommentIds,
  92. unflushedTime,
  93. alreadyLoaded: true,
  94. historyRangesSupport,
  95. type: Array.isArray(lines) ? 'sharejs-text-ot' : 'history-ot',
  96. }
  97. }
  98. },
  99. async getDocAndRecentOps(projectId, docId, fromVersion) {
  100. const { lines, version, ranges, pathname, projectHistoryId, type } =
  101. await DocumentManager.getDoc(projectId, docId)
  102. if (fromVersion === -1) {
  103. return {
  104. lines,
  105. version,
  106. ops: [],
  107. ranges,
  108. pathname,
  109. projectHistoryId,
  110. type,
  111. }
  112. } else {
  113. const ops = await RedisManager.promises.getPreviousDocOps(
  114. docId,
  115. fromVersion,
  116. version
  117. )
  118. return {
  119. lines,
  120. version,
  121. ops,
  122. ranges,
  123. pathname,
  124. projectHistoryId,
  125. type,
  126. }
  127. }
  128. },
  129. async appendToDoc(projectId, docId, linesToAppend, originOrSource, userId) {
  130. let { lines: currentLines, type } = await DocumentManager.getDoc(
  131. projectId,
  132. docId
  133. )
  134. if (type === 'history-ot') {
  135. const file = StringFileData.fromRaw(currentLines)
  136. // TODO(24596): tc support for history-ot
  137. currentLines = file.getLines()
  138. }
  139. const currentLineSize = getTotalSizeOfLines(currentLines)
  140. const addedSize = getTotalSizeOfLines(linesToAppend)
  141. const newlineSize = '\n'.length
  142. if (currentLineSize + newlineSize + addedSize > Settings.max_doc_length) {
  143. throw new Errors.FileTooLargeError(
  144. 'doc would become too large if appending this text'
  145. )
  146. }
  147. return await DocumentManager.setDoc(
  148. projectId,
  149. docId,
  150. currentLines.concat(linesToAppend),
  151. originOrSource,
  152. userId,
  153. false,
  154. false
  155. )
  156. },
  157. async setDoc(
  158. projectId,
  159. docId,
  160. newLines,
  161. originOrSource,
  162. userId,
  163. undoing,
  164. external
  165. ) {
  166. if (newLines == null) {
  167. throw new Error('No lines were provided to setDoc')
  168. }
  169. // Circular dependencies. Import at runtime.
  170. const HistoryOTUpdateManager = require('./HistoryOTUpdateManager')
  171. const UpdateManager = require('./UpdateManager')
  172. const {
  173. lines: oldLines,
  174. version,
  175. alreadyLoaded,
  176. type,
  177. } = await DocumentManager.getDoc(projectId, docId)
  178. logger.debug(
  179. { docId, projectId, oldLines, newLines },
  180. 'setting a document via http'
  181. )
  182. let op
  183. if (type === 'history-ot') {
  184. const file = StringFileData.fromRaw(oldLines)
  185. const operation = DiffCodec.diffAsHistoryOTEditOperation(
  186. file,
  187. newLines.join('\n')
  188. )
  189. if (operation.isNoop()) {
  190. op = []
  191. } else {
  192. op = [operation.toJSON()]
  193. }
  194. } else {
  195. op = DiffCodec.diffAsShareJsOp(oldLines, newLines)
  196. if (undoing) {
  197. for (const o of op || []) {
  198. o.u = true
  199. } // Turn on undo flag for each op for track changes
  200. }
  201. }
  202. const { origin, source } = extractOriginOrSource(originOrSource)
  203. const update = {
  204. doc: docId,
  205. op,
  206. v: version,
  207. meta: {
  208. user_id: userId,
  209. },
  210. }
  211. if (external) {
  212. update.meta.type = 'external'
  213. }
  214. if (origin) {
  215. update.meta.origin = origin
  216. } else if (source) {
  217. update.meta.source = source
  218. }
  219. // Keep track of external updates, whether they are for live documents
  220. // (flush) or unloaded documents (evict), and whether the update is a no-op.
  221. Metrics.inc('external-update', 1, {
  222. status: op.length > 0 ? 'diff' : 'noop',
  223. method: alreadyLoaded ? 'flush' : 'evict',
  224. path: source,
  225. })
  226. // Do not notify the frontend about a noop update.
  227. // We still want to execute the code below
  228. // to evict the doc if we loaded it into redis for
  229. // this update, otherwise the doc would never be
  230. // removed from redis.
  231. if (op.length > 0) {
  232. if (type === 'history-ot') {
  233. await HistoryOTUpdateManager.applyUpdate(projectId, docId, update)
  234. } else {
  235. await UpdateManager.promises.applyUpdate(projectId, docId, update)
  236. }
  237. }
  238. // If the document was loaded already, then someone has it open
  239. // in a project, and the usual flushing mechanism will happen.
  240. // Otherwise we should remove it immediately since nothing else
  241. // is using it.
  242. if (alreadyLoaded) {
  243. return await DocumentManager.flushDocIfLoaded(projectId, docId)
  244. } else {
  245. try {
  246. return await DocumentManager.flushAndDeleteDoc(projectId, docId, {})
  247. } finally {
  248. // There is no harm in flushing project history if the previous
  249. // call failed and sometimes it is required
  250. HistoryManager.flushProjectChangesAsync(projectId)
  251. }
  252. }
  253. },
  254. async flushDocIfLoaded(projectId, docId) {
  255. let {
  256. lines,
  257. version,
  258. ranges,
  259. unflushedTime,
  260. lastUpdatedAt,
  261. lastUpdatedBy,
  262. } = await RedisManager.promises.getDoc(projectId, docId)
  263. if (lines == null || version == null) {
  264. Metrics.inc('flush-doc-if-loaded', 1, { status: 'not-loaded' })
  265. logger.debug({ projectId, docId }, 'doc is not loaded so not flushing')
  266. // TODO: return a flag to bail out, as we go on to remove doc from memory?
  267. return
  268. } else if (unflushedTime == null) {
  269. Metrics.inc('flush-doc-if-loaded', 1, { status: 'unmodified' })
  270. logger.debug({ projectId, docId }, 'doc is not modified so not flushing')
  271. return
  272. }
  273. logger.debug({ projectId, docId, version }, 'flushing doc')
  274. Metrics.inc('flush-doc-if-loaded', 1, { status: 'modified' })
  275. if (!Array.isArray(lines)) {
  276. const file = StringFileData.fromRaw(lines)
  277. // TODO(24596): tc support for history-ot
  278. lines = file.getLines()
  279. }
  280. const result = await PersistenceManager.promises.setDoc(
  281. projectId,
  282. docId,
  283. lines,
  284. version,
  285. ranges,
  286. lastUpdatedAt,
  287. lastUpdatedBy || null
  288. )
  289. await RedisManager.promises.clearUnflushedTime(docId)
  290. return result
  291. },
  292. async flushAndDeleteDoc(projectId, docId, options) {
  293. let result
  294. try {
  295. result = await DocumentManager.flushDocIfLoaded(projectId, docId)
  296. } catch (error) {
  297. if (options.ignoreFlushErrors) {
  298. logger.warn(
  299. { projectId, docId, err: error },
  300. 'ignoring flush error while deleting document'
  301. )
  302. } else {
  303. throw error
  304. }
  305. }
  306. await RedisManager.promises.removeDocFromMemory(projectId, docId)
  307. return result
  308. },
  309. async acceptChanges(projectId, docId, changeIds) {
  310. if (changeIds == null) {
  311. changeIds = []
  312. }
  313. const {
  314. lines,
  315. version,
  316. ranges,
  317. pathname,
  318. projectHistoryId,
  319. historyRangesSupport,
  320. } = await DocumentManager.getDoc(projectId, docId)
  321. if (lines == null || version == null) {
  322. throw new Errors.NotFoundError(`document not found: ${docId}`)
  323. }
  324. // TODO(24596): tc support for history-ot
  325. const newRanges = RangesManager.acceptChanges(
  326. projectId,
  327. docId,
  328. changeIds,
  329. ranges,
  330. lines
  331. )
  332. await RedisManager.promises.updateDocument(
  333. projectId,
  334. docId,
  335. lines,
  336. version,
  337. [],
  338. newRanges,
  339. {}
  340. )
  341. if (historyRangesSupport) {
  342. const historyUpdates = RangesManager.getHistoryUpdatesForAcceptedChanges({
  343. docId,
  344. acceptedChangeIds: changeIds,
  345. changes: ranges.changes || [],
  346. lines,
  347. pathname,
  348. projectHistoryId,
  349. })
  350. if (historyUpdates.length === 0) {
  351. return
  352. }
  353. await ProjectHistoryRedisManager.promises.queueOps(
  354. projectId,
  355. ...historyUpdates.map(op => JSON.stringify(op))
  356. )
  357. }
  358. },
  359. async rejectChanges(projectId, docId, changeIds, userId) {
  360. const UpdateManager = require('./UpdateManager')
  361. const HistoryOTUpdateManager = require('./HistoryOTUpdateManager')
  362. const { lines, version, ranges } = await DocumentManager.getDoc(
  363. projectId,
  364. docId
  365. )
  366. if (lines == null || version == null) {
  367. throw new Errors.NotFoundError(`document not found: ${docId}`)
  368. }
  369. const changesToReject = ranges.changes
  370. ? ranges.changes.filter(change => changeIds.includes(change.id))
  371. : []
  372. // Apply inverted operations for rejected changes (based on reject-changes.ts logic)
  373. // Sort changes in reverse order by position to avoid conflicts
  374. changesToReject.sort((a, b) => b.op.p - a.op.p)
  375. const ops = []
  376. for (const change of changesToReject) {
  377. if (change.op.i) {
  378. const deleteOp = {
  379. p: change.op.p,
  380. d: change.op.i,
  381. u: true,
  382. }
  383. ops.push(deleteOp)
  384. } else if (change.op.d) {
  385. const insertOp = {
  386. p: change.op.p,
  387. i: change.op.d,
  388. u: true,
  389. }
  390. ops.push(insertOp)
  391. }
  392. }
  393. const update = {
  394. doc: docId,
  395. op: ops,
  396. v: version,
  397. meta: {
  398. user_id: userId,
  399. ts: new Date().toISOString(),
  400. },
  401. }
  402. if (HistoryOTUpdateManager.isHistoryOTEditOperationUpdate(update)) {
  403. await HistoryOTUpdateManager.applyUpdate(projectId, docId, update)
  404. } else {
  405. await UpdateManager.promises.applyUpdate(projectId, docId, update)
  406. }
  407. return { rejectedChangeIds: changesToReject.map(c => c.id) }
  408. },
  409. async updateCommentState(projectId, docId, commentId, userId, resolved) {
  410. const { lines, version, pathname, historyRangesSupport } =
  411. await DocumentManager.getDoc(projectId, docId)
  412. if (lines == null || version == null) {
  413. throw new Errors.NotFoundError(`document not found: ${docId}`)
  414. }
  415. if (historyRangesSupport) {
  416. await RedisManager.promises.updateCommentState(docId, commentId, resolved)
  417. await ProjectHistoryRedisManager.promises.queueOps(
  418. projectId,
  419. JSON.stringify({
  420. pathname,
  421. commentId,
  422. resolved,
  423. meta: {
  424. ts: new Date(),
  425. user_id: userId,
  426. },
  427. })
  428. )
  429. }
  430. },
  431. async getComment(projectId, docId, commentId) {
  432. // TODO(24596): tc support for history-ot
  433. const { ranges } = await DocumentManager.getDoc(projectId, docId)
  434. const comment = ranges?.comments?.find(comment => comment.id === commentId)
  435. if (!comment) {
  436. throw new Errors.NotFoundError({
  437. message: 'comment not found',
  438. info: { commentId },
  439. })
  440. }
  441. return comment
  442. },
  443. async deleteComment(projectId, docId, commentId, userId) {
  444. const { lines, version, ranges, pathname, historyRangesSupport } =
  445. await DocumentManager.getDoc(projectId, docId)
  446. if (lines == null || version == null) {
  447. throw new Errors.NotFoundError(`document not found: ${docId}`)
  448. }
  449. // TODO(24596): tc support for history-ot
  450. const newRanges = RangesManager.deleteComment(commentId, ranges)
  451. await RedisManager.promises.updateDocument(
  452. projectId,
  453. docId,
  454. lines,
  455. version,
  456. [],
  457. newRanges,
  458. {}
  459. )
  460. if (historyRangesSupport) {
  461. await RedisManager.promises.updateCommentState(docId, commentId, false)
  462. await ProjectHistoryRedisManager.promises.queueOps(
  463. projectId,
  464. JSON.stringify({
  465. pathname,
  466. deleteComment: commentId,
  467. meta: {
  468. ts: new Date(),
  469. user_id: userId,
  470. },
  471. })
  472. )
  473. }
  474. },
  475. async renameDoc(projectId, docId, userId, update, projectHistoryId) {
  476. await RedisManager.promises.renameDoc(
  477. projectId,
  478. docId,
  479. userId,
  480. update,
  481. projectHistoryId
  482. )
  483. },
  484. async getDocAndFlushIfOld(projectId, docId) {
  485. let { lines, version, unflushedTime, alreadyLoaded } =
  486. await DocumentManager.getDoc(projectId, docId)
  487. // if doc was already loaded see if it needs to be flushed
  488. if (
  489. alreadyLoaded &&
  490. unflushedTime != null &&
  491. Date.now() - unflushedTime > MAX_UNFLUSHED_AGE
  492. ) {
  493. await DocumentManager.flushDocIfLoaded(projectId, docId)
  494. }
  495. if (!Array.isArray(lines)) {
  496. const file = StringFileData.fromRaw(lines)
  497. // TODO(24596): tc support for history-ot
  498. lines = file.getLines()
  499. }
  500. return { lines, version }
  501. },
  502. async resyncDocContents(projectId, docId, path, opts = {}) {
  503. logger.debug({ projectId, docId, path }, 'start resyncing doc contents')
  504. let {
  505. lines,
  506. ranges,
  507. resolvedCommentIds,
  508. version,
  509. projectHistoryId,
  510. historyRangesSupport,
  511. } = await RedisManager.promises.getDoc(projectId, docId)
  512. // To avoid issues where the same docId appears with different paths,
  513. // we use the path from the resyncProjectStructure update. If we used
  514. // the path from the getDoc call to web then the two occurences of the
  515. // docId would map to the same path, and this would be rejected by
  516. // project-history as an unexpected resyncDocContent update.
  517. if (lines == null || version == null) {
  518. logger.debug(
  519. { projectId, docId },
  520. 'resyncing doc contents - not found in redis - retrieving from web'
  521. )
  522. ;({
  523. lines,
  524. ranges,
  525. resolvedCommentIds,
  526. version,
  527. projectHistoryId,
  528. historyRangesSupport,
  529. } = await PersistenceManager.promises.getDoc(projectId, docId, {
  530. peek: true,
  531. }))
  532. } else {
  533. logger.debug(
  534. { projectId, docId },
  535. 'resyncing doc contents - doc in redis - will queue in redis'
  536. )
  537. }
  538. if (opts.historyRangesMigration) {
  539. historyRangesSupport = opts.historyRangesMigration === 'forwards'
  540. }
  541. await ProjectHistoryRedisManager.promises.queueResyncDocContent(
  542. projectId,
  543. projectHistoryId,
  544. docId,
  545. lines,
  546. ranges ?? {},
  547. resolvedCommentIds,
  548. version,
  549. // use the path from the resyncProjectStructure update
  550. path,
  551. historyRangesSupport
  552. )
  553. if (opts.historyRangesMigration) {
  554. await RedisManager.promises.setHistoryRangesSupportFlag(
  555. docId,
  556. historyRangesSupport
  557. )
  558. }
  559. },
  560. async getDocWithLock(projectId, docId) {
  561. const UpdateManager = require('./UpdateManager')
  562. return await UpdateManager.promises.lockUpdatesAndDo(
  563. DocumentManager.getDoc,
  564. projectId,
  565. docId
  566. )
  567. },
  568. async getCommentWithLock(projectId, docId, commentId) {
  569. const UpdateManager = require('./UpdateManager')
  570. return await UpdateManager.promises.lockUpdatesAndDo(
  571. DocumentManager.getComment,
  572. projectId,
  573. docId,
  574. commentId
  575. )
  576. },
  577. async getDocAndRecentOpsWithLock(projectId, docId, fromVersion) {
  578. const UpdateManager = require('./UpdateManager')
  579. return await UpdateManager.promises.lockUpdatesAndDo(
  580. DocumentManager.getDocAndRecentOps,
  581. projectId,
  582. docId,
  583. fromVersion
  584. )
  585. },
  586. async getDocAndFlushIfOldWithLock(projectId, docId) {
  587. const UpdateManager = require('./UpdateManager')
  588. return await UpdateManager.promises.lockUpdatesAndDo(
  589. DocumentManager.getDocAndFlushIfOld,
  590. projectId,
  591. docId
  592. )
  593. },
  594. async setDocWithLock(
  595. projectId,
  596. docId,
  597. lines,
  598. source,
  599. userId,
  600. undoing,
  601. external
  602. ) {
  603. const UpdateManager = require('./UpdateManager')
  604. return await UpdateManager.promises.lockUpdatesAndDo(
  605. DocumentManager.setDoc,
  606. projectId,
  607. docId,
  608. lines,
  609. source,
  610. userId,
  611. undoing,
  612. external
  613. )
  614. },
  615. async appendToDocWithLock(projectId, docId, lines, source, userId) {
  616. const UpdateManager = require('./UpdateManager')
  617. return await UpdateManager.promises.lockUpdatesAndDo(
  618. DocumentManager.appendToDoc,
  619. projectId,
  620. docId,
  621. lines,
  622. source,
  623. userId
  624. )
  625. },
  626. async flushDocIfLoadedWithLock(projectId, docId) {
  627. const UpdateManager = require('./UpdateManager')
  628. return await UpdateManager.promises.lockUpdatesAndDo(
  629. DocumentManager.flushDocIfLoaded,
  630. projectId,
  631. docId
  632. )
  633. },
  634. async flushAndDeleteDocWithLock(projectId, docId, options) {
  635. const UpdateManager = require('./UpdateManager')
  636. return await UpdateManager.promises.lockUpdatesAndDo(
  637. DocumentManager.flushAndDeleteDoc,
  638. projectId,
  639. docId,
  640. options
  641. )
  642. },
  643. async acceptChangesWithLock(projectId, docId, changeIds) {
  644. const UpdateManager = require('./UpdateManager')
  645. await UpdateManager.promises.lockUpdatesAndDo(
  646. DocumentManager.acceptChanges,
  647. projectId,
  648. docId,
  649. changeIds
  650. )
  651. },
  652. async rejectChangesWithLock(projectId, docId, changeIds, userId) {
  653. const UpdateManager = require('./UpdateManager')
  654. return await UpdateManager.promises.lockUpdatesAndDo(
  655. DocumentManager.rejectChanges,
  656. projectId,
  657. docId,
  658. changeIds,
  659. userId
  660. )
  661. },
  662. async updateCommentStateWithLock(
  663. projectId,
  664. docId,
  665. threadId,
  666. userId,
  667. resolved
  668. ) {
  669. const UpdateManager = require('./UpdateManager')
  670. await UpdateManager.promises.lockUpdatesAndDo(
  671. DocumentManager.updateCommentState,
  672. projectId,
  673. docId,
  674. threadId,
  675. userId,
  676. resolved
  677. )
  678. },
  679. async deleteCommentWithLock(projectId, docId, threadId, userId) {
  680. const UpdateManager = require('./UpdateManager')
  681. await UpdateManager.promises.lockUpdatesAndDo(
  682. DocumentManager.deleteComment,
  683. projectId,
  684. docId,
  685. threadId,
  686. userId
  687. )
  688. },
  689. async renameDocWithLock(projectId, docId, userId, update, projectHistoryId) {
  690. const UpdateManager = require('./UpdateManager')
  691. await UpdateManager.promises.lockUpdatesAndDo(
  692. DocumentManager.renameDoc,
  693. projectId,
  694. docId,
  695. userId,
  696. update,
  697. projectHistoryId
  698. )
  699. },
  700. async resyncDocContentsWithLock(projectId, docId, path, opts) {
  701. const UpdateManager = require('./UpdateManager')
  702. await UpdateManager.promises.lockUpdatesAndDo(
  703. DocumentManager.resyncDocContents,
  704. projectId,
  705. docId,
  706. path,
  707. opts
  708. )
  709. },
  710. }
  711. module.exports = {
  712. ...callbackifyAll(DocumentManager, {
  713. multiResult: {
  714. getDoc: [
  715. 'lines',
  716. 'version',
  717. 'ranges',
  718. 'pathname',
  719. 'projectHistoryId',
  720. 'unflushedTime',
  721. 'alreadyLoaded',
  722. 'historyRangesSupport',
  723. ],
  724. getDocWithLock: [
  725. 'lines',
  726. 'version',
  727. 'ranges',
  728. 'pathname',
  729. 'projectHistoryId',
  730. 'unflushedTime',
  731. 'alreadyLoaded',
  732. 'historyRangesSupport',
  733. ],
  734. getDocAndFlushIfOld: ['lines', 'version'],
  735. getDocAndFlushIfOldWithLock: ['lines', 'version'],
  736. getDocAndRecentOps: [
  737. 'lines',
  738. 'version',
  739. 'ops',
  740. 'ranges',
  741. 'pathname',
  742. 'projectHistoryId',
  743. 'type',
  744. ],
  745. getDocAndRecentOpsWithLock: [
  746. 'lines',
  747. 'version',
  748. 'ops',
  749. 'ranges',
  750. 'pathname',
  751. 'projectHistoryId',
  752. 'type',
  753. ],
  754. },
  755. }),
  756. promises: DocumentManager,
  757. }