2018-02-01 10:31:42 -05:00
_ = require ( ' underscore ' )
async = require ' async '
logger = require ( ' logger-sharelatex ' )
path = require ( ' path ' )
settings = require ( ' settings-sharelatex ' )
CooldownManager = require ' ../Cooldown/CooldownManager '
Errors = require ' ../Errors/Errors '
Folder = require ( ' ../../models/Folder ' ) . Folder
2018-02-01 10:31:42 -05:00
LockManager = require ( ' ../../infrastructure/LockManager ' )
2018-02-01 10:31:42 -05:00
Project = require ( ' ../../models/Project ' ) . Project
ProjectEntityHandler = require ( ' ./ProjectEntityHandler ' )
ProjectGetter = require ( ' ./ProjectGetter ' )
ProjectLocator = require ( ' ./ProjectLocator ' )
SafePath = require ' ./SafePath '
2018-02-19 06:23:43 -05:00
LOCK_NAMESPACE = " mongoTransaction "
2018-02-01 10:31:42 -05:00
wrapWithLock = (methodWithoutLock) ->
2018-02-15 08:30:25 -05:00
# This lock is used whenever we read or write to an existing project's
# structure. Some operations to project structure cannot be done atomically
# in mongo, this lock is used to prevent reading the structure between two
# parts of a staged update.
2018-02-01 10:31:42 -05:00
methodWithLock = (project_id, args..., callback) ->
2018-02-19 06:23:43 -05:00
LockManager . runWithLock LOCK_NAMESPACE , project_id ,
2018-02-01 10:31:42 -05:00
(cb) -> methodWithoutLock project_id , args . . . , cb
callback
methodWithLock.withoutLock = methodWithoutLock
methodWithLock
2018-02-01 10:31:42 -05:00
module.exports = ProjectEntityMongoUpdateHandler = self =
2018-02-19 06:23:43 -05:00
LOCK_NAMESPACE: LOCK_NAMESPACE
2018-02-01 10:31:42 -05:00
addDoc: wrapWithLock ( project_id , folder_id , doc , callback = (err, result) -> ) ->
ProjectGetter . getProjectWithoutLock project_id , { rootFolder : true , name : true } , (err, project) ->
2018-02-01 10:31:42 -05:00
if err ?
logger . err project_id : project_id , err : err , " error getting project for add doc "
return callback ( err )
logger . log project_id: project_id , folder_id: folder_id , doc_name: doc . name , " adding doc to project with project "
self . _confirmFolder project , folder_id , (folder_id) =>
self . _putElement project , folder_id , doc , " doc " , callback
2018-02-01 10:31:42 -05:00
addFile: wrapWithLock ( project_id , folder_id , fileRef , callback = (error, result, project) -> ) ->
2018-02-15 10:20:14 -05:00
ProjectGetter . getProjectWithoutLock project_id , { rootFolder : true , name : true } , (err, project) ->
2018-02-01 10:31:42 -05:00
if err ?
logger . err project_id : project_id , err : err , " error getting project for add file "
return callback ( err )
logger . log project_id: project . _id , folder_id: folder_id , file_name: fileRef . name , " adding file "
self . _confirmFolder project , folder_id , (folder_id)->
self . _putElement project , folder_id , fileRef , " file " , callback
2018-02-01 10:31:42 -05:00
replaceFile: wrapWithLock (project_id, file_id, callback) ->
ProjectGetter . getProjectWithoutLock project_id , { rootFolder: true , name : true } , (err, project) ->
2018-02-01 10:31:42 -05:00
return callback ( err ) if err ?
ProjectLocator . findElement { project : project , element_id: file_id , type: ' file ' } , (err, fileRef, path)=>
return callback ( err ) if err ?
conditions = _id : project . _id
inc = { }
inc [ " #{ path . mongo } .rev " ] = 1
2018-02-22 07:03:13 -05:00
# we need to increment the project version number for any structure change
inc [ ' version ' ] = 1
2018-02-01 10:31:42 -05:00
set = { }
set [ " #{ path . mongo } .created " ] = new Date ( )
update =
" $inc " : inc
" $set " : set
Project . update conditions , update , { } , (err) ->
return callback ( err ) if err ?
callback null , fileRef , project , path
2018-02-01 10:31:42 -05:00
mkdirp: wrapWithLock (project_id, path, callback) ->
2018-02-01 10:31:42 -05:00
folders = path . split ( ' / ' )
folders = _ . select folders , (folder)->
return folder . length != 0
ProjectGetter . getProjectWithOnlyFolders project_id , (err, project)=>
if path == ' / '
logger . log project_id: project . _id , " mkdir is only trying to make path of / so sending back root folder "
return callback ( null , [ ] , project . rootFolder [ 0 ] )
logger . log project_id: project . _id , path : path , folders : folders , " running mkdirp "
builtUpPath = ' '
procesFolder = (previousFolders, folderName, callback)=>
previousFolders = previousFolders || [ ]
parentFolder = previousFolders [ previousFolders . length - 1 ]
if parentFolder ?
parentFolder_id = parentFolder . _id
builtUpPath = " #{ builtUpPath } / #{ folderName } "
2018-02-12 11:03:11 -05:00
ProjectLocator . findElementByPath project: project , path: builtUpPath , (err, foundFolder)=>
2018-02-01 10:31:42 -05:00
if ! foundFolder ?
logger . log path : path , project_id : project . _id , folderName : folderName , " making folder from mkdirp "
2018-02-01 10:31:42 -05:00
self . addFolder . withoutLock project_id , parentFolder_id , folderName , (err, newFolder, parentFolder_id)->
2018-02-01 10:31:42 -05:00
return callback ( err ) if err ?
newFolder.parentFolder_id = parentFolder_id
previousFolders . push newFolder
callback null , previousFolders
else
foundFolder.filterOut = true
previousFolders . push foundFolder
callback null , previousFolders
async . reduce folders , [ ] , procesFolder , (err, folders) ->
return callback ( err ) if err ?
lastFolder = folders [ folders . length - 1 ]
folders = _ . select folders , (folder)->
! folder . filterOut
callback null , folders , lastFolder
2018-02-01 10:31:42 -05:00
moveEntity: wrapWithLock ( project_id , entity_id , destFolderId , entityType , callback = (error) -> ) ->
ProjectGetter . getProjectWithoutLock project_id , { rootFolder : true , name : true } , (err, project) ->
2018-02-01 10:31:42 -05:00
return callback ( err ) if err ?
ProjectLocator . findElement { project , element_id: entity_id , type: entityType } , (err, entity, entityPath)->
return callback ( err ) if err ?
self . _checkValidMove project , entityType , entity , entityPath , destFolderId , (error) ->
return callback ( error ) if error ?
ProjectEntityHandler . getAllEntitiesFromProject project , (error, oldDocs, oldFiles) ->
return callback ( error ) if error ?
self . _removeElementFromMongoArray Project , project_id , entityPath . mongo , (err, newProject)->
return callback ( err ) if err ?
self . _putElement newProject , destFolderId , entity , entityType , (err, result, newProject)->
return callback ( err ) if err ?
ProjectEntityHandler . getAllEntitiesFromProject newProject , (err, newDocs, newFiles) ->
return callback ( err ) if err ?
startPath = entityPath . fileSystem
endPath = result . path . fileSystem
changes = { oldDocs , newDocs , oldFiles , newFiles }
callback null , project . name , startPath , endPath , entity . rev , changes , callback
2018-02-01 10:31:42 -05:00
deleteEntity: wrapWithLock (project_id, entity_id, entityType, callback) ->
ProjectGetter . getProjectWithoutLock project_id , { name : true , rootFolder : true } , (error, project) ->
2018-02-01 10:31:42 -05:00
return callback ( error ) if error ?
ProjectLocator . findElement { project: project , element_id: entity_id , type: entityType } , (error, entity, path) ->
return callback ( error ) if error ?
self . _removeElementFromMongoArray Project , project_id , path . mongo , (error) ->
return callback ( error ) if error ?
callback null , entity , path , project
2018-02-01 10:31:42 -05:00
renameEntity: wrapWithLock (project_id, entity_id, entityType, newName, callback) ->
ProjectGetter . getProjectWithoutLock project_id , { rootFolder : true , name : true } , (error, project)=>
2018-02-01 10:31:42 -05:00
return callback ( error ) if error ?
ProjectEntityHandler . getAllEntitiesFromProject project , (error, oldDocs, oldFiles) =>
return callback ( error ) if error ?
ProjectLocator . findElement { project : project , element_id : entity_id , type : entityType } , (error, entity, entPath, parentFolder)=>
return callback ( error ) if error ?
# check if the new name already exists in the current folder
self . _checkValidElementName parentFolder , newName , (error) =>
return callback ( error ) if error ?
endPath = path . join ( path . dirname ( entPath . fileSystem ) , newName )
conditions = { _id : project_id }
2018-02-22 07:03:13 -05:00
update = " $set " : { } , " $inc " : { }
2018-02-01 10:31:42 -05:00
namePath = entPath . mongo + " .name "
update [ " $set " ] [ namePath ] = newName
2018-02-22 07:03:13 -05:00
# we need to increment the project version number for any structure change
update [ " $inc " ] [ " version " ] = 1
2018-02-01 10:31:42 -05:00
Project . findOneAndUpdate conditions , update , { " new " : true } , (error, newProject) ->
return callback ( error ) if error ?
ProjectEntityHandler . getAllEntitiesFromProject newProject , (error, newDocs, newFiles) =>
return callback ( error ) if error ?
startPath = entPath . fileSystem
changes = { oldDocs , newDocs , oldFiles , newFiles }
callback null , project . name , startPath , endPath , entity . rev , changes , callback
2018-02-01 10:31:42 -05:00
addFolder: wrapWithLock (project_id, parentFolder_id, folderName, callback) ->
ProjectGetter . getProjectWithoutLock project_id , { rootFolder : true , name : true } , (err, project) ->
2018-02-01 10:31:42 -05:00
if err ?
logger . err project_id : project_id , err : err , " error getting project for add folder "
return callback ( err )
self . _confirmFolder project , parentFolder_id , (parentFolder_id) =>
folder = new Folder name: folderName
logger . log project: project . _id , parentFolder_id : parentFolder_id , folderName : folderName , " adding new folder "
self . _putElement project , parentFolder_id , folder , " folder " , (err)=>
if err ?
logger . err err : err , project_id : project . _id , " error adding folder to project "
return callback ( err )
callback null , folder , parentFolder_id
_removeElementFromMongoArray: ( model , model_id , path , callback = (err, project) -> ) ->
conditions = { _id : model_id }
update = { " $unset " : { } }
update [ " $unset " ] [ path ] = 1
model . update conditions , update , { } , (err)->
2018-02-22 07:03:13 -05:00
pullUpdate = { " $pull " : { } , " $inc " : { } }
2018-02-01 10:31:42 -05:00
nonArrayPath = path . slice ( 0 , path . lastIndexOf ( " . " ) )
pullUpdate [ " $pull " ] [ nonArrayPath ] = null
2018-02-22 07:03:13 -05:00
# we need to increment the project version number for any structure change
pullUpdate [ " $inc " ] [ " version " ] = 1
2018-02-01 10:31:42 -05:00
model . findOneAndUpdate conditions , pullUpdate , { " new " : true } , callback
_countElements: (project)->
countFolder = (folder)->
total = 0
for subfolder in folder ? . folders or [ ]
total += countFolder ( subfolder )
if folder ? . folders ? . length ?
total += folder . folders . length
if folder ? . docs ? . length ?
total += folder . docs . length
if folder ? . fileRefs ? . length ?
total += folder . fileRefs . length
total
countFolder project . rootFolder [ 0 ]
_putElement: ( project , folder_id , element , type , callback = (err, path, project)-> ) ->
sanitizeTypeOfElement = (elementType)->
lastChar = elementType . slice - 1
if lastChar != " s "
elementType += " s "
if elementType == " files "
elementType = " fileRefs "
return elementType
if ! element ? or ! element . _id ?
e = new Error ( " no element passed to be inserted " )
logger . err project_id : project . _id , folder_id : folder_id , element : element , type : type , " failed trying to insert element as it was null "
return callback ( e )
type = sanitizeTypeOfElement type
# original check path.resolve("/", element.name) isnt "/#{element.name}" or element.name.match("/")
# check if name is allowed
if not SafePath . isCleanFilename element . name
e = new Errors . InvalidNameError ( " invalid element name " )
logger . err project_id : project . _id , folder_id : folder_id , element : element , type : type , " failed trying to insert element as name was invalid "
return callback ( e )
if ! folder_id ?
folder_id = project . rootFolder [ 0 ] . _id
if self . _countElements ( project ) > settings . maxEntitiesPerProject
logger . warn project_id : project . _id , " project too big, stopping insertions "
CooldownManager . putProjectOnCooldown ( project . _id )
return callback ( " project_has_to_many_files " )
ProjectLocator . findElement { project : project , element_id : folder_id , type : " folders " } , (err, folder, path)=>
if err ?
logger . err err : err , project_id : project . _id , folder_id : folder_id , type : type , element : element , " error finding folder for _putElement "
return callback ( err )
newPath =
fileSystem: " #{ path . fileSystem } / #{ element . name } "
mongo: path . mongo
# check if the path would be too long
if not SafePath . isAllowedLength newPath . fileSystem
return callback new Errors . InvalidNameError ( " path too long " )
self . _checkValidElementName folder , element . name , (err) =>
return callback ( err ) if err ?
id = element . _id + ' '
element._id = require ( ' mongoose ' ) . Types . ObjectId ( id )
conditions = _id : project . _id
mongopath = " #{ path . mongo } . #{ type } "
2018-02-22 07:03:13 -05:00
update = " $push " : { } , " $inc " : { }
2018-02-01 10:31:42 -05:00
update [ " $push " ] [ mongopath ] = element
2018-02-22 07:03:13 -05:00
# we need to increment the project version number for any structure change
2018-02-23 07:09:35 -05:00
update [ " $inc " ] [ " version " ] = 1 # increment project version number
logger . log project_id: project . _id , element_id: element . _id , fileType: type , folder_id: folder_id , mongopath : mongopath , update: update , " adding element to project "
Project . findOneAndUpdate conditions , update , { " new " : true } , (err, newProject)->
2018-02-01 10:31:42 -05:00
if err ?
logger . err err: err , project_id: project . _id , ' error saving in putElement project '
return callback ( err )
2018-02-23 07:09:35 -05:00
callback ( err , { path : newPath } , newProject )
2018-02-01 10:31:42 -05:00
_checkValidElementName: ( folder , name , callback = (err) -> ) ->
# check if the name is already taken by a doc, file or
# folder. If so, return an error "file already exists".
err = new Errors . InvalidNameError ( " file already exists " )
for doc in folder ? . docs or [ ]
return callback ( err ) if doc . name is name
for file in folder ? . fileRefs or [ ]
return callback ( err ) if file . name is name
for folder in folder ? . folders or [ ]
return callback ( err ) if folder . name is name
callback ( )
_confirmFolder: (project, folder_id, callback)->
logger . log folder_id : folder_id , project_id : project . _id , " confirming folder in project "
if folder_id + ' ' == ' undefined '
callback ( project . rootFolder [ 0 ] . _id )
else if folder_id != null
callback folder_id
else
callback ( project . rootFolder [ 0 ] . _id )
_checkValidMove: ( project , entityType , entity , entityPath , destFolderId , callback = (error) -> ) ->
ProjectLocator . findElement { project , element_id: destFolderId , type : " folder " } , (err, destEntity, destFolderPath) ->
return callback ( err ) if err ?
# check if there is already a doc/file/folder with the same name
# in the destination folder
self . _checkValidElementName destEntity , entity . name , (err)->
return callback ( err ) if err ?
if entityType . match ( /folder/ )
logger . log destFolderPath: destFolderPath . fileSystem , folderPath: entityPath . fileSystem , " checking folder is not moving into child folder "
isNestedFolder = destFolderPath . fileSystem . slice ( 0 , entityPath . fileSystem . length ) == entityPath . fileSystem
if isNestedFolder
return callback ( new Errors . InvalidNameError ( " destination folder is a child folder of me " ) )
callback ( )