RestoreManager.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. const Settings = require('@overleaf/settings')
  2. const Path = require('path')
  3. const FileWriter = require('../../infrastructure/FileWriter')
  4. const FileSystemImportManager = require('../Uploads/FileSystemImportManager')
  5. const EditorController = require('../Editor/EditorController')
  6. const Errors = require('../Errors/Errors')
  7. const moment = require('moment')
  8. const { callbackifyAll } = require('@overleaf/promise-utils')
  9. const { fetchJson } = require('@overleaf/fetch-utils')
  10. const ProjectLocator = require('../Project/ProjectLocator')
  11. const RestoreManager = {
  12. async restoreFileFromV2(userId, projectId, version, pathname) {
  13. const fsPath = await RestoreManager._writeFileVersionToDisk(
  14. projectId,
  15. version,
  16. pathname
  17. )
  18. const basename = Path.basename(pathname)
  19. let dirname = Path.dirname(pathname)
  20. if (dirname === '.') {
  21. // no directory
  22. dirname = ''
  23. }
  24. const parentFolderId = await RestoreManager._findOrCreateFolder(
  25. projectId,
  26. dirname
  27. )
  28. const addEntityWithName = async name =>
  29. await FileSystemImportManager.promises.addEntity(
  30. userId,
  31. projectId,
  32. parentFolderId,
  33. name,
  34. fsPath,
  35. false
  36. )
  37. return await RestoreManager._addEntityWithUniqueName(
  38. addEntityWithName,
  39. basename
  40. )
  41. },
  42. async revertFile(userId, projectId, version, pathname) {
  43. const fsPath = await RestoreManager._writeFileVersionToDisk(
  44. projectId,
  45. version,
  46. pathname
  47. )
  48. const basename = Path.basename(pathname)
  49. let dirname = Path.dirname(pathname)
  50. if (dirname === '.') {
  51. // no directory
  52. dirname = ''
  53. }
  54. const parentFolderId = await RestoreManager._findOrCreateFolder(
  55. projectId,
  56. dirname
  57. )
  58. let fileExists = true
  59. try {
  60. // TODO: Is there a better way of doing this?
  61. await ProjectLocator.promises.findElementByPath({
  62. projectId,
  63. path: pathname,
  64. })
  65. } catch (error) {
  66. fileExists = false
  67. }
  68. if (fileExists) {
  69. throw new Errors.InvalidError('File already exists')
  70. }
  71. const importInfo = await FileSystemImportManager.promises.importFile(
  72. fsPath,
  73. pathname
  74. )
  75. if (importInfo.type !== 'doc') {
  76. // TODO: Handle binary files
  77. throw new Errors.InvalidError('File is not editable')
  78. }
  79. const ranges = await RestoreManager._getRangesFromHistory(
  80. projectId,
  81. version,
  82. pathname
  83. )
  84. return await EditorController.promises.addDocWithRanges(
  85. projectId,
  86. parentFolderId,
  87. basename,
  88. importInfo.lines,
  89. ranges,
  90. 'revert',
  91. userId
  92. )
  93. },
  94. async _findOrCreateFolder(projectId, dirname) {
  95. const { lastFolder } = await EditorController.promises.mkdirp(
  96. projectId,
  97. dirname
  98. )
  99. return lastFolder?._id
  100. },
  101. async _addEntityWithUniqueName(addEntityWithName, basename) {
  102. try {
  103. return await addEntityWithName(basename)
  104. } catch (error) {
  105. if (error instanceof Errors.InvalidNameError) {
  106. // likely a duplicate name, so try with a prefix
  107. const date = moment(new Date()).format('Do MMM YY H:mm:ss')
  108. // Move extension to the end so the file type is preserved
  109. const extension = Path.extname(basename)
  110. basename = Path.basename(basename, extension)
  111. basename = `${basename} (Restored on ${date})`
  112. if (extension !== '') {
  113. basename = `${basename}${extension}`
  114. }
  115. return await addEntityWithName(basename)
  116. } else {
  117. throw error
  118. }
  119. }
  120. },
  121. async _writeFileVersionToDisk(projectId, version, pathname) {
  122. const url = `${
  123. Settings.apis.project_history.url
  124. }/project/${projectId}/version/${version}/${encodeURIComponent(pathname)}`
  125. return await FileWriter.promises.writeUrlToDisk(projectId, url)
  126. },
  127. async _getRangesFromHistory(projectId, version, pathname) {
  128. const url = `${
  129. Settings.apis.project_history.url
  130. }/project/${projectId}/ranges/version/${version}/${encodeURIComponent(pathname)}`
  131. return await fetchJson(url)
  132. },
  133. }
  134. module.exports = { ...callbackifyAll(RestoreManager), promises: RestoreManager }