ProjectLocator.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. const _ = require('underscore')
  2. const logger = require('logger-sharelatex')
  3. const async = require('async')
  4. const ProjectGetter = require('./ProjectGetter')
  5. const Errors = require('../Errors/Errors')
  6. const { promisifyMultiResult } = require('../../util/promises')
  7. function findElement(options, _callback) {
  8. // The search algorithm below potentially invokes the callback multiple
  9. // times.
  10. const callback = _.once(_callback)
  11. const {
  12. project,
  13. project_id: projectId,
  14. element_id: elementId,
  15. type,
  16. } = options
  17. const elementType = sanitizeTypeOfElement(type)
  18. let count = 0
  19. const endOfBranch = function () {
  20. if (--count === 0) {
  21. logger.warn(
  22. `element ${elementId} could not be found for project ${
  23. projectId || project._id
  24. }`
  25. )
  26. callback(new Errors.NotFoundError('entity not found'))
  27. }
  28. }
  29. function search(searchFolder, path) {
  30. count++
  31. const element = _.find(
  32. searchFolder[elementType],
  33. el => (el != null ? el._id : undefined) + '' === elementId + ''
  34. ) // need to ToString both id's for robustness
  35. if (
  36. element == null &&
  37. searchFolder.folders != null &&
  38. searchFolder.folders.length !== 0
  39. ) {
  40. _.each(searchFolder.folders, (folder, index) => {
  41. if (folder == null) {
  42. return
  43. }
  44. const newPath = {}
  45. for (const key of Object.keys(path)) {
  46. const value = path[key]
  47. newPath[key] = value
  48. } // make a value copy of the string
  49. newPath.fileSystem += `/${folder.name}`
  50. newPath.mongo += `.folders.${index}`
  51. search(folder, newPath)
  52. })
  53. endOfBranch()
  54. } else if (element != null) {
  55. const elementPlaceInArray = getIndexOf(
  56. searchFolder[elementType],
  57. elementId
  58. )
  59. path.fileSystem += `/${element.name}`
  60. path.mongo += `.${elementType}.${elementPlaceInArray}`
  61. callback(null, element, path, searchFolder)
  62. } else if (element == null) {
  63. endOfBranch()
  64. }
  65. }
  66. const path = { fileSystem: '', mongo: 'rootFolder.0' }
  67. const startSearch = project => {
  68. if (elementId + '' === project.rootFolder[0]._id + '') {
  69. callback(null, project.rootFolder[0], path, null)
  70. } else {
  71. search(project.rootFolder[0], path)
  72. }
  73. }
  74. if (project != null) {
  75. startSearch(project)
  76. } else {
  77. ProjectGetter.getProject(
  78. projectId,
  79. { rootFolder: true, rootDoc_id: true },
  80. (err, project) => {
  81. if (err != null) {
  82. return callback(err)
  83. }
  84. if (project == null) {
  85. return callback(new Errors.NotFoundError('project not found'))
  86. }
  87. startSearch(project)
  88. }
  89. )
  90. }
  91. }
  92. function findRootDoc(opts, callback) {
  93. const getRootDoc = project => {
  94. if (project.rootDoc_id != null) {
  95. findElement(
  96. { project, element_id: project.rootDoc_id, type: 'docs' },
  97. (error, ...args) => {
  98. if (error != null) {
  99. if (error instanceof Errors.NotFoundError) {
  100. return callback(null, null)
  101. } else {
  102. return callback(error)
  103. }
  104. }
  105. callback(null, ...args)
  106. }
  107. )
  108. } else {
  109. callback(null, null)
  110. }
  111. }
  112. const { project, project_id: projectId } = opts
  113. if (project != null) {
  114. getRootDoc(project)
  115. } else {
  116. ProjectGetter.getProject(
  117. projectId,
  118. { rootFolder: true, rootDoc_id: true },
  119. (err, project) => {
  120. if (err != null) {
  121. logger.warn({ err }, 'error getting project')
  122. callback(err)
  123. } else {
  124. getRootDoc(project)
  125. }
  126. }
  127. )
  128. }
  129. }
  130. function findElementByPath(options, callback) {
  131. const { project, project_id: projectId, path, exactCaseMatch } = options
  132. if (path == null) {
  133. return new Error('no path provided for findElementByPath')
  134. }
  135. if (project != null) {
  136. _findElementByPathWithProject(project, path, exactCaseMatch, callback)
  137. } else {
  138. ProjectGetter.getProject(
  139. projectId,
  140. { rootFolder: true, rootDoc_id: true },
  141. (err, project) => {
  142. if (err != null) {
  143. return callback(err)
  144. }
  145. _findElementByPathWithProject(project, path, exactCaseMatch, callback)
  146. }
  147. )
  148. }
  149. }
  150. function _findElementByPathWithProject(
  151. project,
  152. needlePath,
  153. exactCaseMatch,
  154. callback
  155. ) {
  156. let matchFn
  157. if (exactCaseMatch) {
  158. matchFn = (a, b) => a === b
  159. } else {
  160. matchFn = (a, b) =>
  161. (a != null ? a.toLowerCase() : undefined) ===
  162. (b != null ? b.toLowerCase() : undefined)
  163. }
  164. function getParentFolder(haystackFolder, foldersList, level, cb) {
  165. if (foldersList.length === 0) {
  166. return cb(null, haystackFolder)
  167. }
  168. const needleFolderName = foldersList[level]
  169. let found = false
  170. for (const folder of haystackFolder.folders) {
  171. if (matchFn(folder.name, needleFolderName)) {
  172. found = true
  173. if (level === foldersList.length - 1) {
  174. return cb(null, folder)
  175. } else {
  176. return getParentFolder(folder, foldersList, level + 1, cb)
  177. }
  178. }
  179. }
  180. if (!found) {
  181. cb(
  182. new Error(
  183. `not found project: ${project._id} search path: ${needlePath}, folder ${foldersList[level]} could not be found`
  184. )
  185. )
  186. }
  187. }
  188. function getEntity(folder, entityName, cb) {
  189. let result, type
  190. if (entityName == null) {
  191. return cb(null, folder, 'folder')
  192. }
  193. for (const file of folder.fileRefs || []) {
  194. if (matchFn(file != null ? file.name : undefined, entityName)) {
  195. result = file
  196. type = 'file'
  197. }
  198. }
  199. for (const doc of folder.docs || []) {
  200. if (matchFn(doc != null ? doc.name : undefined, entityName)) {
  201. result = doc
  202. type = 'doc'
  203. }
  204. }
  205. for (const childFolder of folder.folders || []) {
  206. if (
  207. matchFn(childFolder != null ? childFolder.name : undefined, entityName)
  208. ) {
  209. result = childFolder
  210. type = 'folder'
  211. }
  212. }
  213. if (result != null) {
  214. cb(null, result, type)
  215. } else {
  216. cb(
  217. new Error(
  218. `not found project: ${project._id} search path: ${needlePath}, entity ${entityName} could not be found`
  219. )
  220. )
  221. }
  222. }
  223. if (project == null) {
  224. return callback(new Error('Tried to find an element for a null project'))
  225. }
  226. if (needlePath === '' || needlePath === '/') {
  227. return callback(null, project.rootFolder[0], 'folder')
  228. }
  229. if (needlePath.indexOf('/') === 0) {
  230. needlePath = needlePath.substring(1)
  231. }
  232. const foldersList = needlePath.split('/')
  233. const needleName = foldersList.pop()
  234. const rootFolder = project.rootFolder[0]
  235. const jobs = []
  236. jobs.push(cb => getParentFolder(rootFolder, foldersList, 0, cb))
  237. jobs.push((folder, cb) => getEntity(folder, needleName, cb))
  238. async.waterfall(jobs, callback)
  239. }
  240. function sanitizeTypeOfElement(elementType) {
  241. const lastChar = elementType.slice(-1)
  242. if (lastChar !== 's') {
  243. elementType += 's'
  244. }
  245. if (elementType === 'files') {
  246. elementType = 'fileRefs'
  247. }
  248. return elementType
  249. }
  250. function getIndexOf(searchEntity, id) {
  251. const { length } = searchEntity
  252. let count = 0
  253. while (count < length) {
  254. if (
  255. (searchEntity[count] != null ? searchEntity[count]._id : undefined) +
  256. '' ===
  257. id + ''
  258. ) {
  259. return count
  260. }
  261. count++
  262. }
  263. }
  264. module.exports = {
  265. findElement,
  266. findElementByPath,
  267. findRootDoc,
  268. promises: {
  269. findElement: promisifyMultiResult(findElement, [
  270. 'element',
  271. 'path',
  272. 'folder',
  273. ]),
  274. findElementByPath: promisifyMultiResult(findElementByPath, [
  275. 'element',
  276. 'type',
  277. ]),
  278. findRootDoc: promisifyMultiResult(findRootDoc, [
  279. 'element',
  280. 'path',
  281. 'folder',
  282. ]),
  283. },
  284. }