app/js/modules/ui.js

/**
 * @file Contains all ui functions
 * @author yafp
 * @module ui
 */
'use strict'

// ----------------------------------------------------------------------------
// REQUIRE MODULES
// ----------------------------------------------------------------------------
//
const utils = require('./utils.js')
const ffmpeg = require('./ffmpeg.js')
const youtubeDl = require('./youtubeDl.js')
const sentry = require('./sentry.js')

// ----------------------------------------------------------------------------
// VARIABLES
// ----------------------------------------------------------------------------
//
var distractEnabler = 0
var arrayUserUrls = [] // contains the urls which should be downloaded
var arrayUserUrlsN = []

/**
* @function windowMainApplicationStateSet
* @summary Updates the application state information
* @description Updates the applicationState in globalObject. Updates the application state displayed in the UI. Starts or stops the spinner depending on the state
* @param {string} [newState] - String which defines the new state of the application
*/
function windowMainApplicationStateSet (newState = 'idle') {
    if (newState === 'Download in progress') { // #97
        // enable powerSaveBlocker
        const { ipcRenderer } = require('electron')
        utils.writeConsoleMsg('info', 'windowMainApplicationStateSet ::: Trying to enable the PowerSaveBlocker now, as media-dupes is currently downloading.')
        ipcRenderer.send('enablePowerSaveBlocker')
    }

    utils.globalObjectSet('applicationState', newState) // update the global object
    utils.writeConsoleMsg('info', 'windowMainApplicationStateSet ::: Setting application state to: _' + newState + '_.')

    if (newState === 'idle') {
        newState = ' '
        windowMainLoadingAnimationHide()
    } else {
        windowMainLoadingAnimationShow()
    }
    $('#applicationState').html(newState) // update the main ui
}

/**
* @function windowMainLoadingAnimationShow
* @summary Shows the loading animation / download spinner
* @description Shows the loading animation / download spinner. applicationStateSet() is using this function
*/
function windowMainLoadingAnimationShow () {
    // if ( $('#md_spinner').attr( "hidden" )) { // only if it isnt already displayed
    if ($('#div').data('hidden', true)) { // only if it isnt already displayed
        utils.writeConsoleMsg('info', 'windowMainLoadingAnimationShow ::: Show spinner')
        $('#md_spinner').attr('hidden', false)
    }
}

/**
* @function windowMainLoadingAnimationHide
* @summary Hides the loading animation / download spinner
* @description Hides the loading animation / download spinner. applicationStateSet() is using this function
*/
function windowMainLoadingAnimationHide () {
    if ($('#div').data('hidden', false)) { // only if it isnt already hidden
        utils.writeConsoleMsg('info', 'windowMainLoadingAnimationHide ::: Hide spinner')
        $('#md_spinner').attr('hidden', true)
    }
}

/**
* @function windowMainButtonsOthersEnable
* @summary Enables some of the footer buttons when a download is finished
* @description Is executed when a download task has ended by the user
*/
function windowMainButtonsOthersEnable () {
    // enable some buttons
    $('#inputNewUrl').prop('disabled', false) // url input field
    $('#buttonShowHelp').prop('disabled', false) // help / intro
    utils.writeConsoleMsg('info', 'windowMainButtonsOthersEnable ::: Did enable some other UI elements')
}

/**
* @function windowMainButtonsOthersDisable
* @summary Disables some of the footer buttons while a download is running
* @description Is executed when a download task is started by the user
*/
function windowMainButtonsOthersDisable () {
    // disable some buttons
    $('#inputNewUrl').prop('disabled', true) // url input field
    $('#buttonShowHelp').prop('disabled', true) // help / intro
    utils.writeConsoleMsg('info', 'windowMainButtonsOthersDisable ::: Did disable some other UI elements')
}

/**
* @function windowMainButtonsStartEnable
* @summary Enabled the 2 start buttons
* @description Is executed when the todo-list contains at least 1 item
*/
function windowMainButtonsStartEnable () {
    // enable start buttons if needed - if needed
    if ($('#buttonStartVideoExec').is(':disabled')) {
        // Button: VideoExec
        $('#buttonStartVideoExec').prop('disabled', false)
        $('#buttonStartVideoExec').removeClass('btn-secondary') // remove class: secondary
        $('#buttonStartVideoExec').addClass('btn-danger') // add class: danger
        // Button: Video
        $('#buttonStartVideo').prop('disabled', false)
        $('#buttonStartVideo').removeClass('btn-secondary') // remove danger class
        $('#buttonStartVideo').addClass('btn-danger') // add class: danger
        // Button: AudioExec
        $('#buttonStartAudioExec').prop('disabled', false)
        $('#buttonStartAudioExec').removeClass('btn-secondary') // remove class: secondary
        $('#buttonStartAudioExec').addClass('btn-danger') // add class: danger

        utils.writeConsoleMsg('info', 'windowMainButtonsStartEnable ::: Did enable both start buttons')
    }
}

/**
* @function windowMainButtonsStartDisable
* @summary Disables the 2 start buttons
* @description Is executed when a download task is started by the user
*/
function windowMainButtonsStartDisable () {
    // disable start buttons - if needed
    if ($('#buttonStartVideoExec').is(':enabled')) {
        // Button: VideoExec
        $('#buttonStartVideoExec').prop('disabled', true)
        $('#buttonStartVideoExec').removeClass('btn-danger') // remove class: danger
        $('#buttonStartVideoExec').addClass('btn-secondary') // add class: secondary
        // Button: Video
        $('#buttonStartVideo').prop('disabled', true)
        $('#buttonStartVideo').removeClass('btn-danger') // remove danger class
        $('#buttonStartVideo').addClass('btn-secondary') // add class: secondary
        // Button: AudioExec
        $('#buttonStartAudioExec').prop('disabled', true)
        $('#buttonStartAudioExec').removeClass('btn-danger') // remove danger class
        $('#buttonStartAudioExec').addClass('btn-secondary') // add class: secondary

        utils.writeConsoleMsg('info', 'windowMainButtonsStartDisable ::: Did disable both start buttons')
    }
}

/**
* @function windowMainBlurSet
* @summary Can set a blur level for entire main ui
* @description Can set a blur level for entire main ui. Is used on the mainUI when the settingsUI is open
* @param {boolean} enable- To enable or disable blur
*/
function windowMainBlurSet (enable) {
    if (enable === true) {
        // mainContainer
        $('#mainContainer').css('filter', 'blur(2px)') // blur
        $('#mainContainer').css('pointer-events', 'none') // disable click events
        // titlebar
        $('.titlebar').css('filter', 'blur(2px)') // blur
        $('.titlebar').css('pointer-events', 'none') // disable click events
        utils.writeConsoleMsg('info', 'windowMainBlurSet ::: Enabled blur effect')
    } else {
        // mainContainer
        $('#mainContainer').css('filter', 'blur(0px)') // unblur
        $('#mainContainer').css('pointer-events', 'auto') // enable click-events
        // titlebar
        $('.titlebar').css('filter', 'blur(0px)') // unblur
        $('.titlebar').css('pointer-events', 'auto') // enable click-events
        utils.writeConsoleMsg('info', 'windowMainBlurSet ::: Disabled blur effect')
    }
}

