MongoAWS.coffee 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. settings = require "settings-sharelatex"
  2. logger = require "logger-sharelatex"
  3. AWS = require 'aws-sdk'
  4. S3S = require 's3-streams'
  5. {db, ObjectId} = require "./mongojs"
  6. JSONStream = require "JSONStream"
  7. ReadlineStream = require "byline"
  8. zlib = require "zlib"
  9. Metrics = require "metrics-sharelatex"
  10. DAYS = 24 * 3600 * 1000 # one day in milliseconds
  11. createStream = (streamConstructor, project_id, doc_id, pack_id) ->
  12. AWS_CONFIG =
  13. accessKeyId: settings.trackchanges.s3.key
  14. secretAccessKey: settings.trackchanges.s3.secret
  15. endpoint: settings.trackchanges.s3.endpoint
  16. s3ForcePathStyle: settings.trackchanges.s3.pathStyle
  17. return streamConstructor new AWS.S3(AWS_CONFIG), {
  18. "Bucket": settings.trackchanges.stores.doc_history,
  19. "Key": project_id+"/changes-"+doc_id+"/pack-"+pack_id
  20. }
  21. module.exports = MongoAWS =
  22. archivePack: (project_id, doc_id, pack_id, _callback = (error) ->) ->
  23. callback = (args...) ->
  24. _callback(args...)
  25. _callback = () ->
  26. query = {
  27. _id: ObjectId(pack_id)
  28. doc_id: ObjectId(doc_id)
  29. }
  30. return callback new Error("invalid project id") if not project_id?
  31. return callback new Error("invalid doc id") if not doc_id?
  32. return callback new Error("invalid pack id") if not pack_id?
  33. logger.log {project_id, doc_id, pack_id}, "uploading data to s3"
  34. upload = createStream S3S.WriteStream, project_id, doc_id, pack_id
  35. db.docHistory.findOne query, (err, result) ->
  36. return callback(err) if err?
  37. return callback new Error("cannot find pack to send to s3") if not result?
  38. return callback new Error("refusing to send pack with TTL to s3") if result.expiresAt?
  39. uncompressedData = JSON.stringify(result)
  40. if uncompressedData.indexOf("\u0000") != -1
  41. error = new Error("null bytes found in upload")
  42. logger.error err: error, project_id: project_id, doc_id: doc_id, pack_id: pack_id, error.message
  43. return callback(error)
  44. zlib.gzip uncompressedData, (err, buf) ->
  45. logger.log {project_id, doc_id, pack_id, origSize: uncompressedData.length, newSize: buf.length}, "compressed pack"
  46. return callback(err) if err?
  47. upload.on 'error', (err) ->
  48. callback(err)
  49. upload.on 'finish', () ->
  50. Metrics.inc("archive-pack")
  51. logger.log {project_id, doc_id, pack_id}, "upload to s3 completed"
  52. callback(null)
  53. upload.write buf
  54. upload.end()
  55. readArchivedPack: (project_id, doc_id, pack_id, _callback = (error, result) ->) ->
  56. callback = (args...) ->
  57. _callback(args...)
  58. _callback = () ->
  59. return callback new Error("invalid project id") if not project_id?
  60. return callback new Error("invalid doc id") if not doc_id?
  61. return callback new Error("invalid pack id") if not pack_id?
  62. logger.log {project_id, doc_id, pack_id}, "downloading data from s3"
  63. download = createStream S3S.ReadStream, project_id, doc_id, pack_id
  64. inputStream = download
  65. .on 'open', (obj) ->
  66. return 1
  67. .on 'error', (err) ->
  68. callback(err)
  69. gunzip = zlib.createGunzip()
  70. gunzip.setEncoding('utf8')
  71. gunzip.on 'error', (err) ->
  72. logger.log {project_id, doc_id, pack_id, err}, "error uncompressing gzip stream"
  73. callback(err)
  74. outputStream = inputStream.pipe gunzip
  75. parts = []
  76. outputStream.on 'error', (err) ->
  77. return callback(err)
  78. outputStream.on 'end', () ->
  79. logger.log {project_id, doc_id, pack_id}, "download from s3 completed"
  80. try
  81. object = JSON.parse parts.join('')
  82. catch e
  83. return callback(e)
  84. object._id = ObjectId(object._id)
  85. object.doc_id = ObjectId(object.doc_id)
  86. object.project_id = ObjectId(object.project_id)
  87. for op in object.pack
  88. op._id = ObjectId(op._id) if op._id?
  89. callback null, object
  90. outputStream.on 'data', (data) ->
  91. parts.push data
  92. unArchivePack: (project_id, doc_id, pack_id, callback = (error) ->) ->
  93. MongoAWS.readArchivedPack project_id, doc_id, pack_id, (err, object) ->
  94. return callback(err) if err?
  95. Metrics.inc("unarchive-pack")
  96. # allow the object to expire, we can always retrieve it again
  97. object.expiresAt = new Date(Date.now() + 7 * DAYS)
  98. logger.log {project_id, doc_id, pack_id}, "inserting object from s3"
  99. db.docHistory.insert object, callback