<commit_before>/*
 * Copyright 2016 resin.io
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict'

const Immutable = require('immutable')
const _ = require('lodash')
const redux = require('redux')
const uuidV4 = require('uuid/v4')
const constraints = require('../../../shared/drive-constraints')
const supportedFormats = require('../../../shared/supported-formats')
const errors = require('../../../shared/errors')
const fileExtensions = require('../../../shared/file-extensions')
const utils = require('../../../shared/utils')
const settings = require('./settings')

/**
 * @summary Verify and throw if any state fields are nil
 * @function
 * @public
 *
 * @param {Object} object - state object
 * @param {Array<Array<String>> | Array<String>} fields - array of object field paths
 * @param {String} name - name of the state we're dealing with
 * @throws
 *
 * @example
 * const fields = [ 'type', 'percentage' ]
 * verifyNoNilFields(action.data, fields, 'flash')
 */
const verifyNoNilFields = (object, fields, name) => {
  const nilFields = _.filter(fields, (field) => {
    return _.isNil(_.get(object, field))
  })
  if (nilFields.length) {
    throw new Error(`Missing ${name} fields: ${nilFields.join(', ')}`)
  }
}

/**
 * @summary FLASH_STATE fields that can't be nil
 * @constant
 * @private
 */
const flashStateNoNilFields = [
  'speed',
  'totalSpeed'
]

/**
 * @summary SELECT_IMAGE fields that can't be nil
 * @constant
 * @private
 */
const selectImageNoNilFields = [
  'path',
  'extension'
]

/**
 * @summary Application default state
 * @type {Object}
 * @constant
 * @private
 */
const DEFAULT_STATE = Immutable.fromJS({
  applicationSessionUuid: '',
  flashingWorkflowUuid: '',
  availableDrives: [],
  selection: {
    devices: new Immutable.OrderedSet()
  },
  isFlashing: false,
  flashResults: {},
  flashState: {
    flashing: 0,
    verifying: 0,
    successful: 0,
    failed: 0,
    percentage: 0,
    speed: null,
    totalSpeed: null
  }
})

/**
 * @summary Application supported action messages
 * @type {Object}
 * @constant
 */
const ACTIONS = _.fromPairs(_.map([
  'SET_AVAILABLE_DRIVES',
  'SET_FLASH_STATE',
  'RESET_FLASH_STATE',
  'SET_FLASHING_FLAG',
  'UNSET_FLASHING_FLAG',
  'SELECT_DRIVE',
  'SELECT_IMAGE',
  'DESELECT_DRIVE',
  'DESELECT_IMAGE',
  'SET_APPLICATION_SESSION_UUID',
  'SET_FLASHING_WORKFLOW_UUID',
  'SET_WEBVIEW_SHOWING_STATUS'
], (message) => {
  return [ message, message ]
}))

/**
 * @summary Get available drives from the state
 * @function
 * @public
 *
 * @param {Object} state - state object
 * @returns {Object} new state
 *
 * @example
 * const drives = getAvailableDrives(state)
 * _.find(drives, { device: '/dev/sda' })
 */
const getAvailableDrives = (state) => {
  // eslint-disable-next-line lodash/prefer-lodash-method
  return state.get('availableDrives').toJS()
}

/**
 * @summary The redux store reducer
 * @function
 * @private
 *
 * @param {Object} state - application state
 * @param {Object} action - dispatched action
 * @returns {Object} new application state
 *
 * @example
 * const newState = storeReducer(DEFAULT_STATE, {
 *   type: ACTIONS.DESELECT_DRIVE
 * });
 */