/**
* @function windowMainIntroShow
* @summary start an intro / user tutorial
* @description Starts a short intro / tutorial which explains the user-interface. Using introJs
*/
function windowMainIntroShow () {
    utils.writeConsoleMsg('info', 'windowMainIntroShow ::: Starting the media-dupes intro')
    const introJs = require('intro.js')
    introJs().start()
}

/**
* @function windowMainLogAppend
* @summary Appends text to the log textarea
* @description Appends text to the log textarea
* @param {String} newLine - The content for the line which should be appended to the UI Log
*/
function windowMainLogAppend (newLine, addTimestamp = false) {
    if (addTimestamp === true) {
        var currentTimestamp = utils.generateTimestamp() // get a current timestamp
        newLine = currentTimestamp + ' > ' + newLine
    }

    $('#textareaLogOutput').val(function (i, text) {
        return text + newLine + '\n'
    })
    windowMainLogScrollToEnd() // scroll log textarea to the end
}

/**
* @function windowMainLogReset
* @summary Resets the ui log
* @description Resets the content of the ui log
*/
function windowMainLogReset () {
    document.getElementById('textareaLogOutput').value = ''
    utils.writeConsoleMsg('info', 'windowMainLogReset ::: Did reset the log-textarea')
}

/**
* @function windowMainLogScrollToEnd
* @summary Scrolls the UI log to the end
* @description Scrolls the UI log to the end / latest entry
*/
function windowMainLogScrollToEnd () {
    $('#textareaLogOutput').scrollTop($('#textareaLogOutput')[0].scrollHeight) // scroll log textarea to the end
}

/**
* @function windowMainResetAskUser
* @summary Ask the user if he wants to execute the UI reset function if there are currently downloads in progress
* @description Ask the user if he wants to execute the UI reset function if there are currently downloads in progress
*/
function windowMainResetAskUser () {
    var curState = utils.globalObjectGet('applicationState') // get application state
    if (curState === 'Download in progress') {
        const Noty = require('noty')
        var n = new Noty(
            {
                theme: 'bootstrap-v4',
                layout: 'bottom',
                type: 'info',
                closeWith: [''], // to prevent closing the confirm-dialog by clicking something other then a confirm-dialog-button
                text: 'Seems like <b>media-dupes</b> is currently downloading. Do you really want to reset the UI?',
                buttons: [
                    Noty.button('Yes', 'btn btn-success mediaDupes_btnDefaultWidth', function () {
                        n.close()
                        windowMainUiReset()
                    },
                    {
                        id: 'button1', 'data-status': 'ok'
                    }),
                    Noty.button('No', 'btn btn-secondary mediaDupes_btnDefaultWidth float-right', function () {
                        n.close()
                    })
                ]
            })

        n.show() // show the noty dialog
    } else {
        windowMainUiReset()
    }
}

/**
* @function windowMainOpenDownloadFolder
* @summary Triggers code in main.js to open the download folder of the user
* @description Triggers code in main.js to open the download folder of the user
*/
function windowMainOpenDownloadFolder () {
    const { ipcRenderer } = require('electron')
    var configuredDownloadFolder = utils.globalObjectGet('downloadDir')
    utils.writeConsoleMsg('info', 'windowMainOpenDownloadFolder ::: Seems like we should use the following dir: _' + configuredDownloadFolder + '_.')
    ipcRenderer.send('openUserDownloadFolder', configuredDownloadFolder)
}

/**
* @function windowMainSettingsUiLoad
* @summary Navigate to setting.html
* @description Is triggered via button on index.html. Calls method on main.js which loads setting.html to the application window
*/
function windowMainSettingsUiLoad () {
    const { ipcRenderer } = require('electron')
    windowMainBlurSet(true) // blur the main UI
    ipcRenderer.send('settingsUiLoad') // tell main.js to load settings UI
}

