PackManager.coffee 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  1. async = require "async"
  2. _ = require "underscore"
  3. {db, ObjectId, BSON} = require "./mongojs"
  4. logger = require "logger-sharelatex"
  5. LockManager = require "./LockManager"
  6. MongoAWS = require "./MongoAWS"
  7. ProjectIterator = require "./ProjectIterator"
  8. # Sharejs operations are stored in a 'pack' object
  9. #
  10. # e.g. a single sharejs update looks like
  11. #
  12. # {
  13. # "doc_id" : 549dae9e0a2a615c0c7f0c98,
  14. # "project_id" : 549dae9c0a2a615c0c7f0c8c,
  15. # "op" : [ {"p" : 6981, "d" : "?" } ],
  16. # "meta" : { "user_id" : 52933..., "start_ts" : 1422310693931, "end_ts" : 1422310693931 },
  17. # "v" : 17082
  18. # }
  19. #
  20. # and a pack looks like this
  21. #
  22. # {
  23. # "doc_id" : 549dae9e0a2a615c0c7f0c98,
  24. # "project_id" : 549dae9c0a2a615c0c7f0c8c,
  25. # "pack" : [ U1, U2, U3, ...., UN],
  26. # "meta" : { "user_id" : 52933..., "start_ts" : 1422310693931, "end_ts" : 1422310693931 },
  27. # "v" : 17082
  28. # "v_end" : ...
  29. # }
  30. #
  31. # where U1, U2, U3, .... are single updates stripped of their
  32. # doc_id and project_id fields (which are the same for all the
  33. # updates in the pack).
  34. #
  35. # The pack itself has v and meta fields, this makes it possible to
  36. # treat packs and single updates in a similar way.
  37. #
  38. # The v field of the pack itself is from the first entry U1, the
  39. # v_end field from UN. The meta.end_ts field of the pack itself is
  40. # from the last entry UN, the meta.start_ts field from U1.
  41. DAYS = 24 * 3600 * 1000 # one day in milliseconds
  42. module.exports = PackManager =
  43. MAX_SIZE: 1024*1024 # make these configurable parameters
  44. MAX_COUNT: 1024
  45. insertCompressedUpdates: (project_id, doc_id, lastUpdate, newUpdates, temporary, callback = (error) ->) ->
  46. return callback() if newUpdates.length == 0
  47. updatesToFlush = []
  48. updatesRemaining = newUpdates.slice()
  49. n = lastUpdate?.n || 0
  50. sz = lastUpdate?.sz || 0
  51. while updatesRemaining.length and n < PackManager.MAX_COUNT and sz < PackManager.MAX_SIZE
  52. nextUpdate = updatesRemaining[0]
  53. nextUpdateSize = BSON.calculateObjectSize(nextUpdate)
  54. if nextUpdateSize + sz > PackManager.MAX_SIZE and n > 0
  55. break
  56. n++
  57. sz += nextUpdateSize
  58. updatesToFlush.push updatesRemaining.shift()
  59. PackManager.flushCompressedUpdates project_id, doc_id, lastUpdate, updatesToFlush, temporary, (error) ->
  60. return callback(error) if error?
  61. PackManager.insertCompressedUpdates project_id, doc_id, null, updatesRemaining, temporary, callback
  62. flushCompressedUpdates: (project_id, doc_id, lastUpdate, newUpdates, temporary, callback = (error) ->) ->
  63. return callback() if newUpdates.length == 0
  64. if lastUpdate? and not (temporary and ((Date.now() - lastUpdate.meta?.start_ts) > 1 * DAYS))
  65. PackManager.appendUpdatesToExistingPack project_id, doc_id, lastUpdate, newUpdates, temporary, callback
  66. else
  67. PackManager.insertUpdatesIntoNewPack project_id, doc_id, newUpdates, temporary, callback
  68. insertUpdatesIntoNewPack: (project_id, doc_id, newUpdates, temporary, callback = (error) ->) ->
  69. first = newUpdates[0]
  70. last = newUpdates[newUpdates.length - 1]
  71. n = newUpdates.length
  72. sz = BSON.calculateObjectSize(newUpdates)
  73. newPack =
  74. project_id: ObjectId(project_id.toString())
  75. doc_id: ObjectId(doc_id.toString())
  76. pack: newUpdates
  77. n: n
  78. sz: sz
  79. meta:
  80. start_ts: first.meta.start_ts
  81. end_ts: last.meta.end_ts
  82. v: first.v
  83. v_end: last.v
  84. temporary: temporary
  85. if temporary
  86. newPack.expiresAt = new Date(Date.now() + 7 * DAYS)
  87. logger.log {project_id, doc_id, newUpdates}, "inserting updates into new pack"
  88. db.docHistory.save newPack, (err, result) ->
  89. return callback(err) if err?
  90. if temporary
  91. return callback()
  92. else
  93. PackManager.updateIndex project_id, doc_id, callback
  94. appendUpdatesToExistingPack: (project_id, doc_id, lastUpdate, newUpdates, temporary, callback = (error) ->) ->
  95. first = newUpdates[0]
  96. last = newUpdates[newUpdates.length - 1]
  97. n = newUpdates.length
  98. sz = BSON.calculateObjectSize(newUpdates)
  99. query =
  100. _id: lastUpdate._id
  101. project_id: ObjectId(project_id.toString())
  102. doc_id: ObjectId(doc_id.toString())
  103. pack: {$exists: true}
  104. update =
  105. $push:
  106. "pack": {$each: newUpdates}
  107. $inc:
  108. "n": n
  109. "sz": sz
  110. $set:
  111. "meta.end_ts": last.meta.end_ts
  112. "v_end": last.v
  113. if lastUpdate.expiresAt and temporary
  114. update.$set.expiresAt = new Date(Date.now() + 7 * DAYS)
  115. logger.log {project_id, doc_id, lastUpdate, newUpdates}, "appending updates to existing pack"
  116. db.docHistory.findAndModify {query, update, new:true, fields:{meta:1,v_end:1}}, callback
  117. # Retrieve all changes for a document
  118. getOpsByVersionRange: (project_id, doc_id, fromVersion, toVersion, callback = (error, updates) ->) ->
  119. PackManager.loadPacksByVersionRange project_id, doc_id, fromVersion, toVersion, (error) ->
  120. query = {doc_id:ObjectId(doc_id.toString())}
  121. query.v = {$lte:toVersion} if toVersion?
  122. query.v_end = {$gte:fromVersion} if fromVersion?
  123. #console.log "query:", query
  124. db.docHistory.find(query).sort {v:-1}, (err, result) ->
  125. return callback(err) if err?
  126. #console.log "getOpsByVersionRange:", err, result
  127. updates = []
  128. opInRange = (op, from, to) ->
  129. return false if fromVersion? and op.v < fromVersion
  130. return false if toVersion? and op.v > toVersion
  131. return true
  132. for docHistory in result
  133. #console.log 'adding', docHistory.pack
  134. for op in docHistory.pack.reverse() when opInRange(op, fromVersion, toVersion)
  135. op.project_id = docHistory.project_id
  136. op.doc_id = docHistory.doc_id
  137. #console.log "added op", op.v, fromVersion, toVersion
  138. updates.push op
  139. callback(null, updates)
  140. loadPacksByVersionRange: (project_id, doc_id, fromVersion, toVersion, callback) ->
  141. PackManager.getIndex doc_id, (err, indexResult) ->
  142. return callback(err) if err?
  143. indexPacks = indexResult?.packs or []
  144. packInRange = (pack, from, to) ->
  145. return false if fromVersion? and pack.v_end < fromVersion
  146. return false if toVersion? and pack.v > toVersion
  147. return true
  148. neededIds = (pack._id for pack in indexPacks when packInRange(pack, fromVersion, toVersion))
  149. if neededIds.length
  150. PackManager.fetchPacksIfNeeded project_id, doc_id, neededIds, callback
  151. else
  152. callback()
  153. fetchPacksIfNeeded: (project_id, doc_id, pack_ids, callback) ->
  154. db.docHistory.find {_id: {$in: (ObjectId(id) for id in pack_ids)}}, {_id:1}, (err, loadedPacks) ->
  155. return callback(err) if err?
  156. allPackIds = (id.toString() for id in pack_ids)
  157. loadedPackIds = (pack._id.toString() for pack in loadedPacks)
  158. packIdsToFetch = _.difference allPackIds, loadedPackIds
  159. logger.log {project_id, doc_id, loadedPackIds, allPackIds, packIdsToFetch}, "analysed packs"
  160. return callback() if packIdsToFetch.length is 0
  161. async.eachLimit packIdsToFetch, 4, (pack_id, cb) ->
  162. MongoAWS.unArchivePack project_id, doc_id, pack_id, cb
  163. , (err) ->
  164. return callback(err) if err?
  165. logger.log {project_id, doc_id}, "done unarchiving"
  166. callback()
  167. # Retrieve all changes across a project
  168. makeProjectIterator: (project_id, before, callback) ->
  169. # get all the docHistory Entries
  170. db.docHistory.find({project_id: ObjectId(project_id)},{pack:false}).sort {"meta.end_ts":-1}, (err, packs) ->
  171. return callback(err) if err?
  172. allPacks = []
  173. seenIds = {}
  174. for pack in packs
  175. allPacks.push pack
  176. seenIds[pack._id] = true
  177. db.docHistoryIndex.find {project_id: ObjectId(project_id)}, (err, indexes) ->
  178. return callback(err) if err?
  179. for index in indexes
  180. for pack in index.packs when not seenIds[pack._id]
  181. pack.project_id = index.project_id
  182. pack.doc_id = index._id
  183. pack.fromIndex = true
  184. allPacks.push pack
  185. seenIds[pack._id] = true
  186. callback(null, new ProjectIterator(allPacks, before, PackManager.getPackById))
  187. getPackById: (project_id, doc_id, pack_id, callback) ->
  188. db.docHistory.findOne {_id: pack_id}, (err, pack) ->
  189. return callback(err) if err?
  190. if not pack?
  191. MongoAWS.unArchivePack project_id, doc_id, pack_id, callback
  192. else if pack.expiresAt? and not pack.temporary and false # TODO: remove false, temporarily disabled
  193. # we only need to touch the TTL on the listing of changes in the project
  194. # because diffs on individual documents are always done after that
  195. PackManager.increaseTTL pack, callback
  196. # only do this for cached packs, not temporary ones to avoid older packs
  197. # being kept longer than newer ones (which messes up the last update version)
  198. else
  199. callback(null, pack)
  200. increaseTTL: (pack, callback) ->
  201. if pack.expiresAt < new Date(Date.now() + 6 * DAYS)
  202. # update cache expiry since we are using this pack
  203. db.docHistory.findAndModify {
  204. query: {_id: pack._id}
  205. update: {$set: {expiresAt: new Date(Date.now() + 7 * DAYS)}}
  206. }, (err) ->
  207. return callback(err, pack)
  208. else
  209. callback(null, pack)
  210. # Manage docHistoryIndex collection
  211. getIndex: (doc_id, callback) ->
  212. db.docHistoryIndex.findOne {_id:ObjectId(doc_id.toString())}, callback
  213. getPackFromIndex: (doc_id, pack_id, callback) ->
  214. db.docHistoryIndex.findOne {_id:ObjectId(doc_id.toString()), "packs._id": pack_id}, {"packs.$":1}, callback
  215. getLastPackFromIndex: (doc_id, callback) ->
  216. db.docHistoryIndex.findOne {_id: ObjectId(doc_id.toString())}, {packs:{$slice:-1}}, (err, indexPack) ->
  217. return callback(err) if err?
  218. return callback() if not indexPack?
  219. callback(null,indexPack[0])
  220. getIndexWithKeys: (doc_id, callback) ->
  221. PackManager.getIndex doc_id, (err, index) ->
  222. return callback(err) if err?
  223. return callback() if not index?
  224. for pack in index?.packs or []
  225. index[pack._id] = pack
  226. callback(null, index)
  227. initialiseIndex: (project_id, doc_id, callback) ->
  228. PackManager.findCompletedPacks project_id, doc_id, (err, packs) ->
  229. #console.log 'err', err, 'packs', packs, packs?.length
  230. return callback(err) if err?
  231. return callback() if not packs?
  232. PackManager.insertPacksIntoIndexWithLock project_id, doc_id, packs, callback
  233. updateIndex: (project_id, doc_id, callback) ->
  234. # find all packs prior to current pack
  235. PackManager.findUnindexedPacks project_id, doc_id, (err, newPacks) ->
  236. return callback(err) if err?
  237. return callback() if not newPacks? or newPacks.length is 0
  238. PackManager.insertPacksIntoIndexWithLock project_id, doc_id, newPacks, (err) ->
  239. return callback(err) if err?
  240. logger.log {project_id, doc_id, newPacks}, "added new packs to index"
  241. callback()
  242. findCompletedPacks: (project_id, doc_id, callback) ->
  243. query = { doc_id: ObjectId(doc_id.toString()), expiresAt: {$exists:false} }
  244. db.docHistory.find(query, {pack:false}).sort {v:1}, (err, packs) ->
  245. return callback(err) if err?
  246. return callback() if not packs?
  247. return callback() if not packs?.length
  248. last = packs.pop() # discard the last pack, if it's still in progress
  249. packs.push(last) if last.finalised # it's finalised so we push it back to archive it
  250. callback(null, packs)
  251. findPacks: (project_id, doc_id, callback) ->
  252. query = { doc_id: ObjectId(doc_id.toString()), expiresAt: {$exists:false} }
  253. db.docHistory.find(query, {pack:false}).sort {v:1}, (err, packs) ->
  254. return callback(err) if err?
  255. return callback() if not packs?
  256. return callback() if not packs?.length
  257. callback(null, packs)
  258. findUnindexedPacks: (project_id, doc_id, callback) ->
  259. PackManager.getIndexWithKeys doc_id, (err, indexResult) ->
  260. return callback(err) if err?
  261. PackManager.findCompletedPacks project_id, doc_id, (err, historyPacks) ->
  262. return callback(err) if err?
  263. return callback() if not historyPacks?
  264. # select only the new packs not already in the index
  265. newPacks = (pack for pack in historyPacks when not indexResult?[pack._id]?)
  266. newPacks = (_.omit(pack, 'doc_id', 'project_id', 'n', 'sz') for pack in newPacks)
  267. if newPacks.length
  268. logger.log {project_id, doc_id, n: newPacks.length}, "found new packs"
  269. callback(null, newPacks)
  270. insertPacksIntoIndexWithLock: (project_id, doc_id, newPacks, callback) ->
  271. LockManager.runWithLock(
  272. "HistoryIndexLock:#{doc_id}",
  273. (releaseLock) ->
  274. PackManager._insertPacksIntoIndex project_id, doc_id, newPacks, releaseLock
  275. callback
  276. )
  277. _insertPacksIntoIndex: (project_id, doc_id, newPacks, callback) ->
  278. db.docHistoryIndex.findAndModify {
  279. query: {_id:ObjectId(doc_id.toString())}
  280. update:
  281. $setOnInsert: project_id: ObjectId(project_id.toString())
  282. $push:
  283. packs: {$each: newPacks, $sort: {v: 1}}
  284. upsert: true
  285. }, callback
  286. # Archiving packs to S3
  287. archivePack: (project_id, doc_id, pack_id, callback) ->
  288. clearFlagOnError = (err, cb) ->
  289. if err? # clear the inS3 flag on error
  290. PackManager.clearPackAsArchiveInProgress project_id, doc_id, pack_id, (err2) ->
  291. return cb(err2) if err2?
  292. return cb(err)
  293. else
  294. cb()
  295. async.series [
  296. (cb) ->
  297. PackManager.checkArchiveNotInProgress project_id, doc_id, pack_id, cb
  298. (cb) ->
  299. PackManager.markPackAsArchiveInProgress project_id, doc_id, pack_id, cb
  300. (cb) ->
  301. MongoAWS.archivePack project_id, doc_id, pack_id, (err) ->
  302. clearFlagOnError(err, cb)
  303. (cb) ->
  304. PackManager.checkArchivedPack project_id, doc_id, pack_id, (err) ->
  305. clearFlagOnError(err, cb)
  306. (cb) ->
  307. PackManager.markPackAsArchived project_id, doc_id, pack_id, cb
  308. (cb) ->
  309. PackManager.setTTLOnArchivedPack project_id, doc_id, pack_id, callback
  310. ], callback
  311. checkArchivedPack: (project_id, doc_id, pack_id, callback) ->
  312. db.docHistory.findOne {_id: pack_id}, (err, pack) ->
  313. return callback(err) if err?
  314. return callback new Error("pack not found") if not pack?
  315. MongoAWS.readArchivedPack project_id, doc_id, pack_id, (err, result) ->
  316. delete result.last_checked
  317. delete pack.last_checked
  318. # need to compare ids as ObjectIds with .equals()
  319. for key in ['_id', 'project_id', 'doc_id']
  320. result[key] = pack[key] if result[key].equals(pack[key])
  321. for op, i in result.pack
  322. op._id = pack.pack[i]._id if op._id? and op._id.equals(pack.pack[i]._id)
  323. if _.isEqual pack, result
  324. callback()
  325. else
  326. logger.err {pack, result, jsondiff: JSON.stringify(pack) is JSON.stringify(result)}, "difference when comparing packs"
  327. callback new Error("pack retrieved from s3 does not match pack in mongo")
  328. # Extra methods to test archive/unarchive for a doc_id
  329. pushOldPacks: (project_id, doc_id, callback) ->
  330. PackManager.findPacks project_id, doc_id, (err, packs) ->
  331. return callback(err) if err?
  332. return callback() if not packs?.length
  333. PackManager.processOldPack project_id, doc_id, packs[0]._id, callback
  334. pullOldPacks: (project_id, doc_id, callback) ->
  335. PackManager.loadPacksByVersionRange project_id, doc_id, null, null, callback
  336. # Processing old packs via worker
  337. processOldPack: (project_id, doc_id, pack_id, callback) ->
  338. markAsChecked = (err) ->
  339. PackManager.markPackAsChecked project_id, doc_id, pack_id, (err2) ->
  340. return callback(err2) if err2?
  341. callback(err)
  342. logger.log {project_id, doc_id}, "processing old packs"
  343. db.docHistory.findOne {_id:pack_id}, (err, pack) ->
  344. return markAsChecked(err) if err?
  345. return markAsChecked() if not pack?
  346. return callback() if pack.expiresAt? # return directly
  347. PackManager.finaliseIfNeeded project_id, doc_id, pack._id, pack, (err) ->
  348. return markAsChecked(err) if err?
  349. PackManager.updateIndexIfNeeded project_id, doc_id, (err) ->
  350. return markAsChecked(err) if err?
  351. PackManager.findUnarchivedPacks project_id, doc_id, (err, unarchivedPacks) ->
  352. return markAsChecked(err) if err?
  353. if not unarchivedPacks?.length
  354. logger.log {project_id, doc_id}, "no packs need archiving"
  355. return markAsChecked()
  356. async.eachSeries unarchivedPacks, (pack, cb) ->
  357. PackManager.archivePack project_id, doc_id, pack._id, cb
  358. , (err) ->
  359. return markAsChecked(err) if err?
  360. logger.log {project_id, doc_id}, "done processing"
  361. markAsChecked()
  362. finaliseIfNeeded: (project_id, doc_id, pack_id, pack, callback) ->
  363. sz = pack.sz / (1024 * 1024) # in fractions of a megabyte
  364. n = pack.n / 1024 # in fraction of 1024 ops
  365. age = (Date.now() - pack.meta.end_ts) / DAYS
  366. if age < 30 # always keep if less than 1 month old
  367. logger.log {project_id, doc_id, pack_id, age}, "less than 30 days old"
  368. return callback()
  369. # compute an archiving threshold which decreases for each month of age
  370. archive_threshold = 30 / age
  371. if sz > archive_threshold or n > archive_threshold or age > 90
  372. logger.log {project_id, doc_id, pack_id, age, archive_threshold, sz, n}, "meets archive threshold"
  373. PackManager.markPackAsFinalisedWithLock project_id, doc_id, pack_id, callback
  374. else
  375. logger.log {project_id, doc_id, pack_id, age, archive_threshold, sz, n}, "does not meet archive threshold"
  376. callback()
  377. markPackAsFinalisedWithLock: (project_id, doc_id, pack_id, callback) ->
  378. LockManager.runWithLock(
  379. "HistoryLock:#{doc_id}",
  380. (releaseLock) ->
  381. PackManager._markPackAsFinalised project_id, doc_id, pack_id, releaseLock
  382. callback
  383. )
  384. _markPackAsFinalised: (project_id, doc_id, pack_id, callback) ->
  385. logger.log {project_id, doc_id, pack_id}, "marking pack as finalised"
  386. db.docHistory.findAndModify {
  387. query: {_id: pack_id}
  388. update: {$set: {finalised: true}}
  389. }, callback
  390. updateIndexIfNeeded: (project_id, doc_id, callback) ->
  391. logger.log {project_id, doc_id}, "archiving old packs"
  392. PackManager.getIndexWithKeys doc_id, (err, index) ->
  393. return callback(err) if err?
  394. if not index?
  395. PackManager.initialiseIndex project_id, doc_id, callback
  396. else
  397. PackManager.updateIndex project_id, doc_id, callback
  398. markPackAsChecked: (project_id, doc_id, pack_id, callback) ->
  399. logger.log {project_id, doc_id, pack_id}, "marking pack as checked"
  400. db.docHistory.findAndModify {
  401. query: {_id: pack_id}
  402. update: {$currentDate: {"last_checked":true}}
  403. }, callback
  404. findUnarchivedPacks: (project_id, doc_id, callback) ->
  405. PackManager.getIndex doc_id, (err, indexResult) ->
  406. return callback(err) if err?
  407. indexPacks = indexResult?.packs or []
  408. unArchivedPacks = (pack for pack in indexPacks when not pack.inS3?)
  409. if unArchivedPacks.length
  410. logger.log {project_id, doc_id, n: unArchivedPacks.length}, "find unarchived packs"
  411. callback(null, unArchivedPacks)
  412. # Archive locking flags
  413. checkArchiveNotInProgress: (project_id, doc_id, pack_id, callback) ->
  414. logger.log {project_id, doc_id, pack_id}, "checking if archive in progress"
  415. PackManager.getPackFromIndex doc_id, pack_id, (err, result) ->
  416. return callback(err) if err?
  417. return callback new Error("pack not found in index") if not result?
  418. if result.inS3
  419. return callback new Error("pack archiving already done")
  420. else if result.inS3?
  421. return callback new Error("pack archiving already in progress")
  422. else
  423. return callback()
  424. markPackAsArchiveInProgress: (project_id, doc_id, pack_id, callback) ->
  425. logger.log {project_id, doc_id}, "marking pack as archive in progress status"
  426. db.docHistoryIndex.findAndModify {
  427. query: {_id:ObjectId(doc_id.toString()), packs: {$elemMatch: {"_id": pack_id, inS3: {$exists:false}}}}
  428. fields: { "packs.$": 1 }
  429. update: {$set: {"packs.$.inS3":false}}
  430. }, (err, result) ->
  431. return callback(err) if err?
  432. return callback new Error("archive is already in progress") if not result?
  433. logger.log {project_id, doc_id, pack_id}, "marked as archive in progress"
  434. callback()
  435. clearPackAsArchiveInProgress: (project_id, doc_id, pack_id, callback) ->
  436. logger.log {project_id, doc_id, pack_id}, "clearing as archive in progress"
  437. db.docHistoryIndex.findAndModify {
  438. query: {_id:ObjectId(doc_id.toString()), "packs" : {$elemMatch: {"_id": pack_id, inS3: false}}}
  439. fields: { "packs.$": 1 }
  440. update: {$unset: {"packs.$.inS3":true}}
  441. }, callback
  442. markPackAsArchived: (project_id, doc_id, pack_id, callback) ->
  443. logger.log {project_id, doc_id, pack_id}, "marking pack as archived"
  444. db.docHistoryIndex.findAndModify {
  445. query: {_id:ObjectId(doc_id.toString()), "packs" : {$elemMatch: {"_id": pack_id, inS3: false}}}
  446. fields: { "packs.$": 1 }
  447. update: {$set: {"packs.$.inS3":true}}
  448. }, (err, result) ->
  449. return callback(err) if err?
  450. return callback new Error("archive is not marked as progress") if not result?
  451. logger.log {project_id, doc_id, pack_id}, "marked as archived"
  452. callback()
  453. setTTLOnArchivedPack: (project_id, doc_id, pack_id, callback) ->
  454. db.docHistory.findAndModify {
  455. query: {_id: pack_id}
  456. update: {$set: {expiresAt: new Date(Date.now() + 1*DAYS)}}
  457. }, (err) ->
  458. logger.log {project_id, doc_id, pack_id}, "set expiry on pack"
  459. callback()