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.

224 lines
6.5 KiB

  1. // ===========================================
  2. // Wiki.js - Background Agent
  3. // 1.0.0
  4. // Licensed under AGPLv3
  5. // ===========================================
  6. const path = require('path')
  7. const ROOTPATH = process.cwd()
  8. const SERVERPATH = path.join(ROOTPATH, 'server')
  9. global.ROOTPATH = ROOTPATH
  10. global.SERVERPATH = SERVERPATH
  11. const IS_DEBUG = process.env.NODE_ENV === 'development'
  12. let appconf = require('./libs/config')()
  13. global.appconfig = appconf.config
  14. global.appdata = appconf.data
  15. // ----------------------------------------
  16. // Load Winston
  17. // ----------------------------------------
  18. global.winston = require('./libs/logger')(IS_DEBUG, 'AGENT')
  19. // ----------------------------------------
  20. // Load global modules
  21. // ----------------------------------------
  22. winston.info('Background Agent is initializing...')
  23. global.db = require('./libs/db').init()
  24. global.upl = require('./libs/uploads-agent').init()
  25. global.git = require('./libs/git').init()
  26. global.entries = require('./libs/entries').init()
  27. global.lang = require('i18next')
  28. global.mark = require('./libs/markdown')
  29. // ----------------------------------------
  30. // Load modules
  31. // ----------------------------------------
  32. const moment = require('moment')
  33. const Promise = require('bluebird')
  34. const fs = Promise.promisifyAll(require('fs-extra'))
  35. const klaw = require('klaw')
  36. const Cron = require('cron').CronJob
  37. const i18nextBackend = require('i18next-node-fs-backend')
  38. const i18nextMw = require('i18next-express-middleware')
  39. const entryHelper = require('./helpers/entry')
  40. // ----------------------------------------
  41. // Localization Engine
  42. // ----------------------------------------
  43. lang
  44. .use(i18nextBackend)
  45. .use(i18nextMw.LanguageDetector)
  46. .init({
  47. load: 'languageOnly',
  48. ns: ['common', 'errors', 'git'],
  49. defaultNS: 'common',
  50. saveMissing: false,
  51. supportedLngs: ['en', 'fr'],
  52. preload: ['en', 'fr'],
  53. fallbackLng: 'en',
  54. backend: {
  55. loadPath: path.join(SERVERPATH, 'locales/{{lng}}/{{ns}}.json')
  56. }
  57. })
  58. // ----------------------------------------
  59. // Start Cron
  60. // ----------------------------------------
  61. let job
  62. let jobIsBusy = false
  63. let jobUplWatchStarted = false
  64. db.onReady.then(() => {
  65. return db.Entry.remove({})
  66. }).then(() => {
  67. job = new Cron({
  68. cronTime: '0 */5 * * * *',
  69. onTick: () => {
  70. // Make sure we don't start two concurrent jobs
  71. if (jobIsBusy) {
  72. winston.warn('Previous job has not completed gracefully or is still running! Skipping for now. (This is not normal, you should investigate)')
  73. return
  74. }
  75. winston.info('Running all jobs...')
  76. jobIsBusy = true
  77. // Prepare async job collector
  78. let jobs = []
  79. let repoPath = path.resolve(ROOTPATH, appconfig.paths.repo)
  80. let dataPath = path.resolve(ROOTPATH, appconfig.paths.data)
  81. let uploadsTempPath = path.join(dataPath, 'temp-upload')
  82. // ----------------------------------------
  83. // REGULAR JOBS
  84. // ----------------------------------------
  85. //* ****************************************
  86. // -> Sync with Git remote
  87. //* ****************************************
  88. jobs.push(git.resync().then(() => {
  89. // -> Stream all documents
  90. let cacheJobs = []
  91. let jobCbStreamDocsResolve = null
  92. let jobCbStreamDocs = new Promise((resolve, reject) => {
  93. jobCbStreamDocsResolve = resolve
  94. })
  95. klaw(repoPath).on('data', function (item) {
  96. if (path.extname(item.path) === '.md' && path.basename(item.path) !== 'README.md') {
  97. let entryPath = entryHelper.parsePath(entryHelper.getEntryPathFromFullPath(item.path))
  98. let cachePath = entryHelper.getCachePath(entryPath)
  99. // -> Purge outdated cache
  100. cacheJobs.push(
  101. fs.statAsync(cachePath).then((st) => {
  102. return moment(st.mtime).isBefore(item.stats.mtime) ? 'expired' : 'active'
  103. }).catch((err) => {
  104. return (err.code !== 'EEXIST') ? err : 'new'
  105. }).then((fileStatus) => {
  106. // -> Delete expired cache file
  107. if (fileStatus === 'expired') {
  108. return fs.unlinkAsync(cachePath).return(fileStatus)
  109. }
  110. return fileStatus
  111. }).then((fileStatus) => {
  112. // -> Update cache and search index
  113. if (fileStatus !== 'active') {
  114. return entries.updateCache(entryPath).then(entry => {
  115. process.send({
  116. action: 'searchAdd',
  117. content: entry
  118. })
  119. return true
  120. })
  121. }
  122. return true
  123. })
  124. )
  125. }
  126. }).on('end', () => {
  127. jobCbStreamDocsResolve(Promise.all(cacheJobs))
  128. })
  129. return jobCbStreamDocs
  130. }))
  131. //* ****************************************
  132. // -> Clear failed temporary upload files
  133. //* ****************************************
  134. jobs.push(
  135. fs.readdirAsync(uploadsTempPath).then((ls) => {
  136. let fifteenAgo = moment().subtract(15, 'minutes')
  137. return Promise.map(ls, (f) => {
  138. return fs.statAsync(path.join(uploadsTempPath, f)).then((s) => { return { filename: f, stat: s } })
  139. }).filter((s) => { return s.stat.isFile() }).then((arrFiles) => {
  140. return Promise.map(arrFiles, (f) => {
  141. if (moment(f.stat.ctime).isBefore(fifteenAgo, 'minute')) {
  142. return fs.unlinkAsync(path.join(uploadsTempPath, f.filename))
  143. } else {
  144. return true
  145. }
  146. })
  147. })
  148. })
  149. )
  150. // ----------------------------------------
  151. // Run
  152. // ----------------------------------------
  153. Promise.all(jobs).then(() => {
  154. winston.info('All jobs completed successfully! Going to sleep for now.')
  155. if (!jobUplWatchStarted) {
  156. jobUplWatchStarted = true
  157. upl.initialScan().then(() => {
  158. job.start()
  159. })
  160. }
  161. return true
  162. }).catch((err) => {
  163. winston.error('One or more jobs have failed: ', err)
  164. }).finally(() => {
  165. jobIsBusy = false
  166. })
  167. },
  168. start: false,
  169. timeZone: 'UTC',
  170. runOnInit: true
  171. })
  172. })
  173. // ----------------------------------------
  174. // Shutdown gracefully
  175. // ----------------------------------------
  176. process.on('disconnect', () => {
  177. winston.warn('Lost connection to main server. Exiting...')
  178. job.stop()
  179. process.exit()
  180. })
  181. process.on('exit', () => {
  182. job.stop()
  183. })