app/js/modules/utils.js

/**
 * @file Contains all helper and utility functions
 * @author yafp
 * @module utils
 */
'use strict'

// ----------------------------------------------------------------------------
// REQUIRE MODULES
// ----------------------------------------------------------------------------
const sentry = require('./sentry.js') // sentry for error reporting

// ----------------------------------------------------------------------------
// FUNCTIONS
// ----------------------------------------------------------------------------

/**
* @function writeConsoleMsg
* @summary Writes console output for the renderer process
* @description Writes console output for the renderer process
* @param {string} type - String which defines the log type
* @param {string} message - String which defines the log message
*/
function writeConsoleMsg (type, message) {
    const prefix = '[ Renderer ] '
    const log = require('electron-log')
    // electron-log can: error, warn, info, verbose, debug, silly
    switch (type) {
    case 'info':
        log.info(prefix + message)
        break

    case 'warn':
        log.warn(prefix + message)
        break

    case 'error':
        log.error(prefix + message)
        break

    default:
        log.silly(prefix + message)
    }
}

/**
* @function showNoty
* @summary Shows a noty notification
* @description Creates an in-app notification using the noty framework
* @param {string} type - Options: alert, success, warning, error, info/information
* @param {string} message - notification text
* @param {number} [timeout] - Timevalue, defines how long the message should be displayed. Use 0 for no-timeout
*/
function showNoty (type, message, timeout = 3000) {
    const Noty = require('noty')
    new Noty({
        type: type,
        timeout: timeout,
        theme: 'bootstrap-v4',
        layout: 'bottom',
        text: message,
        animation: {
            open: 'animated fadeIn', // Animate.css class names: default: bounceInRight
            close: 'animated fadeOut' // Animate.css class names: default:bounceOutRight
        }
    }).show()
}

/**
* @function showNotification
* @summary Shows a desktop notification
* @description Shows a desktop notification
* @param {string} message - The notification message text
* @param {string} [title] - The title of the desktop notification
*/
function showNotification (message, title = 'dirgistered') {
    const myNotification = new Notification(title, {
        body: message,
        icon: 'img/icon/icon.png'
    })

    myNotification.onclick = () => {
        writeConsoleMsg('info', 'showNotification ::: Notification clicked')
        const { ipcRenderer } = require('electron')
        ipcRenderer.send('showAndFocusMainUI') // tell main.js to show the main UI
    }
}

/**
* @function openURL
* @summary Opens an url in browser
* @description Opens a given url in default browser. This is pretty slow, but got no better solution so far.
* @param {string} url - URL string which contains the target url
*/
function openURL (url) {
    const { shell } = require('electron')
    writeConsoleMsg('info', 'openURL ::: Trying to open the url: _' + url + '_.')
    shell.openExternal(url)
}

/**
* @function generateTimestamp
* @summary Generates a timestamp
* @description Generates a timestamp using time-stamp
* @return {string} - timestamp - The generates timestamp
*/
function generateTimestamp (pattern = 'HH:mm:ss') {
    const timestamp = require('time-stamp')
    // var currentTimestamp = timestamp('HH:mm:ss') // hours : minutes : seconds
    var currentTimestamp = timestamp(pattern) // hours : minutes : seconds
    return currentTimestamp
}

/**
* @function createFolder
* @summary Creates a given folder
* @description Checks if the given path exists, otherwise it creates it
* @param {string} folderName- The given folder path which should be created
*/
function createFolder (folderPath) {
    // var fs = require('fs')
    const fs = require('graceful-fs') // see #9
    if (!fs.existsSync(folderPath)) {
        fs.mkdirSync(folderPath)
        writeConsoleMsg('info', 'createFolder ::: Created target folder: _' + folderPath + '_.')
        return true
    } else {
        writeConsoleMsg('warn', 'createFolder ::: Folder: _' + folderPath + '_ already exists')
        return false
    }
}

