async function updateActiveSyncs(userID) {
  const thing = this; // For readability

  // No URLs? Just give the thing back.
  if (!thing.urls || !thing.urls.length)
    return thing;

  // No known syncs? We'll still fall through to the save/index operation at
  // the end.
  if (typeof thing.sync !== 'object')
    thing.sync = {};

  // Determine which external sources we need to contact. While one source
  // may give us updates for many fields, we obviously only want to contact
  // it once.
  let sources = [];
  for (let field in thing.sync) {
    if (thing.sync[field].active && thing.sync[field].source &&
      !sources.includes(thing.sync[field].source))
      sources.push(thing.sync[field].source);
  }

  //  Build array of lookup promises from currently used sources
  let p = [];

  // Keep track of all URLs we're contacting for convenience
  let allURLs = [];

  sources.forEach(source => {
    let adapter = adapters.getAdapterForSource(source);
    // Find all matching URLs in array (we might have, e.g., a URL for
    // a work and one for an edition, and need to contact both).
    let relevantURLs = thing.urls.filter(url => adapter.ask(url));
    allURLs = allURLs.concat(relevantURLs);
    // Add relevant lookups as individual elements to array, log errors
    p.push(...relevantURLs.map(url =>
      adapter
      .lookup(url)
      .catch(error => {
        debug.error(`Problem contacting adapter "${adapter.getSourceID()}" for URL ${url}.`);
        debug.error({ error });
      })
    ));
  });

  if (allURLs.length)
    debug.app(`Retrieving item metadata for ${thing.id} from the following URL(s):\n` +
      allURLs.join(', '));

  // Perform all lookups, then update thing from the results, which will be
  // returned in the same order as the sources array.
  const results = await Promise.all(p);

  // If the label has changed, we need to update the short identifier
  // (slug). This means a possible URL change! Redirects are put in
  // place automatically.
  let needSlugUpdate;

  // Get obj w/ key = source ID, value = reverse order array of
  // result.data objects
  let dataBySource = _organizeDataBySource(results);
  sources.forEach((source) => {
    for (let field in thing.sync) {
      // Only update if everything looks good: sync is active, source
      // ID matches, we may have new data
      if (thing.sync[field].active && thing.sync[field].source === source &&
        Array.isArray(dataBySource[source])) {
        // Earlier results get priority, i.e. need to be assigned last
        for (let d of dataBySource[source]) {
          if (d[field] !== undefined) {
            thing.sync[field].updated = new Date();
            this[field] = d[field];
            if (field == 'label')
              needSlugUpdate = true;
          }
        }
      }
    }
  });

  if (needSlugUpdate)
    await thing.updateSlug(userID, thing.originalLanguage || 'en');

  await thing.save();

  // Index update can keep running after we resolve this promise, hence no "await"
  search.indexThing(thing);

  return thing;


  /**
   * Put valid data from results array into an object with sourceID as
   * the key and data as a reverse-order array. There may be multiple
   * URLs from one source, assigning value to the same field. URLs earlier
   * in the original thing.urls array take priority, so we have to ensure
   * they come last.
   *
   * @param {Object[]} results
   *  results from multiple adapters
   * @returns {Object}
   *  key = source, value = array of data objects
   * @memberof Thing
   * @inner
   * @protected
   */
  function _organizeDataBySource(results) {
    let rv = {};
    results.forEach(result => {
      // Correctly formatted result object with all relevant information
      if (typeof result == 'object' && typeof result.sourceID == 'string' &&
        typeof result.data == 'object') {
        // Initialize array if needed
        if (!Array.isArray(rv[result.sourceID]))
          rv[result.sourceID] = [];

        // See above on why this array is in reverse order
        rv[result.sourceID].unshift(result.data);
      }
    });
    return rv;
  }

}
/**
 * Fetch new external data for all fields set to be synchronized. Saves.
 * - Does not create a new revision, so if you need one, create it first.
 * - Performs search index update.
 * - Resolves with updated thing object.
 * - May result in a slug update, so if initiated by a user, should be passed the
 *   user ID. Otherwise, any slug changes will be without attribution
 *
 * @param {String} userID
 *  the user to associate with any slug changes
 * @returns {Thing}
 *  the updated thing
 * @memberof Thing
 * @instance
 */
Fetch new external data for all fields set to be synchronized. Saves.
- Does not create a new revision, so if you need one, create it first.
- Performs search index update.
- Resolves with updated thing object.
- May result in a slug update, so if initiated by a user, should be passed the
user ID. Otherwise, any slug changes will be without attribution