/**
* @function windowMainDownloadContent
* @summary Handles the download of audio and video
* @description Checks some requirements, then sets the youtube-dl parameter depending on the mode. Finally launched youtube-dl via exec
* @param {String} mode - The download mode. Can be 'video' or 'audio'
* @throws Exit code from youtube-dl exec task
*/
function windowMainDownloadContent (mode) {
    utils.writeConsoleMsg('info', 'downloadContent ::: Start with mode set to: _' + mode + '_.')

    // some example urls for tests
    //
    // VIDEO:
    // YOUTUBE:         http://www.youtube.com/watch?v=90AiXO1pAiA                      // 11 sec       less then 1 MB
    //                  https://www.youtube.com/watch?v=cmiXteWLNw4                     // 1 hour
    //                  https://www.youtube.com/watch?v=bZ_C-AVo5xA                     // for simulating download errors
    // VIMEO:           https://vimeo.com/315670384                                     // 48 sec       around 1GB
    //                  https://vimeo.com/274478457                                     // 6 sec        around 4MB
    //
    // AUDIO:
    // SOUNDCLOUD:      https://soundcloud.com/jperiod/rise-up-feat-black-thought-2
    // BANDCAMP:        https://nosferal.bandcamp.com/album/nosferal-ep-mini-album

    // What is the target dir
    var configuredDownloadFolder = utils.globalObjectGet('downloadDir')
    utils.writeConsoleMsg('info', 'windowMainDownloadContent ::: Download target directory is set to: _' + configuredDownloadFolder + '_.')

    if (utils.isDirectoryAvailable(configuredDownloadFolder)) {
        // the download folder exists

        // check if it is writeable
        if (utils.isDirectoryWriteable(configuredDownloadFolder)) {
            // Prepare UI
            windowMainButtonsStartDisable() // disable the start buttons
            windowMainButtonsOthersDisable() // disables some other buttons
            windowMainApplicationStateSet('Download in progress') // set the application state

            // require some stuff
            const youtubedl = require('youtube-dl')
            const path = require('path')

            var arrayUrlsProcessedSuccessfully = [] // prepare array for urls which got successfully downloaded
            var arrayUrlsThrowingErrors = [] // prepare array for urls which are throwing errors

            // youtube-dl
            utils.writeConsoleMsg('info', 'windowMainDownloadContent ::: Using youtube.dl from: _' + youtubeDl.binaryPathGet() + '_.')

            // ffmpeg
            var ffmpegPath = ffmpeg.ffmpegGetBinaryPath()
            utils.writeConsoleMsg('info', 'windowMainDownloadContent ::: Detected bundled ffmpeg at: _' + ffmpegPath + '_.')

            // Check if todoArray exists otherwise abort and throw error. See: MEDIA-DUPES-J
            if (typeof arrayUserUrlsN === 'undefined' || !(arrayUserUrlsN instanceof Array)) {
                windowMainApplicationStateSet()
                utils.showNoty('error', 'Unexpected state of array _arrayUserUrlsN_ in function downloadContent(). Please report this', 0)
                return
            } else {
                utils.globalObjectSet('todoListStateEmpty', false)
            }

            // Define the youtube-dl parameters depending on the mode (audio vs video)
            // youtube-dl docs: https://github.com/ytdl-org/youtube-dl/blob/master/README.md
            var youtubeDlParameter = []
            switch (mode) {
            case 'audio':
                var settingAudioFormat = utils.globalObjectGet('audioFormat') // get configured audio format

                // generic parameter / flags
                youtubeDlParameter = [
                    // OPTIONS
                    '--ignore-errors', // Continue on download errors, for example to skip unavailable videos in a playlist
                    '--format', 'bestaudio',
                    '--output', path.join(configuredDownloadFolder, 'Audio', '%(track_number)s-%(artist)s-%(album)s-%(title)s-%(id)s.%(ext)s'), // output path
                    // FILESYSTEM OPTIONS
                    '--restrict-filenames', // Restrict filenames to only ASCII characters, and avoid "&" and spaces in filenames
                    '--continue', // Force resume of partially downloaded files. By default, youtube-dl will resume downloads if possible.
                    // POST PROCESSING
                    '--prefer-ffmpeg', '--ffmpeg-location', ffmpegPath, // ffmpeg location
                    '--add-metadata', // Write metadata to the video file
                    '--audio-format', settingAudioFormat, //  Specify audio format: "best", "aac", "flac", "mp3", "m4a", "opus", "vorbis", or "wav"; "best" by default; No effect without -x
                    '--extract-audio', // Convert video files to audio-only files (requires ffmpeg or avconv and ffprobe or avprobe)
                    '--audio-quality', '0', // value between 0 (better) and 9 (worse) for VBR or a specific bitrate like 128K (default 5),
                    '--fixup', 'detect_or_warn' // Automatically correct known faults of the file.
                ]

                // prepend/add some case-specific parameter / flag
                if ((settingAudioFormat === 'mp3') || (settingAudioFormat === 'm4a')) {
                    youtubeDlParameter.unshift('--embed-thumbnail') // prepend Post-processing Option
                }
                break

            case 'video':
                youtubeDlParameter = [
                    // OPTIONS
                    '--ignore-errors', // Continue on download errors, for example to skip unavailable videos in a playlist
                    '--format', 'best',
                    '--output', path.join(configuredDownloadFolder, 'Video', '%(title)s-%(id)s.%(ext)s'), // output path
                    // FILESYSTEM OPTIONS
                    '--restrict-filenames', // Restrict filenames to only ASCII characters, and avoid "&" and spaces in filenames
                    '--continue', // Force resume of partially downloaded files. By default, youtube-dl will resume downloads if possible.
                    // POST PROCESSING
                    '--prefer-ffmpeg', '--ffmpeg-location', ffmpegPath, // ffmpeg location
                    '--add-metadata', // Write metadata to the video file
                    '--audio-quality', '0', // value between 0 (better) and 9 (worse) for VBR or a specific bitrate like 128K (default 5)
                    '--fixup', 'detect_or_warn' // Automatically correct known faults of the file.
                ]
                break

            default:
                windowMainApplicationStateSet()
                utils.writeConsoleMsg('error', 'windowMainDownloadContent ::: Unspecified mode. This should never happen.')
                utils.showNoty('error', 'Unexpected download mode. Please report this issue', 0)
                return
            }

            windowMainLogAppend('\n') // Show mode in log
            windowMainLogAppend('### QUEUE STARTED ###', true) // Show mode in log
            windowMainLogAppend('Download mode:\t' + mode, true) // Show mode in log

            // show the selected audio-format
            if (mode === 'audio') {
                utils.writeConsoleMsg('info', 'windowMainDownloadContent ::: AudioFormat is set to: _' + settingAudioFormat + '_')
                windowMainLogAppend('Audio-Format:\t' + settingAudioFormat, true) // Show mode in log
            }

            // if verboseMode is enabled - append the related youtube-dl flags to the parameter array
            var settingVerboseMode = utils.globalObjectGet('enableVerboseMode')
            if (settingVerboseMode === true) {
                utils.writeConsoleMsg('info', 'windowMainDownloadContent ::: Verbose Mode is enabled')
                youtubeDlParameter.unshift('--verbose') // prepend: verbose
                youtubeDlParameter.unshift('--print-traffic') // prepend: traffic (header)
            } else {
                utils.writeConsoleMsg('info', 'windowMainDownloadContent ::: Verbose Mode is disabled')
            }
            windowMainLogAppend('Verbose mode:\t' + settingVerboseMode, true) // Show verbose mode in log

            // if configured - add custom additional parameter to parameter array - see #88
            var isAdditionalParameterEnabled = utils.globalObjectGet('enableAdditionalParameter')
            if (isAdditionalParameterEnabled === true) {
                var configuredAdditionalParameter = utils.globalObjectGet('additionalYoutubeDlParameter')
                var splittedParameters = configuredAdditionalParameter.split(' ')
                windowMainLogAppend('Added parameters:\t' + splittedParameters, true) // Show mode in log

                if (splittedParameters.length > 0) { // append custom parameter to parameter array
                    for (var j = 0; j < splittedParameters.length; j++) {
                        youtubeDlParameter.push(splittedParameters[j])
                        utils.writeConsoleMsg('info', 'windowMainDownloadContent ::: Appending custom parameter: _' + splittedParameters[j] + '_ to the youtube-dl parameter set.')
                    }
                }
            }

            // update UI
            windowMainSetReadOnly()

            // assuming we got an array with urls to process
            // for each item of the array ... try to start a download-process
            var url
            for (var i = 0; i < arrayUserUrlsN.length; i++) {
                sentry.countEvent('usageURLsOverall')
                url = utils.fullyDecodeURI(arrayUserUrlsN[i]) // decode url - see #25
                utils.writeConsoleMsg('info', 'windowMainDownloadContent ::: Added URL: _' + url + '_ (' + mode + ') with the following parameters: _' + youtubeDlParameter + '_ to the queue.')
                // windowMainLogAppend('Added: \t\t' + url + ' to queue', true) // append url to log

                // Check if url is a playlist (example: https://www.youtube.com/playlist?list=PL53E6B270F5FF0D49 )
                if ((url.includes('playlist')) && (url.includes('list='))) {
                    windowMainLogAppend('Added: \t\t' + url + ' to queue (might be a playlist)', true) // append url to log
                } else {
                    windowMainLogAppend('Added: \t\t' + url + ' to queue', true) // append url to log
                }

                // Download
                //
                // const newDownload = youtubedl.exec(url, youtubeDlParameter, {}, function (error, output) {
                youtubedl.exec(url, youtubeDlParameter, {}, function (error, output) {
                    if (error) {
                        sentry.countEvent('usageURLsFailed')
                        utils.showNoty('error', '<b>Download failed</b><br><br>' + error + '<br><br><small><b><u>Common causes</u></b><br>* youtube-dl does not support this url. Please check the list of extractors<br>* Using old version of media-dupes<br>* Using old version of youtube-dl<br>* Country-/ and/or similar restrictions</small>', 0)
                        utils.writeConsoleMsg('error', 'windowMainDownloadContent ::: Problems downloading an url with the following parameters: _' + youtubeDlParameter + '_. Error: ' + error)
                        windowMainLogAppend('Failed to download a single url', true)
                        arrayUrlsThrowingErrors.push(url) // remember troublesome url (Attention: this is not the actual url . we got a problem here)
                        utils.downloadStatusCheck(arrayUserUrlsN.length, arrayUrlsProcessedSuccessfully.length, arrayUrlsThrowingErrors.length) // check if we are done here
                        throw error
                    }

                    // no error occured for this url - assuming the download finished
                    //
                    arrayUrlsProcessedSuccessfully.push(url)
                    utils.writeConsoleMsg('info', output.join('\n')) // Show processing output for this download task
                    windowMainLogAppend(output.join('\n')) // Show processing output for this download task

                    if (arrayUserUrlsN.length > 1) {
                        utils.showNoty('success', 'Finished 1 download') // inform user
                    }

                    utils.downloadStatusCheck(arrayUserUrlsN.length, arrayUrlsProcessedSuccessfully.length, arrayUrlsThrowingErrors.length) // check if we are done here

                    // FIXME:
                    //
                    // the variable URL contains in both cases the last processed url
                    // thats why we can't show the actual url in the log
                })
            }
        }
    }
}

