Source: playlists.js

"use strict";
// SPDX-License-Identifier: GPL-3.0-or-later
// myMPD (c) 2018-2024 Juergen Mang <mail@jcgames.de>
// https://github.com/jcorporation/mympd

/** @module playlists_js */

/**
 * Validates the playlist
 * @param {string} plist playlist to validate
 * @param {boolean} remove true = remove invalid entries, false = count number of invalid entries
 * @returns {void}
 */
//eslint-disable-next-line no-unused-vars
function playlistValidate(plist, remove) {
    setUpdateViewId('BrowsePlaylistDetailList');
    sendAPI("MYMPD_API_PLAYLIST_CONTENT_VALIDATE", {
        "plist": plist,
        "remove": remove
    }, playlistValidateDedupCheckError, true);
}

/**
 * Deduplicates the playlist
 * @param {string} plist playlist to deduplicate
 * @param {boolean} remove true = remove invalid entries, false = count number of invalid entries
 * @returns {void}
 */
function playlistDedup(plist, remove) {
    setUpdateViewId('BrowsePlaylistDetailList');
    sendAPI("MYMPD_API_PLAYLIST_CONTENT_DEDUP", {
        "plist": plist,
        "remove": remove
    }, playlistValidateDedupCheckError, true);
}

/**
 * Validates and deduplicates the playlist
 * @param {string} plist playlist to deduplicate
 * @param {boolean} remove true = remove invalid entries, false = count number of invalid entries
 * @returns {void}
 */
function playlistValidateDedup(plist, remove) {
    setUpdateViewId('BrowsePlaylistDetailList');
    sendAPI("MYMPD_API_PLAYLIST_CONTENT_VALIDATE_DEDUP", {
        "plist": plist,
        "remove": remove
    }, playlistValidateDedupCheckError, true);
}

/**
 * Handler for jsonrpc responses:
 *  - MYMPD_API_PLAYLIST_CONTENT_DEDUP
 *  - MYMPD_API_PLAYLIST_CONTENT_VALIDATE
 *  - MYMPD_API_PLAYLIST_CONTENT_VALIDATE_DEDUP
 * @param {object} obj jsonrpc response
 * @returns {void}
 */
function playlistValidateDedupCheckError(obj) {
    const alertEl = elGetById('playlistDetailAlert');
    unsetUpdateViewId('BrowsePlaylistDetailList');
    if (obj.error) {
        alertEl.firstElementChild.textContent = tn(obj.error.message, obj.error.data);
        elShow(alertEl);
    }
    else {
        elHide(alertEl);
    }
}

/**
 * Removes positions from a playlist
 * @param {string} plist the playlist
 * @param {Array} positions Positions to remove
 * @returns {void}
 */
//eslint-disable-next-line no-unused-vars
function removeFromPlaylistPositions(plist, positions) {
    setUpdateViewId('BrowsePlaylistDetailList');
    sendAPI("MYMPD_API_PLAYLIST_CONTENT_RM_POSITIONS", {
        "plist": plist,
        "positions": positions
    }, null, false);
}

/**
 * Removes a range from a playlist
 * @param {string} plist the playlist
 * @param {number} start Start of the range (including) / song pos
 * @param {number} [end] End playlist position (excluding), use -1 for open end
 * @returns {void}
 */
//eslint-disable-next-line no-unused-vars
function removeFromPlaylistRange(plist, start, end) {
    setUpdateViewId('BrowsePlaylistDetailList');
    sendAPI("MYMPD_API_PLAYLIST_CONTENT_RM_RANGE", {
        "plist": plist,
        "start": start,
        "end": end
    }, null, false);
}

/**
 * Gets playlists and populates a select
 * @param {number} type type of the playlist
 *                      0 = all playlists,
 *                      1 = static playlists,
 *                      2 = smart playlists
 * @param {string} elId select element id
 * @param {string} searchstr search string
 * @param {string} selectedPlaylist current selected playlist
 * @returns {void}
 */
function filterPlaylistsSelect(type, elId, searchstr, selectedPlaylist) {
    sendAPI("MYMPD_API_PLAYLIST_LIST", {
        "searchstr": searchstr,
        "offset": 0,
        "limit": settings.webuiSettings.maxElementsPerPage,
        "type": type,
        "sort": "Name",
        "sortdesc": false,
        "fields": settings.viewBrowsePlaylistList.fields
    }, function(obj) {
        populatePlaylistSelect(obj, elId, selectedPlaylist);
    }, false);
}

/**
 * Populates the custom input element mympd-select-search
 * @param {object} obj jsonrpc response
 * @param {string} playlistSelectId select element id
 * @param {string} selectedPlaylist current selected playlist
 * @returns {void}
 */
