Completed
Push — master ( 976119...75ac8d )
by greg
17s
created

src/cli/core/manager/Manager.js (2 issues)

Check try statements for empty catch clauses.

Best Practice Coding Style Comprehensibility Minor
1
//import Handlebars from 'handlebars'
2
import fse from 'fs-extra'
3
import events from 'events'
4
import path from 'path'
5
import watch from 'watch'
6
import express from 'express'
7
import bodyParser from 'body-parser'
8
import tinylr from 'tiny-lr'
9
import clc from 'cli-color'
10
import {
11
  coreUtils,
12
  cmsData,
13
  config,
14
  cmsTemplates,
15
  cmsReference,
16
  cmsMedia,
17
  cmsOperations,
18
  abeExtend
19
} from '../../'
20
21
let singleton = Symbol()
22
let singletonEnforcer = Symbol()
23
24
class Manager {
25
  constructor(enforcer) {
26
    if (enforcer != singletonEnforcer) throw 'Cannot construct Json singleton'
27
  }
28
29
  static get instance() {
30
    if (!this[singleton]) {
31
      this[singleton] = new Manager(singletonEnforcer)
32
    }
33
    return this[singleton]
34
  }
35
36
  init() {
37
    this._processesRunning = {}
38
    this.events = {}
39
40
    // Compatibility for abe version < 3.2.
41
    // It's possible to reference html from any directory
42
    if (process.env.ABE_TEMPLATES_PATH) {
43
      this.pathTemplates = path.join(
44
        config.root,
45
        process.env.ABE_TEMPLATES_PATH
46
      )
47
      this.pathPartials = path.join(this.pathTemplates, 'partials')
48
    } else if (
49
      config.themes != null &&
50
      coreUtils.file.exist(
51
        path.join(
52
          config.root,
53
          config.themes.path,
54
          config.themes.name,
55
          config.themes.templates.path
56
        )
57
      )
58
    ) {
59
      this.pathTemplates = path.join(
60
        config.root,
61
        config.themes.path,
62
        config.themes.name,
63
        config.themes.templates.path
64
      )
65
      this.pathPartials = path.join(
66
        config.root,
67
        config.themes.path,
68
        config.themes.name,
69
        config.themes.partials.path
70
      )
71
    } else {
72
      // did the users of the versions < 3.2 overriden the default templates path or partials path ?
73
      if (config.templates != null && config.templates.url != null) {
74
        this.pathTemplates = path.join(config.root, config.templates.url)
75
      } else {
76
        this.pathTemplates = path.join(config.root, 'templates')
77
      }
78
      if (config.partials != null) {
79
        this.pathPartials = path.join(config.root, config.partials)
80
      } else {
81
        this.pathPartials = path.join(config.root, 'partials')
82
      }
83
    }
84
85
    this.pathAssets = this.pathTemplates
86
    if (process.env.ABE_ASSETS_PATH) {
87
      this.pathAssets = path.join(
88
        config.root,
89
        process.env.ABE_ASSETS_PATH
90
      )
91
    }
92
    this.pathPublish = path.join(config.root, config.publish.url)
93
    if (process.env.ABE_DESTINATION_PATH) {
94
      this.pathPublish = path.join(
95
        config.root,
96
        process.env.ABE_DESTINATION_PATH
97
      )
98
    }
99
100
    // config.data.url was the config prior to v3.7.0
101
    if (process.env.ABE_JSON_PATH) {
102
      this.pathData = path.join(
103
        config.root,
104
        process.env.ABE_JSON_PATH
105
      )
106
    } else if (config.data.url != null) {
107
      this.pathData = path.join(config.root, config.data.url)
108
    } else if (coreUtils.file.exist(path.join(config.root, config.data.path))) {
109
      this.pathData = path.join(config.root, config.data.path)
110
    } else {
111
      this.pathData = path.join(config.root, 'data')
112
    }
113
114
    this.pathTemplates = this.pathTemplates.replace(/\/$/, "")
115
    this.pathPartials = this.pathPartials.replace(/\/$/, "")
116
    this.pathAssets = this.pathAssets.replace(/\/$/, "")
117
    this.pathPublish = this.pathPublish.replace(/\/$/, "")
118
    this.pathData = this.pathData.replace(/\/$/, "");
119
    this.pathScripts = path.join(config.root, config.scripts.path)
120
    this.pathStructure = path.join(config.root, config.structure.url)
121
    this.pathReference = path.join(config.root, config.reference.url)
122
    this.pathLocales = path.join(config.root, 'locales')
123
124
    this.connections = []
125
    this.activities = []
126
    this._watcherActivity()
127
128
    this.updateStructureAndTemplates()
129
130
    var p = new Promise(resolve => {
131
      this.getKeysFromSelect()
132
        .then(
133
          () => {
134
            resolve()
135
          },
136
          e => {
137
            console.log('Manager.init', e)
138
            resolve()
139
          }
140
        )
141
        .catch(e => {
142
          console.log('Manager.init', e)
143
        })
144
    })
145
146
    return p
147
  }
148
149
  initDev() {
150
    console.log(`You are in ${process.env.NODE_ENV} mode`)
151
    console.log(
152
      'which means every change in your themes (templates, partials and assets), reference and structure folders will dynamically update your site'
153
    )
154
    console.log("In production, this mode shouldn't be used.")
155
156
    this._watchersStart()
157
158
    var lport = process.env.LIVERELOAD_PORT || 35729
159
    this.lserver = express()
160
161
    // Launching a Livereload server
162
    this.lserver
163
      .use(bodyParser.json())
164
      .use(bodyParser.urlencoded({extended: true}))
165
      .use(tinylr.middleware({app: this.lserver}))
166
      .listen(lport, function() {
167
        console.log('Livereload listening on %d', lport)
168
      })
169
      .on('error', function(err) {
170
        if (err.code == 'EADDRINUSE') {
171
          console.error(
172
            clc.red("can't start the Abe's watch server\n"),
173
            `This watch server has tried to listen on the port ${lport} but this server is already in use by another process...`
174
          )
175
        } else {
176
          console.error(err)
177
        }
178
      })
179
180
    process.on('SIGINT', () => {
181
      try {
182
        this.lserver.close(() => {
183
          console.log('SIGINT:Livereload server has been gracefully terminated')
184
          process.exit(0)
185
        })
186
      } catch (error) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
187
    })
188
    process.on('SIGTERM', () => {
189
      try {
190
        this.lserver.close(() => {
191
          console.log(
192
            'SIGTERM:Livereload server has been gracefully terminated'
193
          )
194
          process.exit(0)
195
        })
196
      } catch (error) {}
0 ignored issues
show
Coding Style Comprehensibility Best Practice introduced by
Empty catch clauses should be used with caution; consider adding a comment why this is needed.
Loading history...
197
    })
198
199
    // sync assets from templates to /site
200
    cmsTemplates.assets.copy()
201
  }
202
203
  _watcherActivity() {
204
    this.events.activity = new events.EventEmitter(0)
205
206
    this.events.activity.on(
207
      'activity',
208
      function(data) {
209
        if (data.user && data.user.username) {
210
          data.user = data.user.username
211
        } else {
212
          data.user = 'admin'
213
        }
214
        this.addActivity(data)
215
        this.events.activity.emit('activity-stream', data)
216
      }.bind(this)
217
    )
218
  }
219
220
  _watchersStart() {
221
    this.events.template = new events.EventEmitter(0)
222
    this.events.structure = new events.EventEmitter(0)
223
    this.events.reference = new events.EventEmitter(0)
224
    this.events.scripts = new events.EventEmitter(0)
225
    this.events.locales = new events.EventEmitter(0)
226
227
    // watch template folder
228
    try {
229
      fse.accessSync(this.pathTemplates, fse.F_OK)
230
      this._watchTemplateFolder = watch.createMonitor(
231
        this.pathTemplates,
232
        monitor => {
233
          monitor.on('created', (f, stat) => {
234
            if (f.indexOf(`.${config.files.templates.extension}`) < 0) {
235
              cmsTemplates.assets.copy()
236
              tinylr.changed(f)
237
              console.log(
238
                'Assets have been synchronized after this creation: ' + f
239
              )
240
            } else {
241
              this.getKeysFromSelect()
242
              this.updateStructureAndTemplates()
243
              if (typeof this.lserver != 'undefined') {
244
                tinylr.changed(f)
245
              }
246
              this.events.template.emit('update')
247
            }
248
          })
249
          monitor.on('changed', (f, curr, prev) => {
250
            if (f.indexOf(`.${config.files.templates.extension}`) < 0) {
251
              cmsTemplates.assets.copy()
252
              if (typeof this.lserver != 'undefined') {
253
                tinylr.changed(f)
254
              }
255
              console.log(
256
                'Assets have been synchronized after this modification: ' + f
257
              )
258
            } else {
259
              this.getKeysFromSelect()
260
              this.updateStructureAndTemplates()
261
              if (typeof this.lserver != 'undefined') {
262
                tinylr.changed(f)
263
              }
264
              this.events.template.emit('update')
265
            }
266
          })
267
          monitor.on('removed', (f, stat) => {
268
            if (f.indexOf(`.${config.files.templates.extension}`) < 0) {
269
              cmsTemplates.assets.copy()
270
              if (typeof this.lserver != 'undefined') {
271
                tinylr.changed(f)
272
              }
273
              console.log(
274
                'Assets have been synchronized after this deletion: ' + f
275
              )
276
            } else {
277
              this.getKeysFromSelect()
278
              this.updateStructureAndTemplates()
279
              if (typeof this.lserver != 'undefined') {
280
                tinylr.changed(f)
281
              }
282
              this.events.template.emit('update')
283
            }
284
          })
285
        }
286
      )
287
    } catch (e) {
288
      console.log('the directory ' + this.pathTemplates + ' does not exist')
289
    }
290
291
    // watch partial folder
292
    try {
293
      fse.accessSync(this.pathPartials, fse.F_OK)
294
      this._watchPartialsFolder = watch.createMonitor(
295
        this.pathPartials,
296
        monitor => {
297
          monitor.on('created', f => {
298
            this.getKeysFromSelect()
299
            this.updateStructureAndTemplates()
300
            if (typeof this.lserver != 'undefined') {
301
              tinylr.changed(f)
302
            }
303
            this.events.template.emit('update')
304
          })
305
          monitor.on('changed', f => {
306
            this.getKeysFromSelect()
307
            this.updateStructureAndTemplates()
308
            if (typeof this.lserver != 'undefined') {
309
              tinylr.changed(f)
310
            }
311
            this.events.template.emit('update')
312
          })
313
          monitor.on('removed', f => {
314
            this.getKeysFromSelect()
315
            this.updateStructureAndTemplates()
316
            if (typeof this.lserver != 'undefined') {
317
              tinylr.changed(f)
318
            }
319
            this.events.template.emit('update')
320
          })
321
        }
322
      )
323
    } catch (e) {
324
      console.log('the directory ' + this.pathPartials + ' does not exist')
325
    }
326
327
    try {
328
      fse.accessSync(this.pathStructure, fse.F_OK)
329
      this._watchStructure = watch.createMonitor(
330
        this.pathStructure,
331
        monitor => {
332
          monitor.on('created', () => {
333
            this.updateStructureAndTemplates()
334
            this.events.structure.emit('update')
335
          })
336
          monitor.on('changed', () => {
337
            this.updateStructureAndTemplates()
338
            this.events.structure.emit('update')
339
          })
340
          monitor.on('removed', () => {
341
            this.updateStructureAndTemplates()
342
            this.events.structure.emit('update')
343
          })
344
        }
345
      )
346
    } catch (e) {
347
      console.log('the directory ' + this.pathStructure + ' does not exist')
348
    }
349
350
    try {
351
      fse.accessSync(this.pathReference, fse.F_OK)
352
      this._watchReferenceFolder = watch.createMonitor(
353
        this.pathReference,
354
        monitor => {
355
          monitor.on('created', f => {
356
            this.updateReferences(f)
357
            if (typeof this.lserver != 'undefined') {
358
              tinylr.changed(f)
359
            }
360
            this.events.reference.emit('update')
361
          })
362
          monitor.on('changed', f => {
363
            this.updateReferences(f)
364
            if (typeof this.lserver != 'undefined') {
365
              tinylr.changed(f)
366
            }
367
            this.events.reference.emit('update')
368
          })
369
          monitor.on('removed', f => {
370
            this.updateReferences()
371
            if (typeof this.lserver != 'undefined') {
372
              tinylr.changed(f)
373
            }
374
            this.events.reference.emit('update')
375
          })
376
        }
377
      )
378
    } catch (e) {
379
      console.log('the directory ' + this.pathReference + ' does not exist')
380
    }
381
382
    try {
383
      fse.accessSync(this.pathLocales, fse.F_OK)
384
      this._watchLocalesFolder = watch.createMonitor(
385
        this.pathLocales,
386
        monitor => {
387
          monitor.on('created', f => {
388
            coreUtils.locales.instance.reloadLocales()
389
            if (typeof this.lserver != 'undefined') {
390
              tinylr.changed(f)
391
            }
392
            this.events.locales.emit('update')
393
          })
394
          monitor.on('changed', f => {
395
            coreUtils.locales.instance.reloadLocales()
396
            if (typeof this.lserver != 'undefined') {
397
              tinylr.changed(f)
398
            }
399
            this.events.locales.emit('update')
400
          })
401
          monitor.on('removed', f => {
402
            coreUtils.locales.instance.reloadLocales()
403
            if (typeof this.lserver != 'undefined') {
404
              tinylr.changed(f)
405
            }
406
            this.events.locales.emit('update')
407
          })
408
        }
409
      )
410
    } catch (e) {
411
      console.log('the directory ' + this.pathLocales + ' does not exist')
412
    }
413
414
    try {
415
      fse.accessSync(this.pathScripts, fse.F_OK)
416
      this._watchScripts = watch.createMonitor(this.pathScripts, monitor => {
417
        monitor.on('created', () => {
418
          abeExtend.plugins.instance.updateScripts()
419
          this.events.scripts.emit('update')
420
        })
421
        monitor.on('changed', () => {
422
          abeExtend.plugins.instance.updateScripts()
423
          this.events.scripts.emit('update')
424
        })
425
        monitor.on('removed', () => {
426
          abeExtend.plugins.instance.updateScripts()
427
          this.events.scripts.emit('update')
428
        })
429
      })
430
    } catch (e) {
431
      console.log('the directory ' + this.pathScripts + ' does not exist')
432
    }
433
  }
434
435
  getKeysFromSelect() {
436
    this._whereKeys = []
437
    var p = new Promise(resolve => {
438
      cmsTemplates.template
439
        .getTemplatesAndPartials(this.pathTemplates, this.pathPartials)
440
        .then(
441
          templatesList => {
442
            return cmsTemplates.template
443
              .getTemplatesTexts(templatesList)
444
              .then(
445
                templatesText => {
446
                  return cmsTemplates.template
447
                    .getAbeRequestWhereKeysFromTemplates(templatesText)
448
                    .then(
449
                      whereKeys => {
450
                        this._whereKeys = whereKeys
451
                        this._slugs = cmsTemplates.template.getAbeSlugFromTemplates(
452
                          templatesText
453
                        )
454
                        this._precontribution = cmsTemplates.template.getAbePrecontribFromTemplates(
455
                          templatesText
456
                        )
457
                        this.updateList()
458
                        resolve()
459
                      },
460
                      e => {
461
                        console.log(
462
                          'Reject: Manager.findRequestColumns',
463
                          e.stack
464
                        )
465
                      }
466
                    )
467
                    .catch(e => {
468
                      console.log('Error: Manager.findRequestColumns', e.stack)
469
                    })
470
                },
471
                e => {
472
                  console.log('Reject: Manager.getTemplatesTexts', e.stack)
473
                }
474
              )
475
              .catch(e => {
476
                console.log('Error: Manager.getTemplatesTexts', e.stack)
477
              })
478
          },
479
          e => {
480
            console.log('Manager.getKeysFromSelect', e.stack)
481
          }
482
        )
483
        .catch(e => {
484
          console.log('Manager.getKeysFromSelect', e.stack)
485
        })
486
    })
487
488
    return p
489
  }
490
491
  getStructureAndTemplates() {
492
    return this._structureAndTemplates
493
  }
494
495
  updateStructureAndTemplates() {
496
    this._structureAndTemplates = cmsTemplates.template.getStructureAndTemplates()
497
  }
498
499
  getThumbsList() {
500
    if (typeof this._thumbs === 'undefined' || this._thumbs === null)
501
      this._thumbs = cmsMedia.image.getThumbsList()
502
    return this._thumbs
503
  }
504
505
  addThumbsToList(thumb) {
506
    if (this._thumbs) this._thumbs.push(thumb)
507
  }
508
509
  getReferences() {
510
    if (typeof this._references === 'undefined' || this._references === null)
511
      this.updateReferences()
512
    return this._references
513
  }
514
515
  getPrecontribution() {
516
    return this._precontribution
517
  }
518
519
  getSlugs() {
520
    return this._slugs
521
  }
522
523
  updateReferences(referenceName) {
524
    var references = cmsReference.reference.getFiles()
525
    if (referenceName && references[referenceName])
526
      this._references[referenceName] = references[referenceName]
527
    else this._references = references
528
529
    return this
530
  }
531
532
  getList() {
533
    return this._list
534
  }
535
536
  getListWithStatusOnFolder(status, folder = '') {
537
    var list = []
538
    folder = path.join(Manager.instance.pathData, folder)
539
    Array.prototype.forEach.call(this._list, file => {
540
      if (
541
        typeof file[status] !== 'undefined' &&
542
        file[status] !== null &&
543
        file.path.indexOf(folder) > -1
544
      ) {
545
        list.push(file)
546
      }
547
    })
548
549
    return list
550
  }
551
552
  setList(list) {
553
    this._list = list
554
555
    return this
556
  }
557
558
  /**
559
   * return true if postUrl is found in the Manager list
560
   * @param {String} postUrl The url path of the file
561
   */
562
  postExist(postUrl) {
563
    const docPath = cmsData.utils.getDocPathFromPostUrl(postUrl)
564
    const found = coreUtils.array.find(this._list, 'path', docPath)
565
566
    if (found.length > 0) {
567
      return true
568
    }
569
    return false
570
  }
571
572
  /**
573
   * When a post is modified or created, this method is called so that the manager updates the list with the updated/new item
574
   * @param {String} revisionPath The full path to the post
575
   */
576
  updatePostInList(revisionPath) {
577
    const docPath = cmsData.utils.getDocPath(revisionPath)
578
    const found = coreUtils.array.find(this._list, 'path', docPath)
579
    const json = cmsData.file.get(revisionPath)
580
    let index
581
    let merged = {}
582
    let revision = cmsData.file.getFileObject(revisionPath)
583
    revision = cmsData.file.getAbeMeta(revision, json)
584
    Array.prototype.forEach.call(this._whereKeys, key => {
585
      var keyFirst = key.split('.')[0]
586
      revision[keyFirst] = json[keyFirst]
587
    })
588
589
    if (found.length > 0) {
590
      index = found[0]
591
      merged[docPath] = this._list[index]
592
      // If I publish, I remove the previous published versions
593
      if (revision.abe_meta.status === 'publish') {
594
        Array.prototype.forEach.call(
595
          merged[docPath].revisions,
596
          (revision, revIndex) => {
597
            if (revision.abe_meta.status === 'publish') {
598
              merged[docPath].revisions.splice(revIndex, 1)
599
            }
600
          }
601
        )
602
      }
603
      merged[docPath].revisions.push(JSON.parse(JSON.stringify(revision)))
604
      const sortedResult = cmsData.revision.sortRevisions(merged)
605
      // Does the publish version has been removed (in the case of unpublish) ?
606
      if (
607
        sortedResult[0]['publish'] &&
608
        !coreUtils.file.exist(sortedResult[0]['publish']['path'])
609
      ) {
610
        delete sortedResult[0]['publish']
611
      }
612
      this._list[index] = sortedResult[0]
613
    } else {
614
      index = this._list.length
615
      let rev = []
616
      rev.push(revision)
617
      merged = cmsData.revision.mergeRevisions(rev)
618
      const sortedResult = cmsData.revision.sortRevisions(merged)
619
      this._list.push(sortedResult[0])
620
    }
621
622
    this._list.sort(coreUtils.sort.predicatBy('date', -1))
623
    this.historize(index)
624
  }
625
626
  /**
627
   * When data.history is set, we do keep the number of revisions = history in the most recent date order
628
   * @param  {[type]} index [description]
629
   * @return {[type]}       [description]
630
   */
631
  historize(index) {
632
    if (config.data.history && config.data.history > 0) {
633
      let arStatus = []
634
      if (this._list[index].revisions.length > config.data.history) {
635
        Array.prototype.forEach.call(
636
          this._list[index].revisions,
637
          (revision, revIndex) => {
638
            if (revIndex >= config.data.history) {
639
              if (revision.abe_meta.status === 'publish') {
640
                if (arStatus.indexOf(revision.abe_meta.status) < 0) {
641
                  arStatus.push(revision.abe_meta.status)
642
                } else {
643
                  this._list[index].revisions.splice(revIndex, 1)
644
                }
645
              } else {
646
                this._list[index].revisions.splice(revIndex, 1)
647
                cmsOperations.remove.removeFile(revision.path)
648
              }
649
            } else if (arStatus.indexOf(revision.abe_meta.status) < 0) {
650
              arStatus.push(revision.abe_meta.status)
651
            }
652
          }
653
        )
654
      }
655
    }
656
  }
657
658
  /**
659
   * When a post is deleted, this method is called so that the manager updates the list with the removed item
660
   * @param {String} postUrl The URL of the post
661
   */
662
  removePostFromList(postUrl) {
663
    const docPath = cmsData.utils.getDocPathFromPostUrl(postUrl)
664
    this._list = coreUtils.array.removeByAttr(this._list, 'path', docPath)
665
  }
666
667
  /**
668
   * This method loads all posts into an array with values used in "select" statements from templates
669
   * + abe_meta data
670
   * @return {Array} Array of objects representing the posts including their revisions
671
   */
672
  updateList() {
673
    this._list = cmsData.file.getAllWithKeys(this._whereKeys)
674
    this._list.sort(coreUtils.sort.predicatBy('date', -1))
675
    console.log('Manager updated')
676
  }
677
678
  getPage(
679
    start = 0,
680
    length = 20,
681
    sortField = 'date',
682
    sortDir = -1,
683
    search = '',
684
    searchFields = ['abe_meta.link', 'abe_meta.template', 'name']
685
  ) {
686
    const total = this._list.length
687
    let totalFiltered = total
688
    let list = this._list.slice()
689
690
    if (search !== '') {
691
      const searches = search.split(' ')
692
      for (var i = 0; i < searches.length; i++) {
693
        list = coreUtils.array.facet(list, searchFields, searches[i])
694
      }
695
696
      totalFiltered = list.length
697
    }
698
699
    if (sortField != 'date' || sortDir != -1) {
700
      list.sort(coreUtils.sort.predicatBy(sortField, sortDir))
701
    }
702
703
    list = list.slice(start, start + length)
704
705
    return {
706
      recordsTotal: total,
707
      recordsFiltered: totalFiltered,
708
      data: list
709
    }
710
  }
711
712
  addProcess(name) {
713
    this._processesRunning[name] = true
714
  }
715
716
  removeProcess(name) {
717
    delete this._processesRunning[name]
718
  }
719
720
  isProcessRunning(name) {
721
    if (
722
      this._processesRunning[name] !== null &&
723
      this._processesRunning[name] === true
724
    ) {
725
      return true
726
    } else {
727
      return false
728
    }
729
  }
730
731
  getActivities() {
732
    return this.activities
733
  }
734
735
  addActivity(activity) {
736
    if (this.activities.length >= 50) this.activities.shift()
737
738
    this.activities.push(activity)
739
  }
740
741
  getConnections() {
742
    return this.connections
743
  }
744
745
  addConnection(res) {
746
    this.connections.push(res)
747
  }
748
749
  removeConnection(res) {
750
    var i = this.connections.indexOf(res)
751
    if (i !== -1) {
752
      this.connections.splice(i, 1)
753
    }
754
  }
755
}
756
757
export default Manager
758