const storeReducer = (state = DEFAULT_STATE, action) => {
  switch (action.type) {
    case ACTIONS.SET_AVAILABLE_DRIVES: {
      // Type: action.data : Array<DriveObject>

      if (!action.data) {
        throw errors.createError({
          title: 'Missing drives'
        })
      }

      const drives = action.data

      if (!_.isArray(drives) || !_.every(drives, _.isPlainObject)) {
        throw errors.createError({
          title: `Invalid drives: ${drives}`
        })
      }

      const newState = state.set('availableDrives', Immutable.fromJS(drives))
      const selectedDevices = newState.getIn([ 'selection', 'devices' ]).toJS()

      // Remove selected drives that are stale, i.e. missing from availableDrives
      const nonStaleNewState = _.reduce(selectedDevices, (accState, device) => {
        // Check whether the drive still exists in availableDrives
        if (device && !_.find(drives, {
          device
        })) {
          // Deselect this drive gone from availableDrives
          return storeReducer(accState, {
            type: ACTIONS.DESELECT_DRIVE,
            data: device
          })
        }

        return accState
      }, newState)

      const shouldAutoselectAll = Boolean(settings.get('disableExplicitDriveSelection'))
      const AUTOSELECT_DRIVE_COUNT = 1
      const nonStaleSelectedDevices = nonStaleNewState.getIn([ 'selection', 'devices' ]).toJS()
      const hasSelectedDevices = nonStaleSelectedDevices.length >= AUTOSELECT_DRIVE_COUNT
      const shouldAutoselectOne = drives.length === AUTOSELECT_DRIVE_COUNT && !hasSelectedDevices<commit_msg>fix(gui): Allow drives to be objects<commit_after>/*
 * Copyright 2016 resin.io
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict'

const Immutable = require('immutable')
const _ = require('lodash')
const redux = require('redux')
const uuidV4 = require('uuid/v4')
const constraints = require('../../../shared/drive-constraints')
const supportedFormats = require('../../../shared/supported-formats')
const errors = require('../../../shared/errors')
const fileExtensions = require('../../../shared/file-extensions')
const utils = require('../../../shared/utils')
const settings = require('./settings')

/**
 * @summary Verify and throw if any state fields are nil
 * @function
 * @public
 *
 * @param {Object} object - state object
 * @param {Array<Array<String>> | Array<String>} fields - array of object field paths
 * @param {String} name - name of the state we're dealing with
 * @throws
 *
 * @example
 * const fields = [ 'type', 'percentage' ]
 * verifyNoNilFields(action.data, fields, 'flash')
 */
const verifyNoNilFields = (object, fields, name) => {
  const nilFields = _.filter(fields, (field) => {
    return _.isNil(_.get(object, field))
  })
  if (nilFields.length) {
    throw new Error(`Missing ${name} fields: ${nilFields.join(', ')}`)
  }
}

/**
 * @summary FLASH_STATE fields that can't be nil
 * @constant
 * @private
 */
const flashStateNoNilFields = [
  'speed',
  'totalSpeed'
]

/**
 * @summary SELECT_IMAGE fields that can't be nil
 * @constant
 * @private
 */
const selectImageNoNilFields = [
  'path',
  'extension'
]

/**
 * @summary Application default state
 * @type {Object}
 * @constant
 * @private
 */
const DEFAULT_STATE = Immutable.fromJS({
  applicationSessionUuid: '',
  flashingWorkflowUuid: '',
  availableDrives: [],
  selection: {
    devices: new Immutable.OrderedSet()
  },
  isFlashing: false,
  flashResults: {},
  flashState: {
    flashing: 0,
    verifying: 0,
    successful: 0,
    failed: 0,
    percentage: 0,
    speed: null,
    totalSpeed: null
  }
})

/**
 * @summary Application supported action messages
 * @type {Object}
 * @constant
 */
const ACTIONS = _.fromPairs(_.map([
  'SET_AVAILABLE_DRIVES',
  'SET_FLASH_STATE',
  'RESET_FLASH_STATE',
  'SET_FLASHING_FLAG',
  'UNSET_FLASHING_FLAG',
  'SELECT_DRIVE',
  'SELECT_IMAGE',
  'DESELECT_DRIVE',
  'DESELECT_IMAGE',
  'SET_APPLICATION_SESSION_UUID',
  'SET_FLASHING_WORKFLOW_UUID',
  'SET_WEBVIEW_SHOWING_STATUS'
], (message) => {
  return [ message, message ]
}))

/**
 * @summary Get available drives from the state
 * @function
 * @public
 *
 * @param {Object} state - state object
 * @returns {Object} new state
 *
 * @example
 * const drives = getAvailableDrives(state)
 * _.find(drives, { device: '/dev/sda' })
 */
const getAvailableDrives = (state) => {
  // eslint-disable-next-line lodash/prefer-lodash-method
  return state.get('availableDrives').toJS()
}

/**
 * @summary The redux store reducer
 * @function
 * @private
 *
 * @param {Object} state - application state
 * @param {Object} action - dispatched action
 * @returns {Object} new application state
 *
 * @example
 * const newState = storeReducer(DEFAULT_STATE, {
 *   type: ACTIONS.DESELECT_DRIVE
 * });
 */
const storeReducer = (state = DEFAULT_STATE, action) => {
  switch (action.type) {
    case ACTIONS.SET_AVAILABLE_DRIVES: {
      // Type: action.data : Array<DriveObject>

      if (!action.data) {
        throw errors.createError({
          title: 'Missing drives'
        })
      }

      const drives = action.data

      if (!_.isArray(drives) || !_.every(drives, _.isObject)) {
        throw errors.createError({
          title: `Invalid drives: ${drives}`
        })
      }

      const newState = state.set('availableDrives', Immutable.fromJS(drives))
      const selectedDevices = newState.getIn([ 'selection', 'devices' ]).toJS()

      // Remove selected drives that are stale, i.e. missing from availableDrives
      const nonStaleNewState = _.reduce(selectedDevices, (accState, device) => {
        // Check whether the drive still exists in availableDrives
        if (device && !_.find(drives, {
          device
        })) {
          // Deselect this drive gone from availableDrives
          return storeReducer(accState, {
            type: ACTIONS.DESELECT_DRIVE,
            data: device
          })
        }

        return accState
      }, newState)

      const shouldAutoselectAll = Boolean(settings.get('disableExplicitDriveSelection'))
      const AUTOSELECT_DRIVE_COUNT = 1
      const nonStaleSelectedDevices = nonStaleNewState.getIn([ 'selection', 'devices' ]).toJS()
      const hasSelectedDevices = nonStaleSelectedDevices.length >= AUTOSELECT_DRIVE_COUNT
      const shouldAutoselectOne = drives.length === AUTOSELECT_DRIVE_COUNT && !hasSelectedDevices
{'fraction_non_alphanumeric': 0.13286118980169973, 'fraction_numerical': 0.003021718602455146, 'mean_word_length': 3.3656224237427863, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 25, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, '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': '5080270', 'n_tokens_mistral': 3275, 'n_tokens_neox': 3208, 'n_words': 868}