2014-02-12 05:23:40 -05:00
request = require ' request '
request = request . defaults ( )
settings = require ' settings-sharelatex '
_ = require ' underscore '
async = require ' async '
logger = require ( ' logger-sharelatex ' )
2017-04-03 11:18:30 -04:00
metrics = require ( ' metrics-sharelatex ' )
2014-02-12 05:23:40 -05:00
Project = require ( " ../../models/Project " ) . Project
2014-06-18 11:37:18 -04:00
module.exports = DocumentUpdaterHandler =
2014-10-15 09:11:02 -04:00
flushProjectToMongo: ( project_id , callback = (error) -> ) ->
logger . log project_id : project_id , " flushing project from document updater "
2018-03-09 05:23:48 -05:00
DocumentUpdaterHandler . _makeRequest {
path: " /project/ #{ project_id } /flush "
method: " POST "
} , project_id , " flushing.mongo.project " , callback
2014-02-12 05:23:40 -05:00
2014-06-18 11:37:18 -04:00
flushMultipleProjectsToMongo: ( project_ids , callback = (error) -> ) ->
jobs = [ ]
for project_id in project_ids
do (project_id) ->
jobs . push (callback) ->
DocumentUpdaterHandler . flushProjectToMongo project_id , callback
async . series jobs , callback
2014-10-15 09:11:02 -04:00
flushProjectToMongoAndDelete: ( project_id , callback = ()-> ) ->
2014-02-12 05:23:40 -05:00
timer = new metrics . Timer ( " delete.mongo.project " )
2018-03-09 05:23:48 -05:00
url = " #{ settings . apis . documentupdater . url } "
DocumentUpdaterHandler . _makeRequest {
path: " /project/ #{ project_id } "
method: " DELETE "
} , project_id , " flushing.mongo.project " , callback
2014-05-08 10:47:50 -04:00
flushDocToMongo: ( project_id , doc_id , callback = (error) -> ) ->
logger . log project_id : project_id , doc_id: doc_id , " flushing doc from document updater "
2018-03-09 05:23:48 -05:00
DocumentUpdaterHandler . _makeRequest {
path: " /project/ #{ project_id } /doc/ #{ doc_id } /flush "
method: " POST "
} , project_id , " flushing.mongo.doc " , callback
2014-10-15 09:11:02 -04:00
deleteDoc : ( project_id , doc_id , callback = ()-> ) ->
logger . log project_id : project_id , doc_id: doc_id , " deleting doc from document updater "
2018-03-09 05:23:48 -05:00
DocumentUpdaterHandler . _makeRequest {
path: " /project/ #{ project_id } /doc/ #{ doc_id } "
method: " DELETE "
} , project_id , " delete.mongo.doc " , callback
2014-02-12 05:23:40 -05:00
2016-12-08 09:09:06 -05:00
getDocument: ( project_id , doc_id , fromVersion , callback = (error, doclines, version, ranges, ops) -> ) ->
2014-10-15 09:11:02 -04:00
logger . log project_id : project_id , doc_id: doc_id , " getting doc from document updater "
2018-03-09 05:23:48 -05:00
DocumentUpdaterHandler . _makeRequest {
path: " /project/ #{ project_id } /doc/ #{ doc_id } ?fromVersion= #{ fromVersion } "
json: true
} , project_id , " get-document " , (error, doc) ->
return callback ( error ) if error ?
callback null , doc . lines , doc . version , doc . ranges , doc . ops
2014-02-12 05:23:40 -05:00
2016-02-04 09:26:50 -05:00
setDocument : ( project_id , doc_id , user_id , docLines , source , callback = (error) -> ) ->
2018-03-09 05:23:48 -05:00
logger . log project_id : project_id , doc_id: doc_id , source: source , user_id: user_id , " setting doc in document updater "
DocumentUpdaterHandler . _makeRequest {
path: " /project/ #{ project_id } /doc/ #{ doc_id } "
method: " POST "
2014-02-12 05:23:40 -05:00
json:
lines: docLines
2014-10-15 11:37:14 -04:00
source: source
2016-02-04 09:26:50 -05:00
user_id: user_id
2018-03-09 05:23:48 -05:00
} , project_id , " set-document " , callback
2014-02-12 05:23:40 -05:00
2017-08-07 09:45:04 -04:00
getProjectDocsIfMatch: ( project_id , projectStateHash , callback = (error, docs) -> ) ->
2017-08-09 11:00:11 -04:00
# If the project state hasn't changed, we can get all the latest
# docs from redis via the docupdater. Otherwise we will need to
# fall back to getting them from mongo.
2017-08-01 09:38:34 -04:00
timer = new metrics . Timer ( " get-project-docs " )
2017-10-11 10:50:21 -04:00
url = " #{ settings . apis . documentupdater . url } /project/ #{ project_id } /get_and_flush_if_old?state= #{ projectStateHash } "
2017-08-01 09:38:34 -04:00
logger . log project_id : project_id , " getting project docs from document updater "
2017-10-11 10:50:21 -04:00
request . post url , (error, res, body)->
2017-08-01 09:38:34 -04:00
timer . done ( )
if error ?
logger . error err : error , url : url , project_id : project_id , " error getting project docs from doc updater "
return callback ( error )
2017-08-09 11:00:11 -04:00
if res . statusCode is 409 # HTTP response code "409 Conflict"
# Docupdater has checked the projectStateHash and found that
# it has changed. This means that the docs currently in redis
# aren't the only change to the project and the full set of
# docs/files should be retreived from docstore/filestore
# instead.
2017-08-07 09:45:04 -04:00
return callback ( )
else if res . statusCode >= 200 and res . statusCode < 300
2017-08-01 09:38:34 -04:00
logger . log project_id : project_id , " got project docs from document document updater "
try
docs = JSON . parse ( body )
catch error
return callback ( error )
callback null , docs
else
logger . error project_id : project_id , url: url , " doc updater returned a non-success status code: #{ res . statusCode } "
callback new Error ( " doc updater returned a non-success status code: #{ res . statusCode } " )
2017-09-08 10:57:29 -04:00
clearProjectState: ( project_id , callback = (error) -> ) ->
logger . log project_id : project_id , " clearing project state from document updater "
2018-03-09 05:23:48 -05:00
DocumentUpdaterHandler . _makeRequest {
path: " /project/ #{ project_id } /clearState "
method: " POST "
} , project_id , " clear-project-state " , callback
2017-09-08 10:57:29 -04:00
2017-05-05 10:19:31 -04:00
acceptChanges: ( project_id , doc_id , change_ids = [ ] , callback = (error) -> ) ->
2018-03-09 05:23:48 -05:00
logger . log { project_id , doc_id } , " accepting #{ change_ids . length } changes "
DocumentUpdaterHandler . _makeRequest {
path: " /project/ #{ project_id } /doc/ #{ doc_id } /change/accept "
2017-05-04 10:33:47 -04:00
json:
change_ids: change_ids
2018-03-09 05:23:48 -05:00
method: " POST "
} , project_id , " accept-changes " , callback
2017-05-04 10:33:47 -04:00
2017-01-24 10:18:49 -05:00
deleteThread: ( project_id , doc_id , thread_id , callback = (error) -> ) ->
timer = new metrics . Timer ( " delete-thread " )
logger . log { project_id , doc_id , thread_id } , " deleting comment range in document updater "
2018-03-09 05:23:48 -05:00
DocumentUpdaterHandler . _makeRequest {
path: " /project/ #{ project_id } /doc/ #{ doc_id } /comment/ #{ thread_id } "
method: " DELETE "
} , project_id , " delete-thread " , callback
2017-01-24 10:18:49 -05:00
2018-04-11 05:08:15 -04:00
resyncProjectHistory: (project_id, projectHistoryId, docs, files, callback) ->
2018-03-09 06:05:17 -05:00
logger . info { project_id , docs , files } , " resyncing project history in doc updater "
2018-03-09 05:23:48 -05:00
DocumentUpdaterHandler . _makeRequest {
path: " /project/ #{ project_id } /history/resync "
2018-04-11 05:08:15 -04:00
json: { docs , files , projectHistoryId }
2018-03-09 05:23:48 -05:00
method: " POST "
} , project_id , " resync-project-history " , callback
2018-02-28 12:31:26 -05:00
2018-04-13 08:43:11 -04:00
updateProjectStructure: ( project_id , projectHistoryId , userId , changes , callback = (error) -> ) ->
2017-12-14 10:38:20 -05:00
return callback ( ) if ! settings . apis . project_history ? . sendProjectStructureOps
2018-03-09 05:23:48 -05:00
2018-02-27 06:48:51 -05:00
Project . findOne { _id: project_id } , { version : true } , (err, currentProject) ->
return callback ( err ) if err ?
return callback new Error ( " project not found " ) if ! currentProject ?
docUpdates = DocumentUpdaterHandler . _getUpdates ( ' doc ' , changes . oldDocs , changes . newDocs )
fileUpdates = DocumentUpdaterHandler . _getUpdates ( ' file ' , changes . oldFiles , changes . newFiles )
2019-03-06 04:15:14 -05:00
projectVersion = changes ? . newProject ? . version
2018-02-27 06:48:51 -05:00
return callback ( ) if ( docUpdates . length + fileUpdates . length ) < 1
2019-03-06 04:15:14 -05:00
# FIXME: remove this check and the request to get the project structure version above
# when we are confident in the use of $inc to increment the project structure version
# in all cases.
if projectVersion ? && currentProject . version == projectVersion
logger . log { project_id , projectVersion } , " got project version in changes "
else if projectVersion ? && currentProject . version != projectVersion
logger . error { project_id , changes , projectVersion , currentProject: currentProject . version } , " project version from db was different from changes (broken lock?) "
else
projectVersion = currentProject . version
logger . warn { project_id , changes , projectVersion } , " did not receive project version in changes "
2018-03-09 06:05:17 -05:00
logger . log { project_id } , " updating project structure in doc updater "
2018-03-09 05:23:48 -05:00
DocumentUpdaterHandler . _makeRequest {
path: " /project/ #{ project_id } "
2018-04-13 08:43:11 -04:00
json: {
docUpdates ,
fileUpdates ,
userId ,
2019-03-06 04:15:14 -05:00
version: projectVersion
2018-04-13 08:43:11 -04:00
projectHistoryId
}
2018-03-09 05:23:48 -05:00
method: " POST "
} , project_id , " update-project-structure " , callback
_makeRequest: (options, project_id, metricsKey, callback) ->
timer = new metrics . Timer ( metricsKey )
request {
url: " #{ settings . apis . documentupdater . url } #{ options . path } "
json: options . json
method: options . method || " GET "
} , (error, res, body)->
timer . done ( )
if error ?
logger . error { error , project_id } , " error making request to document updater "
callback error
else if res . statusCode >= 200 and res . statusCode < 300
callback null , body
else
error = new Error ( " document updater returned a failure status code: #{ res . statusCode } " )
logger . error { error , project_id } , " document updater returned failure status code: #{ res . statusCode } "
callback error
2017-11-01 13:54:58 -04:00
2017-12-14 08:06:27 -05:00
_getUpdates: (entityType, oldEntities, newEntities) ->
2017-11-30 10:46:37 -05:00
oldEntities || = [ ]
newEntities || = [ ]
2017-11-06 10:11:33 -05:00
updates = [ ]
2017-11-14 11:47:15 -05:00
oldEntitiesHash = _ . indexBy oldEntities , (entity) -> entity [ entityType ] . _id . toString ( )
newEntitiesHash = _ . indexBy newEntities , (entity) -> entity [ entityType ] . _id . toString ( )
2017-11-06 10:11:33 -05:00
2018-03-27 07:08:30 -04:00
# Send deletes before adds (and renames) to keep a 1:1 mapping between
# paths and ids
#
# When a file is replaced, we first delete the old file and then add the
# new file. If the 'add' operation is sent to project history before the
# 'delete' then we would have two files with the same path at that point
# in time.
for id , oldEntity of oldEntitiesHash
newEntity = newEntitiesHash [ id ]
if ! newEntity ?
# entity deleted
updates . push
id: id
pathname: oldEntity . path
newPathname: ' '
2017-11-14 11:47:15 -05:00
for id , newEntity of newEntitiesHash
oldEntity = oldEntitiesHash [ id ]
2017-11-10 10:47:12 -05:00
if ! oldEntity ?
2017-11-15 10:12:59 -05:00
# entity added
2017-11-10 10:47:12 -05:00
updates . push
id: id
pathname: newEntity . path
docLines: newEntity . docLines
url: newEntity . url
2017-11-15 10:12:59 -05:00
else if newEntity . path != oldEntity . path
# entity renamed
updates . push
id: id
pathname: oldEntity . path
newPathname: newEntity . path
2017-11-10 10:47:12 -05:00
2017-11-06 10:11:33 -05:00
updates
2014-02-12 05:23:40 -05:00
PENDINGUPDATESKEY = " PendingUpdates "
DOCLINESKEY = " doclines "
DOCIDSWITHPENDINGUPDATES = " DocsWithPendingUpdates "
keys =
pendingUpdates : (op) -> " #{ PENDINGUPDATESKEY } : #{ op . doc_id } "
docsWithPendingUpdates: DOCIDSWITHPENDINGUPDATES
docLines : (op) -> " #{ DOCLINESKEY } : #{ op . doc_id } "
combineProjectIdAndDocId: (project_id, doc_id) -> " #{ project_id } : #{ doc_id } "