/**
* @function windowMainSetReadOnly
* @summary ensures that the user can not add or remove items to the todo-list while the app is processing an existing todo list
* @description ensures that the user can not add or remove items to the todo-list while the app is processing an existing todo list
*/
function windowMainSetReadOnly () {
    $('#buttonAddUrl').prop('disabled', true) // disable the add url button
    $('#inputNewUrl').prop('disabled', true) // url input field

    // make sure the user can no longer remove items from the todolist
    $('#delete').prop('disabled', true) // disable all delete buttons for each url
}

/**
* @function windowMainDownloadVideo
* @summary Does the actual video download
* @description Does the actual video download (without using youtube-dl.exec)
*/
function windowMainDownloadVideo () {

    // some example urls for tests
    //
    // VIDEO:
    // YOUTUBE:         http://www.youtube.com/watch?v=90AiXO1pAiA                      // 11 sec       less then 1 MB
    //                  https://www.youtube.com/watch?v=cmiXteWLNw4                     // 1 hour
    //                  https://www.youtube.com/watch?v=bZ_C-AVo5xA                     // for simulating download errors
    // VIMEO:           https://vimeo.com/315670384                                     // 48 sec       around 1GB
    //                  https://vimeo.com/274478457                                     // 6 sec        around 4MB
    //
    // AUDIO:
    // SOUNDCLOUD:      https://soundcloud.com/jperiod/rise-up-feat-black-thought-2
    // BANDCAMP:        https://nosferal.bandcamp.com/album/nosferal-ep-mini-album

    // FIXME:
    // This method now seems to work good for youtube urls
    // BUT not for non-youtube urls
    // media-dupes is currently not using this function

    /*
    var configuredDownloadFolder = utils.globalObjectGet('downloadDir') // What is the target dir
    utils.writeConsoleMsg('info', 'windowMainDownloadVideo ::: Download target directory is set to: _' + configuredDownloadFolder + '_.')

    if (utils.isDirectoryAvailable(configuredDownloadFolder)) {
        // the default download folder exists

        if (utils.isDirectoryWriteable(configuredDownloadFolder)) {
            // check if it is writeable

            // Prepare UI
            windowMainButtonsStartDisable() // disable the start buttons
            windowMainButtonsOthersDisable() // disables some other buttons
            windowMainLoadingAnimationShow() // start download animation / spinner

            // require some stuff
            const youtubedl = require('youtube-dl')
            const path = require('path')
            const fs = require('fs')

            // ffmpeg
            var ffmpegPath = ffmpeg.ffmpegGetBinaryPath()
            utils.writeConsoleMsg('info', 'windowMainDownloadVideo ::: Detected bundled ffmpeg at: _' + ffmpegPath + '_.')

            var youtubeDlParameter = ''

            youtubeDlParameter = [
                // OPTIONS
                '--ignore-errors', // Continue on download errors, for example to skip unavailable videos in a playlist
                //'--format=best',
                '--output', path.join(configuredDownloadFolder, 'Video', '%(title)s-%(id)s.%(ext)s'), // output path
                // FILESYSTEM OPTIONS
                '--restrict-filenames', // Restrict filenames to only ASCII characters, and avoid "&" and spaces in filenames
                '--continue', // Force resume of partially downloaded files. By default, youtube-dl will resume downloads if possible.
                // VERBOSE
                '--print-json',
                // POST PROCESSING
                '--prefer-ffmpeg', '--ffmpeg-location=' + ffmpegPath, // ffmpeg location
                '--add-metadata', // Write metadata to the video file
                ///'--audio-quality', '0', // value between 0 (better) and 9 (worse) for VBR or a specific bitrate like 128K (default 5)
                //'--fixup', 'detect_or_warn' // Automatically correct known faults of the file.
            ]

            // Check if todoArray exists otherwise abort and throw error. See: MEDIA-DUPES-J
            if (typeof arrayUserUrlsN === 'undefined' || !(arrayUserUrlsN instanceof Array)) {
                utils.showNoty('error', 'Unexpected state of array _arrayUserUrlsN_ in function downloadVideo(). Please report this', 0)
                return
            }

            utils.writeConsoleMsg('info', 'windowMainDownloadVideo ::: Using youtube.dl: _' + youtubeDl.binaryPathGet() + '_.')

            var arrayUrlsThrowingErrors = [] // prepare array for urls which are throwing errors
            var arrayUrlsProcessedSuccessfully = []
            var downloadUrlTargetName = []

            // assuming we got an array with urls to process
            // for each item of the array ... try to start a download-process
            var arrayLength = arrayUserUrlsN.length
            windowMainLogAppend('Queue contains ' + arrayLength + ' urls.')
            windowMainLogAppend('Starting to download items from queue ... ')
            for (var i = 0; i < arrayLength; i++) {
                var url = arrayUserUrlsN[i] // get url

                // decode url - see #25
                //
                // url = decodeURI(url);
                url = utils.fullyDecodeURI(url)

                windowMainLogAppend('Starting to process the url: ' + url + ' ...')
                utils.writeConsoleMsg('info', 'windowMainDownloadVideo ::: Processing URL: _' + url + '_.') // show url
                utils.writeConsoleMsg('info', 'windowMainDownloadVideo ::: Using the following parameters: _' + youtubeDlParameter + '_.') // show parameters

                const video = youtubedl(url, youtubeDlParameter)

                // Variables for progress of each download
                let size = 0
                let pos = 0
                let progress = 0

                video.on('error', function error(error) {
                    //console.log(error)
                    utils.showNoty('error', '<b>Download failed</b><br><br>' + error + '<br><br><small><b><u>Common causes</u></b><br>* youtube-dl does not support this url. Please check the list of extractors<br>* Country-/ and/or similar restrictions</small>', 0)
                    utils.writeConsoleMsg('error', 'windowMainDownloadContent ::: Problems downloading an url with the following parameters: _' + youtubeDlParameter + '_. Error: ' + error)
                    windowMainLogAppend('Failed to download a single url', true)
                    throw error
                })

                // When the download fetches info - start writing to file
                //
                video.on('info', function (info) {
                    // downloadUrlTargetName[i] = path.join(configuredDownloadFolder, 'Video', info._filename) // define the final name & path
                    downloadUrlTargetName[i] = info._filename // define the final name & path

                    size = info.size // needed to handle the progress later on('data'

                    console.log('filename: ' + info._filename)
                    windowMainLogAppend('Filename: ' + info._filename)

                    console.log('size: ' + info.size)
                    windowMainLogAppend('Size: ' + utils.formatBytes(info.size))

                    // start the actual download & write to file
                    var writeStream = fs.createWriteStream(downloadUrlTargetName[i])
                    video.pipe(writeStream)
                })

                // updating progress
                //
                video.on('data', (chunk) => {
                    // console.log('Getting another chunk: _' + chunk.length + '_.')
                    pos += chunk.length
                    if (size) {
                        progress = (pos / size * 100).toFixed(2) // calculate progress
                        console.log('Download-progress is: _' + progress + '_.')
                        windowMainLogAppend('Download progress: ' + progress + '%')
                    }
                })

                // If download was already completed and there is nothing more to download.
                //
                video.on('complete', function complete (info) {
                    console.warn('filename: ' + info._filename + ' already downloaded.')
                    windowMainLogAppend('Filename: ' + info._filename + ' already downloaded.')
                })

                // Download finished
                //
                video.on('end', function () {
                    console.log('Finished downloading 1 url')

                    windowMainLogAppend('Finished downloading url')
                    arrayUrlsProcessedSuccessfully.push(url)

                    // if all downloads finished
                    if (arrayUrlsProcessedSuccessfully.length === arrayLength) {
                        utils.showNotification('Finished downloading ' + arrayUrlsProcessedSuccessfully.length + ' url(s). Queue is now empty.')
                        windowMainLogAppend('Finished downloading ' + arrayUrlsProcessedSuccessfully.length + ' url(s). Queue is now empty.', true)
                        windowMainUiMakeUrgent() // mark mainWindow as urgent to inform the user about the state change
                        windowMainLoadingAnimationHide() // stop download animation / spinner
                        windowMainButtonsOthersEnable() // enable some of the buttons again
                    }
                })
            }
        }
    }
    */
}

