PackManager.coffee 21 KB

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