%PDF- %PDF-
Mini Shell

Mini Shell

Direktori : /proc/thread-self/root/www/varak.cloud/jamstash.varak.cloud/app/subsonic/
Upload File :
Create Path :
Current File : //proc/thread-self/root/www/varak.cloud/jamstash.varak.cloud/app/subsonic/subsonic-service.js

/**
* jamstash.subsonic.service Module
*
* Provides access through $http to the Subsonic server's API.
* Also offers more fine-grained functionality that is not part of Subsonic's API.
*/
angular.module('jamstash.subsonic.service', [
    'ngLodash',
    'jamstash.settings.service',
    'jamstash.model'
])

.service('subsonic', subsonicService);

subsonicService.$inject = [
    '$http',
    '$q',
    'lodash',
    'globals',
    'map'
];

function subsonicService(
    $http,
    $q,
    _,
    globals,
    map
) {
    'use strict';

    var self = this;
    _.extend(self, {
        addToPlaylist        : addToPlaylist,
        deletePlaylist       : deletePlaylist,
        getAlbumByTag        : getAlbumByTag,
        getAlbumListBy       : getAlbumListBy,
        getArtists           : getArtists,
        getGenres            : getGenres,
        getMusicFolders      : getMusicFolders,
        getPlaylist          : getPlaylist,
        getPlaylists         : getPlaylists,
        getPodcast           : getPodcast,
        getPodcasts          : getPodcasts,
        getRandomSongs       : getRandomSongs,
        getRandomStarredSongs: getRandomStarredSongs,
        getDirectory         : getDirectory,
        getStarred           : getStarred,
        newPlaylist          : newPlaylist,
        ping                 : ping,
        recursiveGetDirectory: recursiveGetDirectory,
        savePlaylist         : savePlaylist,
        scrobble             : scrobble,
        search               : search,
        subsonicRequest      : subsonicRequest,
        toggleStar           : toggleStar
    });

    // TODO: Hyz: Remove when refactored
    var content = {
        album: [],
        song: [],
        playlists: [],
        breadcrumb: [],
        playlistsPublic: [],
        playlistsGenre: globals.SavedGenres,
        selectedAutoAlbum: null,
        selectedArtist: null,
        selectedAlbum: null,
        selectedPlaylist: null,
        selectedAutoPlaylist: null,
        selectedGenre: null,
        selectedPodcast: null
    };

    /**
     * Handles building the URL with the correct parameters and error-handling while communicating with
     * a Subsonic server
     * @param  {String} partialUrl the last part of the Subsonic URL you want, e.g. 'getStarred.view'. If it does not start with a '/', it will be prefixed
     * @param  {Object} config     optional $http config object. The base settings expected by Subsonic (username, password, etc.) will be overwritten.
     * @return {Promise}           a Promise that will be resolved if we receive the 'ok' status from Subsonic. Will be rejected otherwise with an object : {'reason': a message that can be displayed to a user, 'httpError': the HTTP error code, 'subsonicError': the error Object sent by Subsonic}
     */
    function subsonicRequest(partialUrl, config) {
        var exception = { reason: 'Error when contacting the Subsonic server.' };
        var deferred = $q.defer();
        var actualUrl = (partialUrl.charAt(0) === '/') ? partialUrl : '/' + partialUrl;
        var url = globals.BaseURL() + actualUrl;

        // Extend the provided config (if it exists) with our params
        // Otherwise we create a config object
        var actualConfig = config || {};
        actualConfig.params = actualConfig.params || {};
        _.extend(actualConfig.params,  {
            u: globals.settings.Username,
            p: globals.settings.Password,
            f: globals.settings.Protocol,
            v: globals.settings.ApiVersion,
            c: globals.settings.ApplicationName
        });
        actualConfig.timeout = globals.settings.Timeout;

        var httpPromise;
        if (globals.settings.Protocol === 'jsonp') {
            actualConfig.params.callback = 'JSON_CALLBACK';
            httpPromise = $http.jsonp(url, actualConfig);
        } else {
            httpPromise = $http.get(url, actualConfig);
        }
        httpPromise.success(function (data) {
            var subsonicResponse = (data['subsonic-response'] !== undefined) ? data['subsonic-response'] : { status: 'failed' };
            if (subsonicResponse.status === 'ok') {
                deferred.resolve(subsonicResponse);
            } else {
                if (subsonicResponse.status === 'failed' && subsonicResponse.error !== undefined) {
                    exception.subsonicError = subsonicResponse.error;
                    exception.version = subsonicResponse.version;
                }
                deferred.reject(exception);
            }
        }).error(function (data, status) {
            exception.httpError = status;
            deferred.reject(exception);
        });
        return deferred.promise;
    }

    function ping() {
        return self.subsonicRequest('ping.view');
    }

    function getMusicFolders() {
        var exception = { reason: 'No music folder found on the Subsonic server.' };
        var promise = self.subsonicRequest('getMusicFolders.view', {
            cache: true
        }).then(function (subsonicResponse) {
            if (subsonicResponse.musicFolders !== undefined && subsonicResponse.musicFolders.musicFolder !== undefined) {
                return [].concat(subsonicResponse.musicFolders.musicFolder);
            } else {
                return $q.reject(exception);
            }
        });
        return promise;
    }

    function getArtists(folder) {
        var exception = { reason: 'No artist found on the Subsonic server.' };
        var params;
        if (! isNaN(folder)) {
            params = {
                musicFolderId: folder
            };
        }
        var promise = self.subsonicRequest('getIndexes.view', {
            cache: true,
            params: params
        }).then(function (subsonicResponse) {
            if (subsonicResponse.indexes !== undefined && (subsonicResponse.indexes.index !== undefined || subsonicResponse.indexes.shortcut !== undefined)) {
                // Make sure shortcut, index and each index's artist are arrays
                // because Madsonic will return an object when there's only one element
                var formattedResponse = {};
                formattedResponse.shortcut = [].concat(subsonicResponse.indexes.shortcut);
                formattedResponse.index = [].concat(subsonicResponse.indexes.index);
                _.map(formattedResponse.index, function (index) {
                    var formattedIndex = index;
                    formattedIndex.artist = [].concat(index.artist);
                    return formattedIndex;
                });
                return formattedResponse;
            } else {
                return $q.reject(exception);
            }
        });
        return promise;
    }

    function getAlbumByTag(id) { // Gets Album by ID3 tag: NOT Being Used Currently 1/24/2015
        var deferred = $q.defer();
        $.ajax({
            url: globals.BaseURL() + '/getAlbum.view?' + globals.BaseParams() + '&id=' + id,
            method: 'GET',
            dataType: globals.settings.Protocol,
            timeout: globals.settings.Timeout,
            success: function (data) {
                if (typeof data["subsonic-response"].album != 'undefined') {
                    content.album = [];
                    content.song = [];

                    var items = [];
                    if (data["subsonic-response"].album.song.length > 0) {
                        items = data["subsonic-response"].album.song;
                    } else {
                        items[0] = data["subsonic-response"].album.song;
                    }
                    angular.forEach(items, function (item, key) {
                        content.song.push(map.mapSong(item));
                    });
                }
                deferred.resolve(content);
            }
        });
        return deferred.promise;
    }

    function getDirectory(id) {
        var exception = { reason: 'This directory is empty.' };
        var promise = self.subsonicRequest('getMusicDirectory.view', {
            params: {
                id: id
            }
        }).then(function (subsonicResponse) {
            if (subsonicResponse.directory.child !== undefined) {
                // Make sure this is an array using concat because Madsonic will return an object when there's only one element
                var children = [].concat(subsonicResponse.directory.child);
                if (children.length > 0) {
                    var allChildren = _.partition(children, function (item) {
                        return item.isDir;
                    });
                    return {
                        directories: map.mapAlbums(allChildren[0]),
                        songs: map.mapSongs(allChildren[1])
                    };
                }
            }
            // We end up here for every else
            return $q.reject(exception);
        });
        return promise;
    }

    // This is used when we add or play a directory, so we recursively get all its contents
    function recursiveGetDirectory(id) {
        var deferred = $q.defer();
        // We first use getDirectory() to get the contents of the root directory
        self.getDirectory(id).then(function (data) {
            var directories = data.directories;
            var songs = data.songs;
            // If there are only songs, we return them immediately: this is a leaf directory and the end of the recursion
            if (directories.length === 0) {
                deferred.resolve(songs);
            } else {
                // otherwise, for each directory, we call ourselves
                var promises = [];
                angular.forEach(directories, function (dir) {
                    var subdirectoryRequest = self.recursiveGetDirectory(dir.id).then(function (data) {
                        // This is where we join all the songs together in a single array
                        return songs.concat(data);
                    });
                    promises.push(subdirectoryRequest);
                });
                // since all of this is asynchronous, we need to wait for all the requests to finish by using $q.all()
                var allRequestsFinished = $q.all(promises).then(function (data) {
                    // and since $q.all() wraps everything in another array, we use flatten() to end up with only one array of songs
                    return _.flatten(data);
                });
                deferred.resolve(allRequestsFinished);
            }
        }, function () {
            // Even if getDirectory returns an error, we resolve with an empty array. Otherwise one empty directory somewhere
            // would keep us from playing all the songs of a directory recursively
            deferred.resolve([]);
        });
        return deferred.promise;
    }

    function getAlbumListBy(type, offset) {
        var actualOffset = (offset > 0) ? offset : 0;
        var exception = { reason: 'No matching albums found on the Subsonic server.' };
        var params = {
            size: globals.settings.AutoAlbumSize,
            type: type,
            offset: actualOffset
        };
        var promise = self.subsonicRequest('getAlbumList.view', {
            params: params
        }).then(function (subsonicResponse) {
            if (subsonicResponse.albumList.album !== undefined) {
                // Make sure this is an array using concat because Madsonic will return an object when there's only one element
                var albumArray = [].concat(subsonicResponse.albumList.album);
                if (albumArray.length > 0) {
                    return map.mapAlbums(albumArray);
                }
            }
            // We end up here for every else
            return $q.reject(exception);
        });
        return promise;
    }

    function search(query, type) {
        if (_([0, 1, 2]).contains(type)) {
            var promise = self.subsonicRequest('search2.view', {
                params: {
                    query: query
                }
            }).then(function (subsonicResponse) {
                var searchResult;
                if (! _.isEmpty(subsonicResponse.searchResult2)) {
                    searchResult = subsonicResponse.searchResult2;
                } else if (! _.isEmpty(subsonicResponse.search2)) {
                    // We also check search2 because Music Cabinet doesn't respond the same thing
                    // as everyone else...
                    searchResult = subsonicResponse.search2;
                }
                if (! _.isEmpty(searchResult)) {
                    // Make sure that song, album and artist are arrays using concat
                    // because Madsonic will return an object when there's only one element
                    switch (type) {
                        case 0:
                            if (searchResult.song !== undefined) {
                                return map.mapSongs([].concat(searchResult.song));
                            }
                            break;
                        case 1:
                            if (searchResult.album !== undefined) {
                                return map.mapAlbums([].concat(searchResult.album));
                            }
                            break;
                        case 2:
                            if (searchResult.artist !== undefined) {
                                return [].concat(searchResult.artist);
                            }
                            break;
                    }
                }
                // We end up here for every else
                return $q.reject({ reason: 'No results.' });
            });
            return promise;
        } else {
            return $q.reject({ reason: 'Wrong search type.' });
        }
    }

    function getRandomSongs(genre, folder) {
        var exception = { reason: 'No songs found on the Subsonic server.' };
        var params = {
            size: globals.settings.AutoPlaylistSize
        };
        if (genre !== undefined && genre !== '' && genre !== 'Random') {
            params.genre = genre;
        }
        if (! isNaN(folder)) {
            params.musicFolderId = folder;
        }
        var promise = self.subsonicRequest('getRandomSongs.view', {
            params: params
        }).then(function (subsonicResponse) {
            if (subsonicResponse.randomSongs !== undefined) {
                // Make sure this is an array using concat because Madsonic will return an object when there's only one element
                var songArray = [].concat(subsonicResponse.randomSongs.song);
                if (songArray.length > 0) {
                    return map.mapSongs(songArray);
                }
            }
            // We end up here for every else
            return $q.reject(exception);
        });
        return promise;
    }

    function getStarred() {
        var promise = self.subsonicRequest('getStarred.view', { cache: true })
            .then(function (subsonicResponse) {
                if (angular.equals(subsonicResponse.starred, {})) {
                    return $q.reject({ reason: 'Nothing is starred on the Subsonic server.' });
                } else {
                    return subsonicResponse.starred;
                }
            });
        return promise;
    }

    function getRandomStarredSongs() {
        var promise = self.getStarred()
            .then(function (starred) {
                if (starred.song !== undefined) {
                    // Make sure this is an array using concat because Madsonic will return an object when there's only one element
                    var songArray = [].concat(starred.song);
                    if (songArray.length > 0) {
                        // Return random subarray of songs
                        var songs = [].concat(_.sample(songArray, globals.settings.AutoPlaylistSize));
                        return map.mapSongs(songs);
                    }
                }
                // We end up here for every else
                return $q.reject({ reason: 'No starred songs found on the Subsonic server.' });
            });
        return promise;
    }

    function getPlaylists() {
        var exception = { reason: 'No playlist found on the Subsonic server.' };
        var promise = self.subsonicRequest('getPlaylists.view')
        .then(function (subsonicResponse) {
            if (subsonicResponse.playlists.playlist !== undefined) {
                // Make sure this is an array using concat because Madsonic will return an object when there's only one element
                var playlistArray = [].concat(subsonicResponse.playlists.playlist);
                if (playlistArray.length > 0) {
                    var allPlaylists = _.partition(playlistArray, function (item) {
                        return item.owner === globals.settings.Username;
                    });
                    return { playlists: allPlaylists[0], playlistsPublic: allPlaylists[1] };
                }
            }
            // We end up here for every else
            return $q.reject(exception);
        });
        return promise;
    }

    function getPlaylist(id) {
        var exception = { reason: 'This playlist is empty.' };
        var promise = self.subsonicRequest('getPlaylist.view', {
            params: {
                id: id
            }
        }).then(function (subsonicResponse) {
            if (subsonicResponse.playlist.entry !== undefined) {
                // Make sure this is an array using concat because Madsonic will return an object when there's only one element
                var entryArray = [].concat(subsonicResponse.playlist.entry);
                if (entryArray.length > 0) {
                    return map.mapSongs(entryArray);
                }
            }
            // We end up here for every else
            return $q.reject(exception);
        });
        return promise;
    }

    function newPlaylist(name) {
        var promise = self.subsonicRequest('createPlaylist.view', {
            params: {
                name: name
            }
        });
        return promise;
    }

    function deletePlaylist(playlistId) {
        var promise = self.subsonicRequest('deletePlaylist.view', {
            params: {
                id: playlistId
            }
        });
        return promise;
    }

    function addToPlaylist(playlistId, songs) {
        var songIds = _.pluck(songs, 'id');
        var promise = self.subsonicRequest('updatePlaylist.view', {
            params: {
                playlistId :  playlistId,
                songIdToAdd: songIds
            }
        });
        return promise;
    }

    function savePlaylist(playlistId, songs) {
        var songIds = _.pluck(songs, 'id');
        var promise = self.subsonicRequest('createPlaylist.view', {
            params: {
                playlistId: playlistId,
                songId    : songIds
            }
        });
        return promise;
    }

    function getGenres() {
        var exception = { reason: 'No genre found on the Subsonic server.' };
        var promise = self.subsonicRequest('getGenres.view')
        .then(function (subsonicResponse) {
            if (subsonicResponse.genres !== undefined && subsonicResponse.genres.genre !== undefined) {
                var genreArray = [].concat(subsonicResponse.genres.genre);
                if (genreArray.length > 0) {
                    var stringArray;
                    if (genreArray[0].value) {
                        stringArray = _.pluck(genreArray, 'value');
                    // Of course, Madsonic doesn't return the same thing as Subsonic...
                    } else if (genreArray[0].content) {
                        stringArray = _.pluck(genreArray, 'content');
                    }
                    return stringArray;
                }
            }
            // We end up here for every else
            return $q.reject(exception);
        });
        return promise;
    }

    function getPodcasts() {
        var exception = { reason: 'No podcast found on the Subsonic server.' };
        var promise = self.subsonicRequest('getPodcasts.view', {
            params: {
                includeEpisodes: false
            }
        })
        .then(function (subsonicResponse) {
            if (subsonicResponse.podcasts !== undefined && subsonicResponse.podcasts.channel !== undefined) {
                // Make sure this is an array using concat because Madsonic will return an object when there's only one element
                var channelArray = [].concat(subsonicResponse.podcasts.channel);
                if (channelArray.length > 0) {
                    return channelArray;
                }
            }
            // We end up here for every else
            return $q.reject(exception);
        });
        return promise;
    }

    function getPodcast(id) {
        var exception = { reason: 'This podcast was not found on the Subsonic server.' };
        var promise = self.subsonicRequest('getPodcasts.view', {
            params: {
                id: id,
                includeEpisodes: true
            }
        }).then(function (subsonicResponse) {
            var episodes = [];
            if (subsonicResponse.podcasts.channel !== undefined) {
                // Make sure this is an array using concat because Madsonic will return an object when there's only one element
                var channelArray = [].concat(subsonicResponse.podcasts.channel);
                if (channelArray.length > 0) {
                    var channel = channelArray[0];
                    if (channel !== null && channel.id === id) {
                        // Make sure this is an array using concat because Madsonic will return an object when there's only one element
                        var episodesArray = [].concat(channel.episode);
                        episodes = _.filter(episodesArray, function (episode) {
                            return episode.status === 'completed';
                        });
                        if (episodes.length > 0) {
                            return map.mapPodcasts(episodes);
                        } else {
                            return $q.reject({ reason: 'No downloaded episode found for this podcast. Please check the podcast settings.' });
                        }
                    }
                }
            }
            // We end up here for every else
            return $q.reject(exception);
        });
        return promise;
    }

    function scrobble(song) {
        var promise = self.subsonicRequest('scrobble.view', {
            params: {
                id: song.id,
                submisssion: true
            }
        }).then(function () {
            if (globals.settings.Debug) { console.log('Successfully scrobbled song: ' + song.id); }
            return true;
        });
        return promise;
    }

    function toggleStar(item) {
        var partialUrl = (item.starred) ? 'unstar.view' : 'star.view';
        var promise = self.subsonicRequest(partialUrl, {
            params: {
                id: item.id
            }
        }).then(function () {
            return ! item.starred;
        });
        return promise;
    }
}

Zerion Mini Shell 1.0