/**
* @function windowMainAddUrl
* @summary Handles the add-url-click of the user
* @description Fetches the user provided url, trims it and adds it to an array. Is then calling todoListUpdate()
*/
function windowMainAddUrl () {
    var newUrl = $('#inputNewUrl').val() // get content of input
    newUrl = newUrl.trim() // trim the url to remove blanks

    if (newUrl !== '') {
        var isUrlValid = utils.validURL(newUrl)
        if (isUrlValid) {
            // check if url is already in arrayUserUrlsN - If not add it to table
            var isThisUrlAlreadyPartOfList = arrayUserUrlsN.includes(newUrl)
            if (isThisUrlAlreadyPartOfList === false) {
                utils.writeConsoleMsg('info', 'windowMainAddUrl ::: Adding new url: _' + newUrl + '_.')
                $('#inputNewUrl').val('') // reset input
                inputUrlFieldSetState() // empty = white
                arrayUserUrlsN.push(newUrl) // append to array
                toDoListSingleUrlAdd(newUrl) // #102
            } else {
                utils.showNoty('warning', 'This url is already part of the current todo list')
            }

            // if array size > 0 -> enable start button
            var arrayLength = arrayUserUrlsN.length
            if (arrayLength === 0) {
                utils.globalObjectSet('todoListStateEmpty', true)
            } else {
                windowMainButtonsStartEnable()
                utils.globalObjectSet('todoListStateEmpty', false)
            }
        } else {
            // invalid url
            utils.writeConsoleMsg('warn', 'windowMainAddUrl ::: Detected invalid url: _' + newUrl + '_.')
            utils.showNoty('warning', 'Please insert a valid url (reason: unable to dectect a valid url)')
            $('#inputNewUrl').focus() // focus to input field
            $('#inputNewUrl').select() // select it entirely
        }
    } else {
        // empty
        utils.writeConsoleMsg('warn', 'windowMainAddUrl ::: Detected empty url.')
        utils.showNoty('warning', 'Please insert a valid url (reason: was empty)')
        $('#inputNewUrl').focus() // focus to input field
    }
}

/**
* @function windowMainDistract
* @summary Starts the distraction mode
* @description Sends a request to main.js to start the "hidden" distraction mode (easteregg)
*/
function windowMainDistract () {
    const { ipcRenderer } = require('electron')
    distractEnabler = distractEnabler + 1
    utils.writeConsoleMsg('info', 'distract ::: Enabler is now: ' + distractEnabler)
    if (distractEnabler === 3) {
        sentry.countEvent('usageTetris')
        distractEnabler = 0 // reset the counter
        utils.writeConsoleMsg('info', 'distract ::: Init some distraction')
        ipcRenderer.send('startDistraction') // tell main.js to load distraction UI
    }
}

/**
* @function windowMainDownloadQueueFinished
* @summary Re-setups the main UI post downloads
* @description Gets triggered after the download function finished
*/
function windowMainDownloadQueueFinished () {
    windowMainUiMakeUrgent() // mark mainWindow as urgent to inform the user about the state change
    windowMainLoadingAnimationHide() // stop download animation / spinner
    windowMainButtonsOthersEnable() // enable some of the buttons again
    windowMainApplicationStateSet() // reset application state
    windowMainDisablePowerSaveBlocker() // disabled the power save blocker

    utils.globalObjectSet('todoListStateEmpty', true)
}

/**
* @function windowMainUiReset
* @summary Resets the UI back to default
* @description Resets the UI back to default
*/
function windowMainUiReset () {
    utils.writeConsoleMsg('info', 'windowMainUiReset ::: Starting to reset the UI')

    // call reload of the mainWindow
    const { ipcRenderer } = require('electron')
    ipcRenderer.send('reloadMainWindow')
}