/**
* @function getFontAwesomeFileIcon
* @summary Returns a FontAwesome iconCode
* @description Gets a given fileextension and returns the related iconCode. This iconCode is then used in the generated output .html
* @param {string} extension - The given extension
* @return {string} - iconCode - The matching iconcode for the given extension
*/
function getFontAwesomeFileIcon (extension) {
    var iconCode

    switch (extension) {
    // audio
    case 'aac':
    case 'aiff':
    case 'flac':
    case 'm4a':
    case 'm4b':
    case 'm4p':
    case 'mp3':
    case 'ogg':
    case 'opus':
    case 'wav':
    case 'wma':
        iconCode = '<i class="fas fa-file-audio"></i>'
        break

    // archive
    case 'zip':
    case '7z':
        iconCode = ' <i class="fas fa-file-archive"></i>'
        break

    // code
    case 'h': // c
    case 'js': // javascript
    case 'css': // css
    case 'php': // php
    case 'py': // phyton
    case 'cmd': // cmd (Windows)
    case 'bat': // batch (Windows)
    case 'xml': // xml
    case 'json': // json
        iconCode = '<i class="fas fa-file-code"></i>'
        break

    // csv
    case 'csv':
        iconCode = '<i class="fas fa-file-csv"></i>'
        break

    // excel
    case 'xls':
    case 'xlsx':
        iconCode = '<i class="fas fa-file-excel"></i>'
        break

    // images
    case 'bmp':
    case 'jpg':
    case 'jpeg':
    case 'tiff':
    case 'tif':
    case 'gif':
    case 'ico':
    case 'psd':
    case 'svg':
    case 'png':
        iconCode = '<i class="fas fa-file-image"></i>'
        break

    // pdf
    case 'pdf':
        iconCode = '<i class="fas fa-file-pdf"></i>'
        break

    // powerpoint
    case 'ppt':
    case 'pptx':
        iconCode = '<i class="fas fa-file-powerpoint"></i>'
        break

    // txt
    case 'txt':
    case 'md': // markdown
        iconCode = '<i class="fas fa-file-alt"></i>'
        break

    // video
    case 'avi':
    case 'mkv':
    case 'wmv':
    case 'mpg':
    case 'mpeg':
    case 'mp4':
    case 'm4v':
    case 'ogv':
        iconCode = '<i class="fas fa-file-video"></i>'
        break

    // word
    case 'doc':
    case 'docx':
        iconCode = '<i class="fas fa-file-word"></i>'
        break

    // default
    default:
        iconCode = '<i class="fas fa-file"></i>'
    }

    return iconCode
}

/**
* @function appendToFile
* @summary Appends text to a given filePath
* @description Appends text to a given filePath
* @param {string} filePath - The path to the file which the text should be appended to
* @param {string} textToAppend - The actual text which should be appended
*/
function appendToFile (filePath, textToAppend) {
    // var fs = require('fs')
    const fs = require('graceful-fs') // see #9

    fs.appendFileSync(filePath, textToAppend, function (error) { // foo
        if (error) {
            writeConsoleMsg('error', 'appendToFile ::: Failed to append text to the .html file (' + filePath + ').')
            showNoty('error', 'Error occured while trying to append text to an index file. Error: ' + error)
        }
    })
}

/**
* @function bytesToSize
* @summary Calculates bytes to size
* @description Calculates bytes to size
* @param {string} bytes - The given amount of bytes
* @return The actual size
*/
function bytesToSize (bytes) {
    const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']
    if (bytes === 0) return 'n/a'
    const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)), 10)
    // if (i === 0) return `${bytes} ${sizes[i]})`
    if (i === 0) return `${bytes} ${sizes[i]}`
    return `${(bytes / (1024 ** i)).toFixed(1)} ${sizes[i]}`
}
// source: https://gist.github.com/lanqy/5193417 and variations

/**
* @function userSettingWrite
* @summary Write a user setting to file
* @description Writes a value for a given key to electron-json-storage
* @param {String} key - Name of storage key
* @param {String} value - New value
* @param {boolean} [silent] - If true - no notification is displayed on initial settingscreation
* @throws Exception when writing a file failed
*/
function userSettingWrite (key, value, silent = false) {
    const storage = require('electron-json-storage')
    const remote = require('electron').remote
    const app = remote.app
    const path = require('path')

    // set new path for userUsettings
    const userSettingsPath = path.join(app.getPath('userData'), 'UserSettings')
    storage.setDataPath(userSettingsPath)

    // write the user setting
    storage.set(key, { setting: value }, function (error) {
        if (error) {
            writeConsoleMsg('error', 'userSettingWrite ::: Unable to write setting _' + key + '_ = _' + value + '_. Error: ' + error)
            throw error
        }
        writeConsoleMsg('info', 'userSettingWrite ::: _' + key + '_ = _' + value + '_')
        globalObjectSet(key, value)

        if (silent === false) {
            showNoty('success', 'Set <b>' + key + '</b> to <b>' + value + '</b>.')
        }
    })
}