function populatePlaylistSelect(obj, playlistSelectId, selectedPlaylist) {
    if (selectedPlaylist === undefined) {
        selectedPlaylist = '';
    }
    const selectEl = elGetById(playlistSelectId);
    //set input element values
    selectEl.value = selectedPlaylist === 'Database'
        ? tn('Database')
        : selectedPlaylist === ''
            ? playlistSelectId === 'modalTimerPlaylistInput'
                ? tn('No playlist')
                : ''
            : selectedPlaylist;
    setData(selectEl, 'value', selectedPlaylist);
    elClear(selectEl.filterResult);
    switch(playlistSelectId) {
        case 'modalTimerPlaylistInput':
            selectEl.addFilterResult('No playlist', '');
            break;
        case 'modalPlaybackJukeboxPlaylistInput':
        case 'modalQueueAddToPlaylistInput':
            selectEl.addFilterResult('Database', 'Database');
            break;
        // No Default
    }

    for (let i = 0; i < obj.result.returnedEntities; i++) {
        selectEl.addFilterResultPlain(obj.result.data[i].uri);
        if (obj.result.data[i].uri === selectedPlaylist) {
            selectEl.filterResult.lastChild.classList.add('active');
        }
    }
}

/**
 * Appends entries to a playlist
 * @param {string} type one of song, stream, dir, search, album, disc, searchdir
 * @param {Array} uris uris to add
 * @param {string} plist playlist to append the uri
 * @param {Function} callback response handling callback
 * @returns {void}
 */
function appendPlaylist(type, uris, plist, callback) {
    switch(type) {
        case 'searchdir':
            sendAPI("MYMPD_API_PLAYLIST_CONTENT_APPEND_SEARCH", {
                "expression": createBaseSearchExpression(uris[0], uris[1]),
                "plist": plist,
                "sort": uris[2],
                "sortdesc": uris[3],
            }, callback, true);
            break;
        case 'song':
        case 'stream':
        case 'dir':
            sendAPI("MYMPD_API_PLAYLIST_CONTENT_APPEND_URIS", {
                "uris": uris,
                "plist": plist
            }, callback, true);
            break;
        case 'search':
            sendAPI("MYMPD_API_PLAYLIST_CONTENT_APPEND_SEARCH", {
                "expression": uris[0],
                "plist": plist,
                "sort": uris[1],
                "sortdesc": uris[2],
            }, callback, true);
            break;
        case 'album':
            sendAPI("MYMPD_API_PLAYLIST_CONTENT_APPEND_ALBUMS", {
                "albumids": uris,
                "plist": plist
            }, callback, true);
            break;
        case 'disc':
        case 'work':
            sendAPI("MYMPD_API_PLAYLIST_CONTENT_APPEND_ALBUM_TAG", {
                "albumid": uris[0],
                "tag": type,
                "value": uris[1].toString(),
                "plist": plist
            }, callback, true);
            break;
        default:
            logError('Invalid type: ' + type);
    }
}

/**
 * Inserts entries into a playlist
 * @param {string} type one of song, stream, dir, search, album, disc, searchdir
 * @param {Array} uris uris to add
 * @param {string} plist playlist to insert the uri
 * @param {number} to position to insert
 * @param {Function} callback response handling callback
 * @returns {void}
 */
function insertPlaylist(type, uris, plist, to, callback) {
    switch(type) {
        case 'searchdir':
            sendAPI("MYMPD_API_PLAYLIST_CONTENT_INSERT_SEARCH", {
                "expression": createBaseSearchExpression(uris[0], uris[1]),
                "plist": plist,
                "sort": uris[2],
                "sortdesc": uris[3],
            }, callback, true);
            break;
        case 'song':
        case 'stream':
        case 'dir':
            sendAPI("MYMPD_API_PLAYLIST_CONTENT_INSERT_URIS", {
                "uris": uris,
                "plist": plist,
                "to": to
            }, callback, true);
            break;
        case 'search':
            sendAPI("MYMPD_API_PLAYLIST_CONTENT_INSERT_SEARCH", {
                "expression": uris[0],
                "plist": plist,
                "to": to,
                "sort": uris[1],
                "sortdesc": uris[2],
            }, callback, true);
            break;
        case 'album':
            sendAPI("MYMPD_API_PLAYLIST_CONTENT_INSERT_ALBUMS", {
                "albumids": uris,
                "plist": plist,
                "to": to
            }, callback, true);
            break;
        case 'disc':
        case 'work':
            sendAPI("MYMPD_API_PLAYLIST_CONTENT_INSERT_ALBUM_TAG", {
                "albumid": uris[0],
                "tag": type,
                "value": uris[1].toString(),
                "plist": plist,
                "to": to
            }, callback, true);
            break;
        default:
            logError('Invalid type: ' + type);
    }
}

