<commit_before>import asap from 'asap'
import assign from 'lodash/assign'
import cookies from 'cookies-js'
import isEmpty from 'lodash/isEmpty'
import isEqual from 'lodash/isEqual'
import filter from 'lodash/filter'
import forEach from 'lodash/forEach'
import map from 'lodash/map'
import once from 'lodash/once'
import React from 'react'
import fpSortBy from 'lodash/fp/sortBy'
import request from 'superagent'
import size from 'lodash/size'
import sortBy from 'lodash/sortBy'
import throttle from 'lodash/throttle'
import Xo from 'xo-lib'
import { createBackoff } from 'jsonrpc-websocket-client'
import { noHostsAvailable } from 'xo-common/api-errors'
import { reflect } from 'promise-toolbox'
import { resolve } from 'url'

import _ from '../intl'
import invoke from '../invoke'
import logError from '../log-error'
import { alert, confirm } from '../modal'
import { error, info, success } from '../notification'
import { noop, rethrow, tap, resolveId, resolveIds } from '../utils'
import {
  connected,
  disconnected,
  signedIn,
  signedOut,
  updateObjects,
  updatePermissions
} from '../store/actions'

// ===================================================================

export const XEN_DEFAULT_CPU_WEIGHT = 256
export const XEN_DEFAULT_CPU_CAP = 0

// ===================================================================

export const XEN_VIDEORAM_VALUES = [1, 2, 4, 8, 16]

// ===================================================================

export const isSrWritable = sr => sr && sr.content_type !== 'iso' && sr.size > 0
export const isSrShared = sr => sr && sr.$PBDs.length > 1
export const isVmRunning = vm => vm && vm.power_state === 'Running'

// ===================================================================

export const signOut = () => {
  cookies.expire('token')
  window.location.reload(true)
}

export const connect = () => {
  xo.open(createBackoff()).catch(error => {
    logError(error, 'failed to connect to xo-server')
  })
}

const xo = invoke(() => {
  const token = cookies.get('token')
  if (!token) {
    signOut()
    throw new Error('no valid token')
  }

  const xo = new Xo({
    credentials: { token }
  })

  xo.on('authenticationFailure', signOut)
  xo.on('scheduledAttempt', ({ delay }) => {
    console.warn('next attempt in %s ms', delay)
  })

  xo.on('closed', connect)

  return xo
})
connect()

const _signIn = new Promise(resolve => xo.once('authenticated', resolve))

const _call = (method, params) => {
  let promise = _signIn.then(() => xo.call(method, params))

  if (process.env.NODE_ENV !== 'production') {
    promise = promise::rethrow(error => {
      console.error('XO error', {
        method,
        params,
        code: error.code,
        message: error.message,
        data: error.data
      })
    })
  }

  return promise
}

// ===================================================================

export const connectStore = store => {
  let updates = {}
  const sendUpdates = throttle(() => {
    store.dispatch(updateObjects(updates))
    updates = {}
  }, 5e2)

  xo.on('open', () => store.dispatch(connected()))
  xo.on('closed', () => {
    store.dispatch(signedOut())
    store.dispatch(disconnected())
  })
  xo.on('authenticated', () => {
    store.dispatch(signedIn(xo.user))

    _call('xo.getAllObjects').then(objects => store.dispatch(updateObjects(objects)))
  })
  xo.on('notification', notification => {
    if (notification.method !== 'all') {
      return
    }

    assign(updates, notification.params.items)
    sendUpdates()
  })
  subscribePermissions(permissions => store.dispatch(updatePermissions(permissions)))

  // work around to keep the user in Redux store up to date
  //
  // FIXME: store subscriptions data directly in Redux
  subscribeUsers(user => {
    store.dispatch(signedIn(xo.user))
  })
}

// -------------------------------------------------------------------

export const resolveUrl = invoke(
  xo._url, // FIXME: accessing private prop
  baseUrl => to => resolve(baseUrl, to)
)

// -------------------------------------------------------------------

