PackManager.coffee 21 KB

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