/**
* @function windowMainToDoListRestore
* @summary Restores urls from json files back to the todoList
* @description Reads all existing json files and restores those contains preiosly stored urls.
*/
function windowMainToDoListRestore () {
    const storage = require('electron-json-storage')
    // utils.writeConsoleMsg('info', 'windowMaintoDoListRestore ::: Check if there are urls to restore ...')

    var curUrl
    var restoreCounter = 0

    storage.getAll(function (error, data) {
        if (error) {
            utils.writeConsoleMsg('error', 'windowMaintoDoListRestore ::: Failed to fetch all json files. Error: ' + error)
            throw error
        }
        // console.error(data); // object with all setting files

        // loop over them and find each which starts with todoListEntry_
        for (var key in data) {
            // utils.writeConsoleMsg('info', 'windowMaintoDoListRestore ::: Current config file: _' + key + '_.') // key = name of json file

            // FIXME:
            // Check for better approach:
            // https://eslint.org/docs/rules/no-prototype-builtins

            if (key.startsWith('todoListEntry_')) {
                curUrl = data[key].url
                utils.writeConsoleMsg('info', 'windowMaintoDoListRestore ::: Restoring the url: _' + curUrl + '_ from settings file: _' + key + '_.') // key = name of json file

                arrayUserUrlsN.push(curUrl) // append to array
                toDoListSingleUrlAdd(curUrl)
                restoreCounter = restoreCounter + 1 // update url-restore counter

                // Delete the related file
                storage.remove(key, function (error) {
                    if (error) {
                        utils.writeConsoleMsg('info', 'windowMaintoDoListRestore ::: Failed to delete a url restore file: _' + key + '_.') // key = name of json file
                        throw error
                    }
                })
            }
        }

        // inform the user if something got restored
        if (restoreCounter > 0) {
            utils.writeConsoleMsg('info', 'windowMaintoDoListRestore ::: Finished restored ' + restoreCounter + ' URLs from previous session.')
            utils.showNoty('success', 'Restored <b>' + restoreCounter + '</b> URLs from your last session.')

            // get focus
            const { ipcRenderer } = require('electron')
            ipcRenderer.send('makeWindowUrgent')

            utils.globalObjectSet('todoListStateEmpty', false) // remember that todo list is now not longer empty - see #105
            windowMainButtonsStartEnable() // enable the start buttons
        } else {
            utils.writeConsoleMsg('info', 'windowMaintoDoListRestore ::: Found no urls to restore from previous session.')
        }
    })
}

/**
* @function windowMainToDoListSave
* @summary Saves the current content of the todoList to files
* @description Creates a json file for each url of the todoList. Those files can be restored on next application launch
*/
function windowMainToDoListSave () {
    const storage = require('electron-json-storage')
    utils.writeConsoleMsg('info', 'windowMainToDoListSave ::: Should backup todolist')

    var isToDoListEmpty = utils.globalObjectGet('todoListStateEmpty') // #79
    if (isToDoListEmpty === false) {
        var baseName = 'todoListEntry_'
        var tempName
        var arrayLength = arrayUserUrlsN.length
        for (var i = 0; i < arrayLength; i++) {
            var url = arrayUserUrlsN[i]
            tempName = baseName + i
            utils.writeConsoleMsg('info', 'windowMainToDoListSave ::: Trying to save the URL: ' + url + '_ to the file: _' + tempName + '_.')

            storage.set(tempName, { url: url }, function (error) {
                if (error) {
                    utils.writeConsoleMsg('error', 'Error writing json file')
                    throw error
                }
                utils.writeConsoleMsg('info', 'windowMainToDoListSave ::: Written the url _' + url + '_ to the file: _' + tempName + '_.')
            })
        }
        utils.writeConsoleMsg('info', 'windowMainToDoListSave ::: Finished saving todoList content to files.')
    } else {
        utils.writeConsoleMsg('info', 'windowMainToDoListSave ::: todoList was empty - nothing to store.') // #79
    }
}

/**
* @function windowMainUiMakeUrgent
* @summary Tells the main process to mark the application as urgent (blinking in task manager)
* @description Is used to inform the user about an important state-change (all downloads finished). Triggers code in main.js which does the actual work
*/
function windowMainUiMakeUrgent () {
    const { ipcRenderer } = require('electron')
    ipcRenderer.send('makeWindowUrgent') // make window urgent after having finished downloading. See #7
}

/**
* @function windowMainEnableAddUrlButton
* @summary Enables the AddUrlButton
* @description Enables the AddUrlButton if it isnt already
*/
function windowMainEnableAddUrlButton () {
    if ($('#buttonAddUrl').is(':disabled')) {
        $('#buttonAddUrl').prop('disabled', false) // enable the add url button
        utils.writeConsoleMsg('info', 'windowMainEnableAddUrlButton ::: Enabled the add URL button.')
    }
}

/**
* @function windowMainDisableAddUrlButton
* @summary Disables the AddUrlButton
* @description Disabled the AddUrlButton if it isnt already
*/
function windowMainDisableAddUrlButton () {
    if ($('#buttonAddUrl').is(':enabled')) {
        $('#buttonAddUrl').prop('disabled', true) // disable the add url button
        utils.writeConsoleMsg('info', 'windowMainDisableAddUrlButton ::: Disabled the add URL button.')
    }
}

/**
* @function inputUrlFieldSetState
* @summary Sets the background color of the url input field based on the given state
* @description Sets the background color of the url input field based on the given state
* @param {String} state - Known states or 'reachable', 'unchecked', 'unreachable' and default/any else
*/
function inputUrlFieldSetState (state) {
    switch (state) {
    case 'valid':
        $('#inputNewUrl').css('background-color', '#90EE90') // green
        windowMainEnableAddUrlButton()
        break
    case 'unchecked':
        $('#inputNewUrl').css('background-color', '#ffe5e5') //  light red
        windowMainDisableAddUrlButton()
        break
    default:
        $('#inputNewUrl').css('background-color', '#FFFFFF') // white
        windowMainDisableAddUrlButton()
        break
    }
}