/**
 * Replaces a playlist
 * @param {string} type one of song, stream, dir, search, album, disc, searchdir
 * @param {Array} uris uris to add
 * @param {string} plist playlist to replace
 * @param {Function} callback response handling callback
 * @returns {void}
 */
function replacePlaylist(type, uris, plist, callback) {
    switch(type) {
        case 'searchdir':
            sendAPI("MYMPD_API_PLAYLIST_CONTENT_REPLACE_SEARCH", {
                "expression": createBaseSearchExpression(uris[0], uris[1]),
                "plist": plist,
                "sort": uris[2],
                "sortdesc": uris[3]
            }, callback, true);
            break;
        case 'song':
        case 'stream':
        case 'dir':
            sendAPI("MYMPD_API_PLAYLIST_CONTENT_REPLACE_URIS", {
                "uris": uris,
                "plist": plist
            }, callback, true);
            break;
        case 'search':
            sendAPI("MYMPD_API_PLAYLIST_CONTENT_REPLACE_SEARCH", {
                "expression": uris[0],
                "plist": plist,
                "sort": uris[1],
                "sortdesc": uris[2],
            }, callback, true);
            break;
        case 'album':
            sendAPI("MYMPD_API_PLAYLIST_CONTENT_REPLACE_ALBUMS", {
                "albumids": uris,
                "plist": plist
            }, callback, true);
            break;
        case 'disc':
        case 'work':
            sendAPI("MYMPD_API_PLAYLIST_CONTENT_REPLACE_ALBUM_TAG", {
                "albumid": uris[0],
                "tag": type,
                "value": uris[1].toString(),
                "plist": plist
            }, callback, true);
            break;
        default:
            logError('Invalid type: ' + type);
    }
}

/**
 * Deletes playlists and shows a confirmation modal before
 * @param {Array} plists playlists to delete
 * @returns {void}
 */
//eslint-disable-next-line no-unused-vars
function showDelPlaylist(plists) {
    showConfirm(tn('Do you really want to delete the playlist?', {"playlist": joinArray(plists)}), tn('Yes, delete it'), function() {
        sendAPI("MYMPD_API_PLAYLIST_RM", {
            "plists": plists
        }, null, false);
    });
}

/**
 * Clears a playlist and shows a confirmation modal before
 * @returns {void}
 */
//eslint-disable-next-line no-unused-vars
function showClearPlaylist() {
    const plist = getDataId('BrowsePlaylistDetailList', 'uri');
    showConfirm(tn('Do you really want to clear the playlist?', {"playlist": plist}), tn('Yes, clear it'), function() {
        sendAPI("MYMPD_API_PLAYLIST_CONTENT_CLEAR", {
            "plist": plist
        }, null, false);
        setUpdateViewId('BrowsePlaylistDetailList');
    });
}

/**
 * Checks if the playlist is a stored playlist of mpd
 * @param {string} uri playlist uri
 * @returns {boolean} true = stored playlist, false = playlist in music directory
 */
function isMPDplaylist(uri) {
    if (uri.charAt(1) === '/' ||
        uri.match(/\.(m3u|pls|asx|xspf)/) !== null)
    {
        return false;
    }
    return true;
}

/**
 * Resume playlist API implementation.
 * Load the playlist from last played song and start playing.
 * @param {string} plist Playlist to resume
 * @param {number} pos Position of first song to resume
 * @param {string} action Action
 * @returns {void}
 */
function resumePlist(plist, pos, action) {
    pos++;
    switch(action) {
        case 'append':
        case 'appendPlay':
            sendAPI("MYMPD_API_QUEUE_APPEND_PLAYLIST_RANGE", {
                'plist': plist,
                'start': pos,
                'end': -1,
                'play': true
            }, null, false);
            break;
        case 'insert':
            sendAPI('MYMPD_API_QUEUE_INSERT_PLAYLIST_RANGE', {
                'plist': plist,
                'start': pos,
                'end': -1,
                'play': true,
                'to': 0,
                'whence': 0
            }, null, false);
            break;
        case 'insertAfterCurrent':
        case 'insertPlayAfterCurrent':
            sendAPI('MYMPD_API_QUEUE_INSERT_PLAYLIST_RANGE', {
                'plist': plist,
                'start': pos,
                'end': -1,
                'play': true,
                'to': 0,
                'whence': 1
            }, null, false);
            break;
        case 'replace':
        case 'replacePlay':
            sendAPI("MYMPD_API_QUEUE_REPLACE_PLAYLIST_RANGE", {
                'plist': plist,
                'start': pos,
                'end': -1,
                'play': true
            }, null, false);
            break;
        // No default
    }
}