shared-data.js 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. // Subscriptions channels
  2. const channels = require('../channels')
  3. // Utils
  4. const { log } = require('../util/logger')
  5. const path = require('path')
  6. const fs = require('fs-extra')
  7. const { rcFolder } = require('../util/rcFolder')
  8. const stats = require('../util/stats')
  9. /**
  10. * @typedef SharedData
  11. * @prop {string} id
  12. * @prop {any} value
  13. * @prop {Date} updated
  14. * @prop {boolean} disk
  15. */
  16. const rootFolder = path.resolve(rcFolder, 'shared-data')
  17. fs.ensureDirSync(rootFolder)
  18. /** @type {Map<string, Map<string, SharedData>>} */
  19. const sharedData = new Map()
  20. const watchers = new Map()
  21. function get ({ id, projectId }, context) {
  22. const store = sharedData.get(projectId)
  23. if (!store) return null
  24. let data = store.get(id)
  25. if (data == null) {
  26. if (fs.existsSync(path.resolve(rootFolder, projectId, `${id}.json`))) {
  27. data = {
  28. id,
  29. updated: new Date(),
  30. disk: true
  31. }
  32. }
  33. }
  34. if (data && data.disk) {
  35. data.value = readOnDisk({ id, projectId }, context)
  36. }
  37. return data
  38. }
  39. async function readOnDisk ({ id, projectId }, context) {
  40. const file = path.resolve(rootFolder, projectId, `${id}.json`)
  41. if (await fs.exists(file)) {
  42. return fs.readJson(file)
  43. }
  44. return null
  45. }
  46. async function set ({ id, projectId, value, disk = false }, context) {
  47. if (disk) {
  48. await writeOnDisk({ id, projectId, value }, context)
  49. }
  50. let store = sharedData.get(projectId)
  51. if (!store) {
  52. store = new Map()
  53. sharedData.set(projectId, store)
  54. }
  55. store.set(id, {
  56. id,
  57. ...(disk ? {} : { value }),
  58. disk,
  59. updated: new Date()
  60. })
  61. const stat = stats.get(`shared-data_${projectId}`, id)
  62. stat.value = 0
  63. context.pubsub.publish(channels.SHARED_DATA_UPDATED, {
  64. sharedDataUpdated: { id, projectId, value }
  65. })
  66. const watchers = notify({ id, projectId, value }, context)
  67. setTimeout(() => log('SharedData set', id, projectId, value, `(${watchers.length} watchers, ${stat.value} subscriptions)`))
  68. return { id, value }
  69. }
  70. async function writeOnDisk ({ id, projectId, value }, context) {
  71. const projectFolder = path.resolve(rootFolder, projectId)
  72. await fs.ensureDir(projectFolder)
  73. await fs.writeJson(path.resolve(projectFolder, `${id}.json`), value)
  74. }
  75. async function remove ({ id, projectId }, context) {
  76. const store = sharedData.get(projectId)
  77. if (store) {
  78. const data = store.get(id)
  79. if (data && data.disk) {
  80. fs.remove(path.resolve(rootFolder, projectId, `${id}.json`))
  81. }
  82. store.delete(id)
  83. }
  84. context.pubsub.publish(channels.SHARED_DATA_UPDATED, {
  85. sharedDataUpdated: { id, projectId, value: undefined }
  86. })
  87. notify({ id, projectId, value: undefined }, context)
  88. log('SharedData remove', id, projectId)
  89. }
  90. function watch ({ id, projectId }, handler) {
  91. let store = watchers.get(projectId)
  92. if (!store) {
  93. store = new Map()
  94. watchers.set(projectId, store)
  95. }
  96. let handlers = store.get(id)
  97. if (!handlers) {
  98. handlers = []
  99. store.set(id, handlers)
  100. }
  101. handlers.push(handler)
  102. }
  103. function unwatch ({ id, projectId }, handler) {
  104. const store = watchers.get(projectId)
  105. if (!store) return
  106. const handlers = store.get(id)
  107. if (!handlers) return
  108. const index = handlers.indexOf(handler)
  109. if (index !== -1) handlers.splice(index, 1)
  110. }
  111. function unWatchAll ({ projectId }, context) {
  112. watchers.delete(projectId)
  113. }
  114. function notify ({ id, projectId, value }, context) {
  115. let handlers = watchers.get(projectId)
  116. if (handlers) {
  117. handlers = handlers.get(id)
  118. }
  119. if (handlers) {
  120. handlers.forEach(fn => fn(value, id))
  121. }
  122. return handlers || []
  123. }
  124. module.exports = {
  125. get,
  126. set,
  127. remove,
  128. watch,
  129. unwatch,
  130. unWatchAll
  131. }