const createSubscription = cb => {
  const delay = 5e3

  const subscribers = Object.create(null)
  let cache
  let n = 0
  let nextId = 0
  let timeout

  let running = false

  const loop = () => {
    if (running) {
      return
    }

    running = true
    _signIn.then(() => cb()).then(result => {
      running = false
      timeout = setTimeout(loop, delay)

      if (!isEqual(result, cache)) {
        cache = result

        forEach(subscribers, subscriber => {
          // A subscriber might have disappeared during iteration.
          //
          // E.g.: if a subscriber triggers the subscription of another.
          if (subscriber) {
            subscriber(result)
          }
        })
      }
    }, error => {
      running = false
      console.error(error)
    })
  }

  const subscribe = cb => {
    const id = nextId++
    subscribers[id] = cb

    if (n++) {
      cache !== undefined && asap(() => cb(cache))
    } else {
      loop()
    }

    return once(() => {
      delete subscribers[id]

      if (!--n) {
        clearTimeout(timeout)
        cache = undefined
      }
    })
  }

  subscribe.forceRefresh = () => {
    if (n) {
      clearTimeout(timeout)
      loop()
    }
  }

  return subscribe
}

// Subscriptions -----------------------------------------------------

export const subscribeCurrentUser = createSubscription(() => xo.refreshUser())

export const subscribeAcls = createSubscription(() => _call('acl.get'))

export const subscribeJobs = createSubscription(() => _call('job.getAll'))

export const subscribeJobsLogs = createSubscription(() => _call('log.get', { namespace: 'jobs' }))

export const subscribeApiLogs = createSubscription(() => _call('log.get', { namespace: 'api' }))

export const subscribePermissions = createSubscription(() => _call('acl.getCurrentPermissions'))

export const subscribePlugins = createSubscription(() => _call('plugin.get'))

export const subscribeRemotes = createSubscription(() => _call('remote.getAll'))

export const subscribeResourceSets = createSubscription(() => _call('resourceSet.getAll'))

export const subscribeScheduleTable = createSubscription(() => _call('scheduler.getScheduleTable'))

export const subscribeSchedules = createSubscription(() => _call('schedule.getAll'))

export const subscribeServers = createSubscription(invoke(
  fpSortBy('host'),
  sort => () => _call('server.getAll').then(sort)
))

export const subscribeUsers = createSubscription(() => _call('user.getAll').then(users => {
  forEach(users, user => {
    user.type = 'user'
  })

  return sortBy(users, 'email')
}))

export const subscribeGroups = createSubscription(() => _call('group.getAll').then(groups => {
  forEach(groups, group => {
    group.type = 'group'
  })

  return sortBy(groups, 'name')
}))

export const subscribeRoles = createSubscription(invoke(
  sortBy('name'),
  sort => () => _call('role.getAll').then(sort)
))

export const subscribeIpPools = createSubscription(() => _call('ipPool.getAll'))

export const subscribeResourceCatalog = createSubscription(() => _call('cloud.getResourceCatalog'))

const xosanSubscriptions = {}
export const subscribeIsInstallingXosan = (pool, cb) => {
  const poolId = resolveId(pool)

  if (!xosanSubscriptions[poolId]) {
    xosanSubscriptions[poolId] = createSubscription(() => _call('xosan.checkSrIsBusy', { poolId }))
  }

  return xosanSubscriptions[poolId](cb)
}

// System ============================================================

export const apiMethods = _call('system.getMethodsInfo')

export const serverVersion = _call('system.getServerVersion')

export const getXoServerTimezone = _call('system.getServerTimezone')

// XO --------------------------------------------------------------------------

export const importConfig = config => (
  _call('xo.importConfig').then(({ $sendTo: url }) =>
    request.post(url).send(config).then(response => {
      if (response.status !== 200) {
        throw new Error('config import failed')
      }
    })
  )
)

export const exportConfig = () => (
  _call('xo.exportConfig').then(({ $getFrom: url }) => { window.location = `.${url}` })
)

// Server ------------------------------------------------------------

export const addServer = (host, username, password, label) => (
  _call('server.add', { host, label, password, username })::tap(
    subscribeServers.forceRefresh
  )::rethrow(() => error(_('serverError'), _('serverAddFailed')))
)

export const editServer = (server, props) => (
  _call('server.set', { ...props, id: resolveId(server) })::tap(
    subscribeServers.forceRefresh
  )
)

export const connectServer = server => (
  _call('server.connect', { id: resolveId(server) })::tap(
    subscribeServers.forceRefresh
  )
)

export const disconnectServer = server => (
  _call('server.disconnect', { id: resolveId(server) })::tap(
    subscribeServers.forceRefresh
  )
)