@param {String} userID
the user to associate with any slug changes
@returns {Thing}
the updated thing
@memberof Thing
@instance
Fetch new external data for all fields set to be synchronized . Saves . - Does not create a new revision so if you need one create it first . - Performs search index update . - Resolves with updated thing object . - May result in a slug update so if initiated by a user should be passed the user ID . Otherwise any slug changes will be without attribution @param { String } userID the user to associate with any slug changes @returns { Thing } the updated thing @memberof Thing @instance
async function updateActiveSyncs(userID) {
  const thing = this; 
  if (!thing.urls || !thing.urls.length)
    return thing;
  if (typeof thing.sync !== 'object')
    thing.sync = {};
  let sources = [];
  for (let field in thing.sync) {
    if (thing.sync[field].active && thing.sync[field].source &&
      !sources.includes(thing.sync[field].source))
      sources.push(thing.sync[field].source);
  }
  let p = [];
  let allURLs = [];
  sources.forEach(source => {
    let adapter = adapters.getAdapterForSource(source);
    let relevantURLs = thing.urls.filter(url => adapter.ask(url));
    allURLs = allURLs.concat(relevantURLs);
    p.push(...relevantURLs.map(url =>
      adapter
      .lookup(url)
      .catch(error => {
        debug.error(`Problem contacting adapter "${adapter.getSourceID()}" for URL ${url}.`);
        debug.error({ error });
      })
    ));
  });
  if (allURLs.length)
    debug.app(`Retrieving item metadata for ${thing.id} from the following URL(s):\n` +
      allURLs.join(', '));
  const results = await Promise.all(p);
  let needSlugUpdate;
  let dataBySource = _organizeDataBySource(results);
  sources.forEach((source) => {
    for (let field in thing.sync) {
      if (thing.sync[field].active && thing.sync[field].source === source &&
        Array.isArray(dataBySource[source])) {
        for (let d of dataBySource[source]) {
          if (d[field] !== undefined) {
            thing.sync[field].updated = new Date();
            this[field] = d[field];
            if (field == 'label')
              needSlugUpdate = true;
          }
        }
      }
    }
  });
  if (needSlugUpdate)
    await thing.updateSlug(userID, thing.originalLanguage || 'en');
  await thing.save();
  search.indexThing(thing);
  return thing;
  function _organizeDataBySource(results) {
    let rv = {};
    results.forEach(result => {
      if (typeof result == 'object' && typeof result.sourceID == 'string' &&
        typeof result.data == 'object') {
        if (!Array.isArray(rv[result.sourceID]))
          rv[result.sourceID] = [];
        rv[result.sourceID].unshift(result.data);
      }
    });
    return rv;
  }
}
async function updateActiveSyncs ( userID ) { const thing = this ; if ( ! thing . urls || ! thing . urls . length ) return thing ; if ( typeof thing . sync !== 'object' ) thing . sync = { } ; let sources = [ ] ; for ( let field in thing . sync ) { if ( thing . sync [ field ] . active && thing . sync [ field ] . source && ! sources . includes ( thing . sync [ field ] . source ) ) sources . push ( thing . sync [ field ] . source ) ; } let p = [ ] ; let allURLs = [ ] ; sources . forEach ( source => { let adapter = adapters . getAdapterForSource ( source ) ; let relevantURLs = thing . urls . filter ( url => adapter . ask ( url ) ) ; allURLs = allURLs . concat ( relevantURLs ) ; p . push ( ... relevantURLs . map ( url => adapter . lookup ( url ) . catch ( error => { debug . error ( ` ${ adapter . getSourceID ( ) } ${ url } ` ) ; debug . error ( { error } ) ; } ) ) ) ; } ) ; if ( allURLs . length ) debug . app ( ` ${ thing . id } \n ` + allURLs . join ( ', ' ) ) ; const results = await Promise . all ( p ) ; let needSlugUpdate ; let dataBySource = _organizeDataBySource ( results ) ; sources . forEach ( ( source ) => { for ( let field in thing . sync ) { if ( thing . sync [ field ] . active && thing . sync [ field ] . source === source && Array . isArray ( dataBySource [ source ] ) ) { for ( let d of dataBySource [ source ] ) { if ( d [ field ] !== undefined ) { thing . sync [ field ] . updated = new Date ( ) ; this [ field ] = d [ field ] ; if ( field == 'label' ) needSlugUpdate = true ; } } } } } ) ; if ( needSlugUpdate ) await thing . updateSlug ( userID , thing . originalLanguage || 'en' ) ; await thing . save ( ) ; search . indexThing ( thing ) ; return thing ; function _organizeDataBySource ( results ) { let rv = { } ; results . forEach ( result => { if ( typeof result == 'object' && typeof result . sourceID == 'string' && typeof result . data == 'object' ) { if ( ! Array . isArray ( rv [ result . sourceID ] ) ) rv [ result . sourceID ] = [ ] ; rv [ result . sourceID ] . unshift ( result . data ) ; } } ) ; return rv ; } }
Fetch new external data for all fields set to be synchronized.
Fetch new external data for all fields set to be synchronized .
// For readability // No URLs? Just give the thing back. // No known syncs? We'll still fall through to the save/index operation at // the end. // Determine which external sources we need to contact. While one source // may give us updates for many fields, we obviously only want to contact // it once. //  Build array of lookup promises from currently used sources // Keep track of all URLs we're contacting for convenience // Find all matching URLs in array (we might have, e.g., a URL for // a work and one for an edition, and need to contact both). // Add relevant lookups as individual elements to array, log errors // Perform all lookups, then update thing from the results, which will be // returned in the same order as the sources array. // If the label has changed, we need to update the short identifier // (slug). This means a possible URL change! Redirects are put in // place automatically. // Get obj w/ key = source ID, value = reverse order array of // result.data objects // Only update if everything looks good: sync is active, source // ID matches, we may have new data // Earlier results get priority, i.e. need to be assigned last // Index update can keep running after we resolve this promise, hence no "await" /**
   * Put valid data from results array into an object with sourceID as
   * the key and data as a reverse-order array. There may be multiple
   * URLs from one source, assigning value to the same field. URLs earlier
   * in the original thing.urls array take priority, so we have to ensure
   * they come last.
   *
   * @param {Object[]} results
   *  results from multiple adapters
   * @returns {Object}
   *  key = source, value = array of data objects
   * @memberof Thing
   * @inner
   * @protected
   */ // Correctly formatted result object with all relevant information // Initialize array if needed // See above on why this array is in reverse order