/**
* @function youtubeSuggest
* @summary Opens a dialog to ask for a user string. Is then using the string to get youtube suggestions for the string
* @description Opens a dialog to ask for a user string. Is then using the string to get youtube suggestions for the string. Results are appended to the log
*/
function youtubeSuggest () {
    const prompt = require('electron-prompt')

    const youtubeSearchUrlBase = 'https://www.youtube.com/results?search_query='
    var url
    var curResult

    prompt({
        title: 'YouTube Search Suggestions',
        label: 'Searchphrase:',
        value: '',
        inputAttrs: {
            type: 'text'
        },
        type: 'input'
    })
        .then((r) => {
            if (r === null) {
                utils.writeConsoleMsg('warn', 'youtubeSuggest ::: User aborted input dialog')
            } else {
                if ((r !== null) && (r !== '')) {
                    windowMainLoadingAnimationShow()
                    utils.writeConsoleMsg('info', 'youtubeSuggest ::: User search string is: _' + r + '_.')
                    windowMainLogAppend('Searching for Youtube suggestions for the searchphase: ' + r, true)

                    var youtubeSuggest = require('youtube-suggest')
                    var assert = require('assert')

                    youtubeSuggest(r).then(function (results) {
                        assert(Array.isArray(results))

                        if (results.length > 0) { // if we got suggestions
                            assert(typeof results[0] === 'string')

                            // Loop over array and append to log
                            for (var i = 0; i < results.length; i++) {
                                curResult = results[i].replace(/ /g, '+') // replace space with +
                                url = youtubeSearchUrlBase + curResult
                                windowMainLogAppend('> ' + results[i] + ' : ' + url)
                            }
                            windowMainLogAppend('Finished searching for Youtube suggestions for the search phrase: ' + r + '\n', true)
                            windowMainLoadingAnimationHide()

                            // ask user if he wants to open all those urls
                            const Noty = require('noty')
                            var n = new Noty(
                                {
                                    theme: 'bootstrap-v4',
                                    layout: 'bottom',
                                    type: 'info',
                                    closeWith: [''], // to prevent closing the confirm-dialog by clicking something other then a confirm-dialog-button
                                    text: 'Do you want to open all suggestions in your browser?',
                                    buttons: [
                                        Noty.button('Yes', 'btn btn-success mediaDupes_btnDefaultWidth', function () {
                                            n.close()

                                            // loop over array and call openURL
                                            for (var i = 0; i < results.length; i++) {
                                                curResult = results[i].replace(/ /g, '+') // replace space with +
                                                url = youtubeSearchUrlBase + curResult
                                                utils.openURL(url)
                                            }
                                        },
                                        {
                                            id: 'button1', 'data-status': 'ok'
                                        }),
                                        Noty.button('No', 'btn btn-secondary mediaDupes_btnDefaultWidth float-right', function () {
                                            n.close()
                                        })
                                    ]
                                })

                            n.show() // show the noty dialog
                        } else {
                            utils.showNoty('warning', '<b>Warning:</b> Unable to get suggestions for this search phrase')
                            windowMainLogAppend('Finished searching for Youtube suggestions for the searchphase: ' + r + ' without results', true)
                            windowMainLoadingAnimationHide()
                        }
                    })
                    // end suggest
                } else {
                    utils.showNoty('warning', '<b>Warning:</b> Unable to search suggestions without search string')
                }
            }
        })
        .catch(console.error)
}

/**
* @function windowMainDisablePowerSaveBlocker
* @summary Disable the power save blocker
* @description RDisable the power save blocker
*/
function windowMainDisablePowerSaveBlocker () {
    utils.writeConsoleMsg('info', 'windowMainDisablePowerSaveBlocker ::: Check if there is a PowerSaveBlocker enabled, if so try to disable it.')
    var currentPowerSaveBlockerStatus = utils.globalObjectGet('powerSaveBlockerEnabled')

    if (currentPowerSaveBlockerStatus === true) {
        var currentPowerSaveBlockerId = utils.globalObjectGet('powerSaveBlockerId') // get the id
        if (currentPowerSaveBlockerId !== -1) {
            utils.writeConsoleMsg('info', 'windowMainDisablePowerSaveBlocker ::: Trying to disable the PowerSaveBlocker with the ID _' + currentPowerSaveBlockerId + '_ now, as media-dupes is not longer downloading.')
            const { ipcRenderer } = require('electron')
            ipcRenderer.send('disablePowerSaveBlocker', currentPowerSaveBlockerId)
        }
    } else {
        utils.writeConsoleMsg('info', 'windowMainDisablePowerSaveBlocker ::: PowerSaveBlocker was not enabled, so nothing to do here.')
    }
}

/**
* @function toDoListSingleUrlRemove
* @summary Remove a single url from todo list array
* @description Remove a single url from todo list array
* @param {String} url - The url which should be removed from the todo array
*/
function toDoListSingleUrlRemove (url) {
    const index = arrayUserUrlsN.indexOf(url)
    // console.error(arrayUserUrlsN)
    // console.error(url)
    if (index > -1) {
        arrayUserUrlsN.splice(index, 1)
        utils.writeConsoleMsg('info', 'toDoListSingleUrlRemove ::: Removed the url from the todo list array.')
    } else {
        utils.writeConsoleMsg('error', 'toDoListSingleUrlRemove ::: Unable to remove the url from the todo list array.')
    }

    // check array size (to ensure saving & restoring urls works as expected)
    if (arrayUserUrlsN.length === 0) {
        utils.globalObjectSet('todoListStateEmpty', true)
        windowMainButtonsStartDisable() // disable start buttons
    } else {
        utils.globalObjectSet('todoListStateEmpty', false)
        windowMainButtonsStartEnable() // Enable Start buttons
    }
}

/**
* @function truncateString
* @summary Truncate a string
* @description Truncate a string
* @param {String} str - The string which should be truncated
* @param {number} num - The length after which the string should get truncated
*/
function truncateString (str, num) {
    utils.writeConsoleMsg('info', 'truncateString ::: Truncating _' + str + '_ after ' + num + ' chars.')

    // remove http/https
    str = str.replace('https://', '')
    str = str.replace('http://', '')

    // If the length of str is less than or equal to num just return str--don't truncate it.
    if (str.length <= num) {
        return str
    }
    return str.slice(0, num) + '...' // Return str truncated with '...' concatenated to the end of str.
}