export const removeServer = server => (
  _call('server.remove', { id: resolveId(server) })::tap(<commit_msg>fix(xo/connectServer): refresh subscription also in case of error<commit_after>import asap from 'asap'
import assign from 'lodash/assign'
import cookies from 'cookies-js'
import isEmpty from 'lodash/isEmpty'
import isEqual from 'lodash/isEqual'
import filter from 'lodash/filter'
import forEach from 'lodash/forEach'
import map from 'lodash/map'
import once from 'lodash/once'
import React from 'react'
import fpSortBy from 'lodash/fp/sortBy'
import request from 'superagent'
import size from 'lodash/size'
import sortBy from 'lodash/sortBy'
import throttle from 'lodash/throttle'
import Xo from 'xo-lib'
import { createBackoff } from 'jsonrpc-websocket-client'
import { lastly, reflect } from 'promise-toolbox'
import { noHostsAvailable } from 'xo-common/api-errors'
import { resolve } from 'url'

import _ from '../intl'
import invoke from '../invoke'
import logError from '../log-error'
import { alert, confirm } from '../modal'
import { error, info, success } from '../notification'
import { noop, rethrow, tap, resolveId, resolveIds } from '../utils'
import {
  connected,
  disconnected,
  signedIn,
  signedOut,
  updateObjects,
  updatePermissions
} from '../store/actions'

// ===================================================================

export const XEN_DEFAULT_CPU_WEIGHT = 256
export const XEN_DEFAULT_CPU_CAP = 0

// ===================================================================

export const XEN_VIDEORAM_VALUES = [1, 2, 4, 8, 16]

// ===================================================================

export const isSrWritable = sr => sr && sr.content_type !== 'iso' && sr.size > 0
export const isSrShared = sr => sr && sr.$PBDs.length > 1
export const isVmRunning = vm => vm && vm.power_state === 'Running'

// ===================================================================

export const signOut = () => {
  cookies.expire('token')
  window.location.reload(true)
}

export const connect = () => {
  xo.open(createBackoff()).catch(error => {
    logError(error, 'failed to connect to xo-server')
  })
}

const xo = invoke(() => {
  const token = cookies.get('token')
  if (!token) {
    signOut()
    throw new Error('no valid token')
  }

  const xo = new Xo({
    credentials: { token }
  })

  xo.on('authenticationFailure', signOut)
  xo.on('scheduledAttempt', ({ delay }) => {
    console.warn('next attempt in %s ms', delay)
  })

  xo.on('closed', connect)

  return xo
})
connect()

const _signIn = new Promise(resolve => xo.once('authenticated', resolve))

const _call = (method, params) => {
  let promise = _signIn.then(() => xo.call(method, params))

  if (process.env.NODE_ENV !== 'production') {
    promise = promise::rethrow(error => {
      console.error('XO error', {
        method,
        params,
        code: error.code,
        message: error.message,
        data: error.data
      })
    })
  }

  return promise
}

// ===================================================================

export const connectStore = store => {
  let updates = {}
  const sendUpdates = throttle(() => {
    store.dispatch(updateObjects(updates))
    updates = {}
  }, 5e2)

  xo.on('open', () => store.dispatch(connected()))
  xo.on('closed', () => {
    store.dispatch(signedOut())
    store.dispatch(disconnected())
  })
  xo.on('authenticated', () => {
    store.dispatch(signedIn(xo.user))

    _call('xo.getAllObjects').then(objects => store.dispatch(updateObjects(objects)))
  })
  xo.on('notification', notification => {
    if (notification.method !== 'all') {
      return
    }

    assign(updates, notification.params.items)
    sendUpdates()
  })
  subscribePermissions(permissions => store.dispatch(updatePermissions(permissions)))

  // work around to keep the user in Redux store up to date
  //
  // FIXME: store subscriptions data directly in Redux
  subscribeUsers(user => {
    store.dispatch(signedIn(xo.user))
  })
}

// -------------------------------------------------------------------

export const resolveUrl = invoke(
  xo._url, // FIXME: accessing private prop
  baseUrl => to => resolve(baseUrl, to)
)

// -------------------------------------------------------------------

const createSubscription = cb => {
  const delay = 5e3

  const subscribers = Object.create(null)
  let cache
  let n = 0
  let nextId = 0
  let timeout

  let running = false

  const loop = () => {
    if (running) {
      return
    }

    running = true
    _signIn.then(() => cb()).then(result => {
      running = false
      timeout = setTimeout(loop, delay)

      if (!isEqual(result, cache)) {
        cache = result

        forEach(subscribers, subscriber => {
          // A subscriber might have disappeared during iteration.
          //
          // E.g.: if a subscriber triggers the subscription of another.
          if (subscriber) {
            subscriber(result)
          }
        })
      }
    }, error => {
      running = false
      console.error(error)
    })
  }

  const subscribe = cb => {
    const id = nextId++
    subscribers[id] = cb

    if (n++) {
      cache !== undefined && asap(() => cb(cache))
    } else {
      loop()
    }

    return once(() => {
      delete subscribers[id]

      if (!--n) {
        clearTimeout(timeout)
        cache = undefined
      }
    })
  }

  subscribe.forceRefresh = () => {
    if (n) {
      clearTimeout(timeout)
      loop()
    }
  }

  return subscribe
}

// Subscriptions -----------------------------------------------------

export const subscribeCurrentUser = createSubscription(() => xo.refreshUser())

export const subscribeAcls = createSubscription(() => _call('acl.get'))

export const subscribeJobs = createSubscription(() => _call('job.getAll'))

export const subscribeJobsLogs = createSubscription(() => _call('log.get', { namespace: 'jobs' }))

export const subscribeApiLogs = createSubscription(() => _call('log.get', { namespace: 'api' }))

export const subscribePermissions = createSubscription(() => _call('acl.getCurrentPermissions'))

export const subscribePlugins = createSubscription(() => _call('plugin.get'))

export const subscribeRemotes = createSubscription(() => _call('remote.getAll'))

export const subscribeResourceSets = createSubscription(() => _call('resourceSet.getAll'))

export const subscribeScheduleTable = createSubscription(() => _call('scheduler.getScheduleTable'))

export const subscribeSchedules = createSubscription(() => _call('schedule.getAll'))

export const subscribeServers = createSubscription(invoke(
  fpSortBy('host'),
  sort => () => _call('server.getAll').then(sort)
))

export const subscribeUsers = createSubscription(() => _call('user.getAll').then(users => {
  forEach(users, user => {
    user.type = 'user'
  })

  return sortBy(users, 'email')
}))

export const subscribeGroups = createSubscription(() => _call('group.getAll').then(groups => {
  forEach(groups, group => {
    group.type = 'group'
  })

  return sortBy(groups, 'name')
}))

export const subscribeRoles = createSubscription(invoke(
  sortBy('name'),
  sort => () => _call('role.getAll').then(sort)
))

export const subscribeIpPools = createSubscription(() => _call('ipPool.getAll'))

export const subscribeResourceCatalog = createSubscription(() => _call('cloud.getResourceCatalog'))

const xosanSubscriptions = {}
export const subscribeIsInstallingXosan = (pool, cb) => {
  const poolId = resolveId(pool)

  if (!xosanSubscriptions[poolId]) {
    xosanSubscriptions[poolId] = createSubscription(() => _call('xosan.checkSrIsBusy', { poolId }))
  }

  return xosanSubscriptions[poolId](cb)
}

// System ============================================================

export const apiMethods = _call('system.getMethodsInfo')

export const serverVersion = _call('system.getServerVersion')

export const getXoServerTimezone = _call('system.getServerTimezone')

// XO --------------------------------------------------------------------------

export const importConfig = config => (
  _call('xo.importConfig').then(({ $sendTo: url }) =>
    request.post(url).send(config).then(response => {
      if (response.status !== 200) {
        throw new Error('config import failed')
      }
    })
  )
)

export const exportConfig = () => (
  _call('xo.exportConfig').then(({ $getFrom: url }) => { window.location = `.${url}` })
)

// Server ------------------------------------------------------------

export const addServer = (host, username, password, label) => (
  _call('server.add', { host, label, password, username })::tap(
    subscribeServers.forceRefresh
  )::rethrow(() => error(_('serverError'), _('serverAddFailed')))
)

export const editServer = (server, props) => (
  _call('server.set', { ...props, id: resolveId(server) })::tap(
    subscribeServers.forceRefresh
  )
)

export const connectServer = server => (
  _call('server.connect', { id: resolveId(server) })::lastly(
    subscribeServers.forceRefresh
  )
)

export const disconnectServer = server => (
  _call('server.disconnect', { id: resolveId(server) })::tap(
    subscribeServers.forceRefresh
  )
)

export const removeServer = server => (
  _call('server.remove', { id: resolveId(server) })::tap(
{'fraction_non_alphanumeric': 0.21677937214800153, 'fraction_numerical': 0.002309087910275441, 'mean_word_length': 3.255966307908283, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 145, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5769814', 'n_tokens_mistral': 5424, 'n_tokens_neox': 5207, 'n_words': 1253}