Source: cli/refreshArbres.js


'use strict'
const flow = require('an-flow')
const taskLog = require('an-log')('sesatheque-cli')
const Ref = require('../../constructors/Ref')
const configRessource = require('../ressource/config')

function logErrorInDataAndTask (message, obj) {
  taskLog.error(message)
  log.dataError(message, obj)
}

/**
 * Rafraichit les datas de tous les arbres
 * @param {string} [oid]
 * @param {errorCallback} done
 */
function refreshArbres (oid, done) {
  function grab (next) {
    let nb = 0
    flow().seq(function () {
      taskLog(`traitement des arbres de ${offset} à ${offset + limit} sur ${nbArbres}`)
      EntityRessource.match('type').equals('arbre').sort('dateCreation').grab({ limit, offset }, this)
    }).seqEach(function (arbre) {
      nb++
      refreshOne(arbre, this)
    }).seq(function () {
      if (nb === limit) {
        offset += limit
        grab(next)
      } else {
        next()
      }
    }).catch(next)
  } // grab

  /**
   * Met éventuellement à jour les enfants de item
   * @private
   * @param {Ref} item l'arbre à vérifier
   * @param next appelé avec (error, itemClean, hasChanged)
   */
  function cleanEnfants (item, next) {
    if (item.enfants && item.enfants.length) {
      // boucle sur les enfants
      let hasChanged = false
      flow(item.enfants).seqEach(function (enfant) {
        const nextEnfant = this
        if (!enfant) {
          log.dataError(`enfant invalide dans ${item && (item.rid || item.aliasOf || item.titre)}`, item)
          return this()
        }
        process.nextTick(cleanEnfant, enfant, function (error, enfantCleaned, enfantsHadChanges) {
          if (error) return nextEnfant(error)
          if (enfantsHadChanges) hasChanged = true
          nextEnfant(null, enfantCleaned)
        })
      }).seq(function (enfants) {
        item.enfants = enfants.filter(e => e)
        next(null, item, hasChanged)
      }).catch(next)
    } else {
      next(null, item, false)
    }
  }

  /**
   * Vérifie s'il faut modifier ref
   * @private
   * @param {Ref} ref
   * @param next appelé avec (error, ref, hasChanged)
   */
  function cleanEnfant (ref, next) {
    let hasChanged = false
    // alias, on fetch et compare
    if (ref.aliasOf) {
      nbRessources++
      $ressourceFetch.fetchOriginal(ref.aliasOf, function (error, ressource) {
        if (error) {
          // si c'est une 404 on veut continuer
          if (/Aucune ressource/.test(error.message)) {
            logErrorInDataAndTask(`la ressource ${ref.aliasOf} n’existe plus dans l’arbre ${currentOid} (${currentTitre})`)
            hasChanged = true
            ref.titre = `Cette ressource n’existe plus (${ref.titre})`
            ref.type = 'error'
            hasChanged = true
            next(null, ref, hasChanged)
          } else {
            next(error)
          }
          // on arrête là
          return
        }
        // on a une ressource
        flow().seq(function () {
          const nextStep = this
          // on regarde si elle n'est pas obsolète
          if (!ressource.relations) return nextStep()
          const replacedByRelation = ressource.relations.find(r => r[0] === configRessource.constantes.relations.estRemplacePar)
          if (!replacedByRelation) return nextStep()
          // y'a un remplaçant désigné, on le cherche
          $ressourceFetch.fetchOriginal(replacedByRelation[1], function (error, ressource) {
            // si y'a un remplaçant c'est fini
            if (!error && ressource) return next(null, new Ref(ressource), true)
            // sinon on continue avec la ressource initiale
            nextStep()
          })
        }).seq(function () {
          ['titre', 'resume', 'description', 'commentaires', 'inc', 'cle'].forEach(p => {
            if (ref[p] !== ressource[p]) {
              hasChanged = true
              ref[p] = ressource[p]
            }
          })
          // public ?
          const isPublic = ressource.publie && !ressource.restriction
          if (ref.public !== isPublic) {
            ref.public = isPublic
            hasChanged = true
          }
          if (ref.type === 'arbre' && ref.enfants && ref.enfants.length) {
            logErrorInDataAndTask(`item arbre avec aliasOf ${ref.aliasOf} et enfants, on vire les enfants pour rendre le chargement dynamique, dans l’arbre ${currentOid} (${currentTitre})`)
            hasChanged = true
            delete ref.enfants
          }
          next(null, ref, hasChanged)
        }).catch(next)
      })

    // error, on fait suivre tel quel
    } else if (ref.type === 'error') {
      next(null, ref, hasChanged)

    // dossier
    } else if (ref.type === 'arbre') {
      // on nettoie les enfants
      if (ref.enfants && ref.enfants.length) {
        process.nextTick(cleanEnfants, ref, function (error, refCleaned, needSaveRef) {
          if (error) return next(error)
          if (needSaveRef) hasChanged = true
          next(null, refCleaned, hasChanged)
        })
      // si vide, on fait suivre tel quel
      } else {
        next(null, ref, hasChanged)
      }

    // cas inconnu, on laisse en le signalant
    } else {
      logErrorInDataAndTask(new Error('enfant sans aliasOf, ni error ni arbre'), ref)
      next(null, ref, hasChanged)
    }
  }

  function refreshOne (arbre, next) {
    currentOid = arbre.oid
    currentTitre = arbre.titre
    if (arbre.enfants && arbre.enfants.length) {
      process.nextTick(function () {
        cleanEnfants(arbre, function (error, arbreCleaned, needSave) {
          if (error) return next(error)
          if (needSave) {
            nbArbresModif++
            $ressourceRepository.save(arbreCleaned, next)
          } else {
            next()
          }
        })
      })
    } else {
      logErrorInDataAndTask(`arbre ${arbre.oid} sans enfants (${arbre.titre})`)
      next()
    }
  }

  if (typeof oid === 'function') {
    done = oid
    oid = undefined
  }
  if (typeof done !== 'function') throw new Error('Erreur interne, pas de callback de commande')
  let offset = 0
  const limit = 10
  let nbArbres = 0
  let nbArbresModif = 0
  let nbRessources = 0
  let currentOid, currentTitre
  const EntityRessource = lassi.service('EntityRessource')
  const $ressourceRepository = lassi.service('$ressourceRepository')
  const $ressourceFetch = lassi.service('$ressourceFetch')
  if (oid) {
    flow().seq(function () {
      EntityRessource.match('oid').equals(oid).grabOne(this)
    }).seq(function (arbre) {
      if (!arbre) {
        taskLog(`L’arbre ${oid} n’existe pas`)
        return done()
      }
      taskLog(`Starting refreshArbres ${oid}`)
      refreshOne(arbre, this)
    }).seq(function () {
      taskLog(`fin du rafraichissement de l’arbre ${oid} (contenant ${nbRessources} ressources), il ${nbArbresModif ? 'a' : 'n’a pas'} été modifié.`)
      done()
    }).catch(done)
  } else {
    flow().seq(function () {
      EntityRessource.match('type').equals('arbre').count(this)
    }).seq(function (nb) {
      nbArbres = nb
      taskLog(`Starting refreshArbres avec ${nb} arbres`)
      grab(this)
    }).seq(function () {
      taskLog(`fin du rafraichissement de ${nbArbres} arbres (contenant ${nbRessources} ressources), dont ${nbArbresModif} modifiés`)
      done()
    }).catch(done)
  }
}

refreshArbres.help = function refreshArbresHelp () {
  taskLog('La commande refreshArbres prend un oid en argument pour mettre à jour l’arbre, sans argument elle lance le rafraîchissement des données de tous les arbres')
}

module.exports = {
  refreshArbres
}