// #102
/**
* @function toDoListSingleUrlAdd
* @summary Add a single row to the dataTable
* @description Add a single row to the dataTable
* @param {String} url - The url which should be added to the datatable todo list
*/
function toDoListSingleUrlAdd (url) {
    windowMainLoadingAnimationShow() // show that something is currently happening

    var urlId = utils.generateUrlId(url) // generate an id for this url
    var shortUrl = truncateString(url, 40)

    // add new row to datatable (with the most important informations)
    //
    var table = $('#example').DataTable()
    table.row.add([
        urlId, // ID
        '<div class="spinner-border spinner-border-sm text-secondary" role="status"><span class="sr-only">Loading...</span></div>', // logo
        '<div class="spinner-border spinner-border-sm text-secondary" role="status"><span class="sr-only">Loading...</span></div>', // preview
        '<button type="button" id="play" name="play" class="btn btn-sm btn-outline-secondary" disabled><i class="fas fa-play"></i></button>', // Play
        shortUrl, // url
        url,
        '', // state
        '<button type="button" id="delete" name="delete" class="btn btn-sm btn-outline-danger disabled"><i class="fas fa-trash-alt"></i></button>' // remove
    ]).draw(false)

    utils.writeConsoleMsg('info', 'toDoListSingleUrlAdd ::: Added the url _' + url + '_ to the todo-list (dataTable).')

    //  The following code is now moved to seperate function: startMetaScraper()
    // Now try to fetch meta informations about the url using: metascraper
    //
    const metascraper = require('metascraper')([
        require('metascraper-video')(),
        require('metascraper-description')(),
        require('metascraper-image')(),
        require('metascraper-audio')(),
        require('metascraper-logo')(),
        require('metascraper-logo-favicon')(),
        require('metascraper-media-provider')(),
        require('metascraper-soundcloud')(),
        require('metascraper-title')(),
        require('metascraper-youtube')()
    ])

    var urlLogo
    var urlImage
    var urlDescription
    var urlTitle
    var urlAudio

    const got = require('got')

    const targetUrl = url

    ;(async () => {
        const { body: html, url } = await got(targetUrl)
        const metadata = await metascraper({ html, url })

        // console.log(metadata) // show all detected metadata
        utils.writeConsoleMsg('info', 'toDoListSingleUrlAdd ::: Scraped the following metadata: ', metadata)

        urlLogo = metadata.logo
        urlImage = metadata.image
        urlDescription = metadata.description
        urlTitle = metadata.title
        urlAudio = metadata.audio

        // find the new datatable record
        //
        var indexes = table.rows().eq(0).filter(function (rowIdx) {
            return table.cell(rowIdx, 0).data() === urlId
        })
        // console.error(indexes)
        // console.error(indexes[0])

        // update the previously generated new row
        //
        // logo
        if (urlLogo == null) {
            urlLogo = 'img/dummy/dummy.png'
        }
        if (urlTitle == null) {
            urlTitle = 'No url title'
        }
        table.cell({ row: indexes[0], column: 1 }).data('<img class="zoomSmall" src="' + urlLogo + '" width="30" title="' + urlTitle + '" onclick=utils.openURL("' + url + '");>')

        // preview
        //
        if (urlImage == null) {
            urlImage = 'img/dummy/dummy.png'
        }
        if (urlDescription == null) {
            urlDescription = 'No url description available'
        }
        table.cell({ row: indexes[0], column: 2 }).data('<img class="zoomSmall" src="' + urlImage + '" width="30" title="' + urlDescription + '"  onclick=ui.imagePreviewModalShow("' + urlImage + '"); >')

        // play button
        //
        if (urlAudio == null) {
            // dont enable the button at all
        } else {
            table.cell({ row: indexes[0], column: 3 }).data('<button type="button" id="play" onclick=ui.playAudio("' + urlAudio + '"); name="play" class="btn btn-sm btn-outline-secondary"><i class="fas fa-play"></i></button>')
        }

        // button delete: enable the delete button (to prevent that the row gets removed while mediascrapper is fetching data
        //
        table.cell({ row: indexes[0], column: 7 }).data('<button type="button" id="delete" name="delete" class="btn btn-sm btn-outline-danger"><i class="fas fa-trash-alt"></i></button>')

        windowMainLoadingAnimationHide()
    })()
}

/**
* @function imagePreviewModalShow
* @summary Shows the preview modal dialog
* @description Shows the preview modal dialog which shows a big version of the image provided by the selected url
* @param {String} url - The url which to the image
*/
function imagePreviewModalShow (url) {
    utils.writeConsoleMsg('info', 'imagePreviewModalShow ::: Trying to show the preview image from _' + url + '_.')
    $('#imagePreview').attr('src', url) // set the image src
    $('#myModalImagePreview').modal('show') // show the modal
}

/**
* @function playAudio
* @summary Pre-listening to the audio stream
* @description  Plays 5 sec of audio as pre-listening
*/
function playAudio (url) {
    utils.writeConsoleMsg('info', 'playAudio ::: Trying to play 5 sec audio from source: _' + url + '_.')

    /*
    var a = new Audio(url)
    try {
        a.play() // start to play
        setTimeout(function () { // stop it again after 5 seconds
            a.pause()
        }, 5000)
    }
    catch(error) {
        utils.writeConsoleMsg('error', 'playAudio ::: Trying to play the audio failed. Error: ' + error)
    }
    */

    var a = new Audio(url)
    var playPromise = a.play()

    // In browsers that don’t yet support this functionality,
    // playPromise won’t be defined.
    if (playPromise !== undefined) {
        playPromise.then(function () {
            // Automatic playback started!
            setTimeout(function () { // stop it again after 5 seconds
                a.pause()
            }, 5000)
        }).catch(function (error) {
            // Automatic playback failed.
            // Show a UI element to let the user manually start playback.
            utils.showNoty('warning', 'Prelisting failed (no supported source was found)')
        })
    }
}

/**
* @function dataTablesReset
* @summary Reset the datatable
* @description  Reset the datatable
*/
function dataTablesReset () {
    var table = $('#example').DataTable()

    table
        .clear()
        .draw()

    arrayUserUrlsN = [] // reset the array
}

// ----------------------------------------------------------------------------
// EXPORT THE MODULE FUNCTIONS
// ----------------------------------------------------------------------------
//
module.exports.windowMainApplicationStateSet = windowMainApplicationStateSet
module.exports.windowMainLoadingAnimationShow = windowMainLoadingAnimationShow
module.exports.windowMainLoadingAnimationHide = windowMainLoadingAnimationHide
module.exports.windowMainButtonsOthersEnable = windowMainButtonsOthersEnable
module.exports.windowMainButtonsOthersDisable = windowMainButtonsOthersDisable
module.exports.windowMainButtonsStartEnable = windowMainButtonsStartEnable
module.exports.windowMainButtonsStartDisable = windowMainButtonsStartDisable
module.exports.windowMainBlurSet = windowMainBlurSet
module.exports.windowMainIntroShow = windowMainIntroShow
module.exports.windowMainLogAppend = windowMainLogAppend
module.exports.windowMainLogReset = windowMainLogReset
module.exports.windowMainLogScrollToEnd = windowMainLogScrollToEnd
module.exports.windowMainResetAskUser = windowMainResetAskUser
module.exports.windowMainOpenDownloadFolder = windowMainOpenDownloadFolder
module.exports.windowMainSettingsUiLoad = windowMainSettingsUiLoad
module.exports.windowMainDownloadContent = windowMainDownloadContent
module.exports.windowMainAddUrl = windowMainAddUrl
module.exports.windowMainDistract = windowMainDistract
module.exports.windowMainDownloadQueueFinished = windowMainDownloadQueueFinished
module.exports.windowMainUiReset = windowMainUiReset
module.exports.windowMainToDoListRestore = windowMainToDoListRestore
module.exports.windowMainToDoListSave = windowMainToDoListSave
module.exports.windowMainUiMakeUrgent = windowMainUiMakeUrgent
module.exports.windowMainSetReadOnly = windowMainSetReadOnly
module.exports.windowMainDownloadVideo = windowMainDownloadVideo
module.exports.windowMainEnableAddUrlButton = windowMainEnableAddUrlButton
module.exports.windowMainDisableAddUrlButton = windowMainDisableAddUrlButton
module.exports.inputUrlFieldSetState = inputUrlFieldSetState
module.exports.youtubeSuggest = youtubeSuggest
module.exports.windowMainDisablePowerSaveBlocker = windowMainDisablePowerSaveBlocker
module.exports.toDoListSingleUrlRemove = toDoListSingleUrlRemove // #102
module.exports.toDoListSingleUrlAdd = toDoListSingleUrlAdd // #102
module.exports.imagePreviewModalShow = imagePreviewModalShow
module.exports.playAudio = playAudio
module.exports.dataTablesReset = dataTablesReset
module.exports.truncateString = truncateString