/**
* @function userSettingRead
* @summary Read a user setting from file
* @description Reads a value stored in local storage (for a given key)
* @param {String} key - Name of local storage key
* @param {Boolean} [optionalUpdateSettingUI] Boolean used for an ugly hack
*/
function userSettingRead (key, optionalUpdateSettingUI = false) {
    const storage = require('electron-json-storage')
    const remote = require('electron').remote
    const app = remote.app
    const path = require('path')

    // writeConsoleMsg('info', 'userSettingRead ::: Trying to read value of key: _' + key + '_.')

    // change path for userSettings
    const userSettingsPath = path.join(app.getPath('userData'), 'UserSettings')
    storage.setDataPath(userSettingsPath)

    // read the json file
    storage.get(key, function (error, data) {
        if (error) {
            writeConsoleMsg('error', 'userSettingRead ::: Unable to read user setting. Error: ' + error)
            throw error
        }
        var value = data.setting
        // writeConsoleMsg('info', 'userSettingRead :::  _' + key + '_ = _' + value + '_.')

        // Setting: enablePrereleases
        //
        if (key === 'enablePrereleases') {
            var settingPrereleases

            // if it is not yet configured
            if ((value === null) || (value === undefined)) {
                settingPrereleases = false // set the default
                writeConsoleMsg('warn', 'userSettingRead ::: No user setting found for: _' + key + '_. Initializing it now with the default value: _' + settingPrereleases + '_.')
                userSettingWrite('enablePrereleases', settingPrereleases, true) // write the setting
            } else {
                settingPrereleases = value // update global var
                writeConsoleMsg('info', 'userSettingRead ::: Found configured _' + key + '_ with value: _' + settingPrereleases + '_.')
            }
            globalObjectSet('enablePrereleases', settingPrereleases) // update the global object

            // Optional: update the settings UI
            if (optionalUpdateSettingUI === true) {
                if (settingPrereleases === true) {
                    $('#checkboxEnablePreReleases').prop('checked', true)
                } else {
                    $('#checkboxEnablePreReleases').prop('checked', false)
                }
            }
        }
        // end: enablePrereleases

        // Setting: enableErrorReporting
        //
        if (key === 'enableErrorReporting') {
            var settingEnableErrorReporting

            // not configured
            if ((value === null) || (value === undefined)) {
                settingEnableErrorReporting = true
                writeConsoleMsg('warn', 'userSettingRead ::: No user setting found for: _' + key + '_. Initializing it now with the default value: _' + settingEnableErrorReporting + '_.')
                userSettingWrite('enableErrorReporting', true, true) // write the setting
                sentry.enableSentry()
            } else {
                settingEnableErrorReporting = value
                writeConsoleMsg('info', 'userSettingRead ::: Found configured _' + key + '_ with value: _' + settingEnableErrorReporting + '_.')

                if (settingEnableErrorReporting === true) {
                    sentry.enableSentry()
                } else {
                    sentry.disableSentry()
                }
            }
            globalObjectSet('enableErrorReporting', settingEnableErrorReporting) // update the global object

            // Optional: update the settings UI
            if (optionalUpdateSettingUI === true) {
                if (settingEnableErrorReporting === true) {
                    $('#checkboxEnableErrorReporting').prop('checked', true)
                } else {
                    $('#checkboxEnableErrorReporting').prop('checked', false)
                }
            }
        }
        // end: enableErrorReporting
    })
}

/**
* @function globalObjectGet
* @summary Gets a value of a single property from the global object in main.js
* @description Gets a value of a single property from the global object in main.js
* @param {String} property - Name of the property
* @return {string} value - Value of the property
*/
function globalObjectGet (property) {
    const { remote } = require('electron')
    var value = remote.getGlobal('sharedObj')[property]
    // writeConsoleMsg('info', 'globalObjectGet ::: Property: _' + property + '_ has the value: _' + value + '_.')
    return value
}

/**
* @function globalObjectSet
* @summary Updates the value of a single property from the global object in main.js
* @description Updates the value of a single property from the global object in main.js
* @param {String} property - Name of the property
* @param {String} value - The new value of the property
*/
function globalObjectSet (property, value) {
    const { ipcRenderer } = require('electron')
    ipcRenderer.send('globalObjectSet', property, value)
}

// ----------------------------------------------------------------------------
// EXPORT THE MODULE FUNCTIONS
// ----------------------------------------------------------------------------
//
module.exports.writeConsoleMsg = writeConsoleMsg
module.exports.showNoty = showNoty
module.exports.showNotification = showNotification
module.exports.openURL = openURL
module.exports.generateTimestamp = generateTimestamp
module.exports.createFolder = createFolder
module.exports.getFontAwesomeFileIcon = getFontAwesomeFileIcon
module.exports.appendToFile = appendToFile
module.exports.bytesToSize = bytesToSize
module.exports.userSettingWrite = userSettingWrite
module.exports.userSettingRead = userSettingRead
module.exports.globalObjectGet = globalObjectGet
module.exports.globalObjectSet = globalObjectSet