Software zum Installieren eines Smart-Mirror Frameworks , zum Nutzen von hochschulrelevanten Informationen, auf einem Raspberry-Pi.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.js 8.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. 'use strict'
  2. const uuid = require('uuid/v4')
  3. const archy = require('archy')
  4. const libCoverage = require('istanbul-lib-coverage')
  5. const {dirname, resolve} = require('path')
  6. const {promisify} = require('util')
  7. /* Shallow clone so we can promisify in-place */
  8. const fs = { ...require('fs') }
  9. const {spawn} = require('cross-spawn')
  10. const rimraf = promisify(require('rimraf'))
  11. const pMap = require('p-map')
  12. const _nodes = Symbol('nodes')
  13. const _label = Symbol('label')
  14. const _coverageMap = Symbol('coverageMap')
  15. const _processInfoDirectory = Symbol('processInfo.directory')
  16. // shared symbol for testing
  17. const _spawnArgs = Symbol.for('spawnArgs')
  18. ;['writeFile', 'readFile', 'readdir'].forEach(fn => {
  19. fs[fn] = promisify(fs[fn])
  20. })
  21. // the enumerable fields
  22. const defaults = () => ({
  23. parent: null,
  24. pid: process.pid,
  25. argv: process.argv,
  26. execArgv: process.execArgv,
  27. cwd: process.cwd(),
  28. time: Date.now(),
  29. ppid: process.ppid,
  30. coverageFilename: null,
  31. externalId: '',
  32. [_nodes]: [],
  33. [_label]: null,
  34. [_coverageMap]: null
  35. })
  36. /* istanbul ignore next */
  37. const fromEntries = Object.fromEntries || (
  38. entries => entries.reduce((obj, [name, value]) => {
  39. obj[name] = value
  40. return obj
  41. }, {})
  42. )
  43. class ProcessInfo {
  44. constructor (fields = {}) {
  45. Object.assign(this, defaults(), fields)
  46. if (!this.uuid) {
  47. this.uuid = uuid()
  48. }
  49. }
  50. get nodes () {
  51. return this[_nodes]
  52. }
  53. set nodes (n) {
  54. this[_nodes] = n
  55. }
  56. set directory (d) {
  57. this[_processInfoDirectory] = resolve(d)
  58. }
  59. get directory () {
  60. return this[_processInfoDirectory]
  61. }
  62. saveSync () {
  63. const f = resolve(this.directory, this.uuid + '.json')
  64. fs.writeFileSync(f, JSON.stringify(this), 'utf-8')
  65. }
  66. async save () {
  67. const f = resolve(this.directory, this.uuid + '.json')
  68. await fs.writeFile(f, JSON.stringify(this), 'utf-8')
  69. }
  70. async getCoverageMap (nyc) {
  71. if (this[_coverageMap]) {
  72. return this[_coverageMap]
  73. }
  74. const childMaps = await Promise.all(this.nodes.map(child => child.getCoverageMap(nyc)))
  75. this[_coverageMap] = await mapMerger(nyc, this.coverageFilename, childMaps)
  76. return this[_coverageMap]
  77. }
  78. get label () {
  79. if (this[_label]) {
  80. return this[_label]
  81. }
  82. const covInfo = this[_coverageMap]
  83. ? '\n ' + this[_coverageMap].getCoverageSummary().lines.pct + ' % Lines'
  84. : ''
  85. return this[_label] = this.argv.join(' ') + covInfo
  86. }
  87. }
  88. const mapMerger = async (nyc, filename, maps) => {
  89. const map = libCoverage.createCoverageMap({})
  90. if (filename) {
  91. map.merge(await nyc.coverageFileLoad(filename))
  92. }
  93. maps.forEach(otherMap => map.merge(otherMap))
  94. return map
  95. }
  96. // Operations on the processinfo database as a whole,
  97. // and the root of the tree rendering operation.
  98. class ProcessDB {
  99. constructor (directory) {
  100. if (!directory) {
  101. const nycConfig = process.env.NYC_CONFIG;
  102. if (nycConfig) {
  103. directory = resolve(JSON.parse(nycConfig).tempDir, 'processinfo')
  104. }
  105. if (!directory) {
  106. throw new TypeError('must provide directory argument when outside of NYC')
  107. }
  108. }
  109. Object.defineProperty(this, 'directory', { get: () => directory, enumerable: true })
  110. this.nodes = []
  111. this[_label] = null
  112. this[_coverageMap] = null
  113. }
  114. get label () {
  115. if (this[_label]) {
  116. return this[_label]
  117. }
  118. const covInfo = this[_coverageMap]
  119. ? '\n ' + this[_coverageMap].getCoverageSummary().lines.pct + ' % Lines'
  120. : ''
  121. return this[_label] = 'nyc' + covInfo
  122. }
  123. async getCoverageMap (nyc) {
  124. if (this[_coverageMap]) {
  125. return this[_coverageMap]
  126. }
  127. const childMaps = await Promise.all(this.nodes.map(child => child.getCoverageMap(nyc)))
  128. this[_coverageMap] = await mapMerger(nyc, undefined, childMaps)
  129. return this[_coverageMap]
  130. }
  131. async renderTree (nyc) {
  132. await this.buildProcessTree()
  133. await this.getCoverageMap(nyc)
  134. return archy(this)
  135. }
  136. async buildProcessTree () {
  137. const infos = await this.readProcessInfos(this.directory)
  138. const index = await this.readIndex()
  139. for (const id in index.processes) {
  140. const node = infos[id]
  141. if (!node) {
  142. throw new Error(`Invalid entry in processinfo index: ${id}`)
  143. }
  144. const idx = index.processes[id]
  145. node.nodes = idx.children.map(id => infos[id])
  146. .sort((a, b) => a.time - b.time)
  147. if (!node.parent) {
  148. this.nodes.push(node)
  149. }
  150. }
  151. }
  152. async _readJSON (file) {
  153. if (Array.isArray(file)) {
  154. const result = await pMap(
  155. file,
  156. f => this._readJSON(f),
  157. { concurrency: 8 }
  158. )
  159. return result.filter(Boolean)
  160. }
  161. try {
  162. return JSON.parse(await fs.readFile(resolve(this.directory, file), 'utf-8'))
  163. } catch (error) {
  164. }
  165. }
  166. async readProcessInfos () {
  167. const files = await fs.readdir(this.directory)
  168. const fileData = await this._readJSON(files.filter(f => f !== 'index.json'))
  169. return fromEntries(fileData.map(info => [
  170. info.uuid,
  171. new ProcessInfo(info)
  172. ]))
  173. }
  174. _createIndex (infos) {
  175. const infoMap = fromEntries(infos.map(info => [
  176. info.uuid,
  177. Object.assign(info, {children: []})
  178. ]))
  179. // create all the parent-child links
  180. infos.forEach(info => {
  181. if (info.parent) {
  182. const parentInfo = infoMap[info.parent]
  183. if (parentInfo && !parentInfo.children.includes(info.uuid)) {
  184. parentInfo.children.push(info.uuid)
  185. }
  186. }
  187. })
  188. // figure out which files were touched by each process.
  189. const files = infos.reduce((files, info) => {
  190. info.files.forEach(f => {
  191. files[f] = files[f] || []
  192. if (!files[f].includes(info.uuid)) {
  193. files[f].push(info.uuid)
  194. }
  195. })
  196. return files
  197. }, {})
  198. const processes = fromEntries(infos.map(info => [
  199. info.uuid,
  200. {
  201. parent: info.parent,
  202. ...(info.externalId ? { externalId: info.externalId } : {}),
  203. children: Array.from(info.children)
  204. }
  205. ]))
  206. const eidList = new Set()
  207. const externalIds = fromEntries(infos.filter(info => info.externalId).map(info => {
  208. if (eidList.has(info.externalId)) {
  209. throw new Error(
  210. `External ID ${info.externalId} used by multiple processes`)
  211. }
  212. eidList.add(info.externalId)
  213. const children = Array.from(info.children)
  214. // flatten the descendant sets of all the externalId procs
  215. // push the next generation onto the list so we accumulate them all
  216. for (let i = 0; i < children.length; i++) {
  217. children.push(...processes[children[i]].children.filter(uuid => !children.includes(uuid)))
  218. }
  219. return [
  220. info.externalId,
  221. {
  222. root: info.uuid,
  223. children
  224. }
  225. ]
  226. }))
  227. return { processes, files, externalIds }
  228. }
  229. async writeIndex () {
  230. const {directory} = this
  231. const files = await fs.readdir(directory)
  232. const infos = await this._readJSON(files.filter(f => f !== 'index.json'))
  233. const index = this._createIndex(infos)
  234. const indexFile = resolve(directory, 'index.json')
  235. await fs.writeFile(indexFile, JSON.stringify(index))
  236. return index
  237. }
  238. async readIndex () {
  239. return await this._readJSON('index.json') || await this.writeIndex()
  240. }
  241. // delete all coverage and processinfo for a given process
  242. // Warning! Doing this makes the index out of date, so make sure
  243. // to update it when you're done!
  244. // Not multi-process safe, because it cannot be done atomically.
  245. async expunge (id) {
  246. const index = await this.readIndex()
  247. const entry = index.externalIds[id]
  248. if (!entry) {
  249. return
  250. }
  251. await pMap(
  252. [].concat(
  253. `${dirname(this.directory)}/${entry.root}.json`,
  254. `${this.directory}/${entry.root}.json`,
  255. ...entry.children.map(c => [
  256. `${dirname(this.directory)}/${c}.json`,
  257. `${this.directory}/${c}.json`
  258. ])
  259. ),
  260. f => rimraf(f),
  261. { concurrency: 8 }
  262. )
  263. }
  264. [_spawnArgs] (name, file, args, options) {
  265. if (!Array.isArray(args)) {
  266. options = args
  267. args = []
  268. }
  269. if (!options) {
  270. options = {}
  271. }
  272. if (!process.env.NYC_CONFIG) {
  273. const nyc = options.nyc || 'nyc'
  274. const nycArgs = options.nycArgs || []
  275. args = [...nycArgs, file, ...args]
  276. file = nyc
  277. }
  278. options.env = {
  279. ...(options.env || process.env),
  280. NYC_PROCESSINFO_EXTERNAL_ID: name
  281. }
  282. return [name, file, args, options]
  283. }
  284. // spawn an externally named process
  285. async spawn (...spawnArgs) {
  286. const [name, file, args, options] = this[_spawnArgs](...spawnArgs)
  287. await this.expunge(name)
  288. return spawn(file, args, options)
  289. }
  290. }
  291. exports.ProcessDB = ProcessDB
  292. exports.ProcessInfo = ProcessInfo