org.runbuddy.libtomahawk.infosystem.InfoSystem.java Source code

Java tutorial

Introduction

Here is the source code for org.runbuddy.libtomahawk.infosystem.InfoSystem.java

Source

/* == This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
 *
 *   Copyright 2013, Enno Gottschalk <mrmaffen@googlemail.com>
 *
 *   Tomahawk is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   Tomahawk is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with Tomahawk. If not, see <http://www.gnu.org/licenses/>.
 */
package org.runbuddy.libtomahawk.infosystem;

import android.text.TextUtils;
import android.util.Log;

import com.google.gson.JsonElement;
import com.google.gson.JsonObject;

import org.runbuddy.libtomahawk.authentication.AuthenticatorManager;
import org.runbuddy.libtomahawk.authentication.AuthenticatorUtils;
import org.runbuddy.libtomahawk.collection.Album;
import org.runbuddy.libtomahawk.collection.Artist;
import org.runbuddy.libtomahawk.collection.Playlist;
import org.runbuddy.libtomahawk.collection.PlaylistEntry;
import org.runbuddy.libtomahawk.database.DatabaseHelper;
import org.runbuddy.libtomahawk.infosystem.hatchet.HatchetInfoPlugin;
import org.runbuddy.libtomahawk.infosystem.hatchet.models.HatchetPlaybackLogEntry;
import org.runbuddy.libtomahawk.infosystem.hatchet.models.HatchetPlaybackLogPostStruct;
import org.runbuddy.libtomahawk.infosystem.hatchet.models.HatchetPlaylistEntries;
import org.runbuddy.libtomahawk.infosystem.hatchet.models.HatchetPlaylistEntriesPostStruct;
import org.runbuddy.libtomahawk.infosystem.hatchet.models.HatchetPlaylistEntriesRequest;
import org.runbuddy.libtomahawk.infosystem.hatchet.models.HatchetPlaylistPostStruct;
import org.runbuddy.libtomahawk.infosystem.hatchet.models.HatchetPlaylistRequest;
import org.runbuddy.libtomahawk.infosystem.hatchet.models.HatchetRelationshipPostStruct;
import org.runbuddy.libtomahawk.infosystem.hatchet.models.HatchetRelationshipStruct;
import org.runbuddy.libtomahawk.resolver.Query;
import org.runbuddy.libtomahawk.utils.GsonHelper;
import org.runbuddy.tomahawk.app.TomahawkApp;
import org.runbuddy.tomahawk.utils.IdGenerator;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

import de.greenrobot.event.EventBus;

/**
 * The InfoSystem resolves metadata for artists and albums like album covers and artist images.
 */
public class InfoSystem {

    private static final String TAG = InfoSystem.class.getSimpleName();

    private static class Holder {

        private static final InfoSystem instance = new InfoSystem();

    }

    public static class OpLogIsEmptiedEvent {

        public HashSet<Integer> mRequestTypes;

        public HashSet<String> mPlaylistIds;
    }

    public class ResultsEvent {

        public boolean mSuccess;

        public InfoRequestData mInfoRequestData;
    }

    private final ArrayList<InfoPlugin> mInfoPlugins = new ArrayList<>();

    private final ConcurrentHashMap<String, InfoRequestData> mSentRequests = new ConcurrentHashMap<>();

    private final ConcurrentHashMap<Integer, InfoRequestData> mLoggedOpsMap = new ConcurrentHashMap<>();

    // We store "create playlists"-loggedOps separately, because we need to check whether or not all
    // "create playlists"-loggedOps have been pushed to Hatchet before sending the corresponding
    // playlist entries
    private final ConcurrentHashMap<Integer, InfoRequestData> mPlaylistsLoggedOpsMap = new ConcurrentHashMap<>();

    // LoggedOps waiting to be sent as soon as mPlaylistsLoggedOpsMap is empty
    private final ArrayList<InfoRequestData> mQueuedLoggedOps = new ArrayList<>();

    private Query mLastPlaybackLogEntry = null;

    private Query mNowPlaying = null;

    private InfoSystem() {
        mInfoPlugins.add(new HatchetInfoPlugin());
    }

    public static InfoSystem get() {
        return Holder.instance;
    }

    public void addInfoPlugin(InfoPlugin plugin) {
        mInfoPlugins.add(plugin);
    }

    public void removeInfoPlugin(InfoPlugin plugin) {
        mInfoPlugins.remove(plugin);
    }

    /**
     * HatchetSearch the added InfoPlugins with the given keyword
     *
     * @return the created InfoRequestData's requestId
     */
    public String resolve(String keyword) {
        if (!TextUtils.isEmpty(keyword)) {
            QueryParams params = new QueryParams();
            params.term = keyword;
            return resolve(InfoRequestData.INFOREQUESTDATA_TYPE_SEARCHES, params);
        }
        return null;
    }

    /**
     * Fill up the given artist with metadata fetched from all added InfoPlugins
     *
     * @param artist the Artist to enrich with data from the InfoPlugins
     * @param full   true, if top-hits and albums should also be resolved
     * @return the created InfoRequestData's requestId
     */
    public String resolve(Artist artist, boolean full) {
        if (artist != null && !TextUtils.isEmpty(artist.getName())) {
            QueryParams params = new QueryParams();
            params.name = artist.getName();
            if (full) {
                return resolve(InfoRequestData.INFOREQUESTDATA_TYPE_ARTISTS_TOPHITSANDALBUMS, params);
            } else {
                return resolve(InfoRequestData.INFOREQUESTDATA_TYPE_ARTISTS, params);
            }
        }
        return null;
    }

    /**
     * Fill up the given artist with metadata fetched from all added InfoPlugins
     *
     * @param album the Album to enrich with data from the InfoPlugins
     * @return the created InfoRequestData's requestId
     */
    public String resolve(Album album) {
        if (album != null && !TextUtils.isEmpty(album.getName())) {
            QueryParams params = new QueryParams();
            params.name = album.getName();
            params.artistname = album.getArtist().getName();
            return resolve(InfoRequestData.INFOREQUESTDATA_TYPE_ALBUMS_TRACKS, params);
        }
        return null;
    }

    /**
     * Fill up the given user with metadata fetched from all added InfoPlugins
     *
     * @param user the User to enrich with data from the InfoPlugins
     * @return the created InfoRequestData's requestId
     */
    public String resolve(User user) {
        if (user != null && !user.isOffline() && !TextUtils.isEmpty(user.getId())) {
            QueryParams params = new QueryParams();
            params.ids = new ArrayList<>();
            params.ids.add(user.getId());
            return resolve(InfoRequestData.INFOREQUESTDATA_TYPE_USERS, params);
        }
        return null;
    }

    /**
     * Get random users
     *
     * @param count the number of users to get
     * @return the created InfoRequestData's requestId
     */
    public String getRandomUsers(int count) {
        QueryParams params = new QueryParams();
        params.random = String.valueOf(true);
        params.count = String.valueOf(count);
        return resolve(InfoRequestData.INFOREQUESTDATA_TYPE_USERS, params);
    }

    public String resolve(Playlist playlist) {
        if (playlist != null) {
            QueryParams params = new QueryParams();
            params.playlist_local_id = playlist.getId();
            params.playlist_id = playlist.getHatchetId();
            return resolve(InfoRequestData.INFOREQUESTDATA_TYPE_PLAYLISTS_PLAYLISTENTRIES, params);
        }
        return null;
    }

    /**
     * Fetch a logged-in user's id and store it
     *
     * @param username the username with which to get the corresponding id
     * @return the created InfoRequestData's requestId
     */
    public String resolveUserId(String username) {
        if (username != null) {
            QueryParams params = new QueryParams();
            params.name = username;
            return resolve(InfoRequestData.INFOREQUESTDATA_TYPE_USERS, params);
        }
        return null;
    }

    /**
     * Fill up the given user with metadata fetched from all added InfoPlugins
     *
     * @param user       the User for which to get the socialActions
     * @param beforeDate the Date that specifies which socialActions to fetch
     * @return the created InfoRequestData's requestId
     */
    public String resolveSocialActions(User user, Date beforeDate) {
        if (user != null && !user.isOffline()) {
            QueryParams params = new QueryParams();
            params.userid = user.getId();
            params.before_date = beforeDate;
            params.limit = String.valueOf(HatchetInfoPlugin.SOCIALACTIONS_LIMIT);
            return resolve(InfoRequestData.INFOREQUESTDATA_TYPE_SOCIALACTIONS, params);
        }
        return null;
    }

    /**
     * Fill up the given user with metadata fetched from all added InfoPlugins
     *
     * @param user       the User to enrich with data from the InfoPlugins
     * @param beforeDate the Date that specifies which socialActions to fetch
     * @return the created InfoRequestData's requestId
     */
    public String resolveFriendsFeed(User user, Date beforeDate) {
        if (user != null && !user.isOffline()) {
            QueryParams params = new QueryParams();
            params.userid = user.getId();
            params.type = HatchetInfoPlugin.HATCHET_SOCIALACTION_PARAMTYPE_FRIENDSFEED;
            params.before_date = beforeDate;
            params.limit = String.valueOf(HatchetInfoPlugin.FRIENDSFEED_LIMIT);
            return resolve(InfoRequestData.INFOREQUESTDATA_TYPE_SOCIALACTIONS, params);
        }
        return null;
    }

    /**
     * Fill up the given user with metadata fetched from all added InfoPlugins
     *
     * @param user the User to enrich with data from the InfoPlugins
     * @return the created InfoRequestData's requestId
     */
    public String resolvePlaybackLog(User user) {
        if (user != null && !user.isOffline()) {
            QueryParams params = new QueryParams();
            params.ids = new ArrayList<>();
            params.ids.add(user.getId());
            return resolve(InfoRequestData.INFOREQUESTDATA_TYPE_USERS_PLAYBACKLOG, params);
        }
        return null;
    }

    /**
     * Fill up the given user with metadata fetched from all added InfoPlugins
     *
     * @param user the User to enrich with data from the InfoPlugins
     * @return the created InfoRequestData's requestId
     */
    public String resolveLovedItems(User user) {
        if (user != null && !user.isOffline()) {
            QueryParams params = new QueryParams();
            params.ids = new ArrayList<>();
            params.ids.add(user.getId());
            return resolve(InfoRequestData.INFOREQUESTDATA_TYPE_USERS_LOVEDITEMS, params, true);
        }
        return null;
    }

    /**
     * Fill up the given user with metadata fetched from all added InfoPlugins
     *
     * @param user the User to enrich with data from the InfoPlugins
     * @return the created InfoRequestData's requestId
     */
    public String resolveFollowings(User user) {
        if (user != null && !user.isOffline()) {
            QueryParams params = new QueryParams();
            params.ids = new ArrayList<>();
            params.ids.add(user.getId());
            return resolve(InfoRequestData.INFOREQUESTDATA_TYPE_USERS_FOLLOWS, params);
        }
        return null;
    }

    /**
     * Fill up the given user with metadata fetched from all added InfoPlugins
     *
     * @param user the User to enrich with data from the InfoPlugins
     * @return the created InfoRequestData's requestId
     */
    public String resolveFollowers(User user) {
        if (user != null && !user.isOffline()) {
            QueryParams params = new QueryParams();
            params.ids = new ArrayList<>();
            params.ids.add(user.getId());
            return resolve(InfoRequestData.INFOREQUESTDATA_TYPE_USERS_FOLLOWERS, params);
        }
        return null;
    }

    /**
     * Fetch the given user's list of loved albums
     *
     * @return the created InfoRequestData's requestId
     */
    public String resolveLovedAlbums(User user) {
        if (user != null && !user.isOffline()) {
            QueryParams params = new QueryParams();
            params.ids = new ArrayList<>();
            params.ids.add(user.getId());
            return resolve(InfoRequestData.INFOREQUESTDATA_TYPE_USERS_LOVEDALBUMS, params, true);
        }
        return null;
    }

    /**
     * Fetch the given user's list of loved artists
     *
     * @return the created InfoRequestData's requestId
     */
    public String resolveLovedArtists(User user) {
        if (user != null && !user.isOffline()) {
            QueryParams params = new QueryParams();
            params.ids = new ArrayList<>();
            params.ids.add(user.getId());
            return resolve(InfoRequestData.INFOREQUESTDATA_TYPE_USERS_LOVEDARTISTS, params, true);
        }
        return null;
    }

    public String resolvePlaylists(User user, boolean isBackgroundRequest) {
        if (user != null && !user.isOffline()) {
            QueryParams params = new QueryParams();
            params.ids = new ArrayList<>();
            params.ids.add(user.getId());
            String requestId = IdGenerator.getSessionUniqueStringId();
            InfoRequestData infoRequestData = new InfoRequestData(requestId,
                    InfoRequestData.INFOREQUESTDATA_TYPE_USERS_PLAYLISTS, params, isBackgroundRequest);
            resolve(infoRequestData);
            return infoRequestData.getRequestId();
        }
        return null;
    }

    /**
     * Build an InfoRequestData object with the given data and order results
     *
     * @param type   the type of the InfoRequestData object
     * @param params all parameters to be given to the InfoPlugin
     * @return the created InfoRequestData's requestId
     */
    public String resolve(int type, QueryParams params) {
        return resolve(type, params, false);
    }

    /**
     * Build an InfoRequestData object with the given data and order results
     *
     * @param type                the type of the InfoRequestData object
     * @param params              all parameters to be given to the InfoPlugin
     * @param isBackgroundRequest boolean indicating whether or not this request should be run with
     *                            the lowest priority (useful for sync operations)
     * @return the created InfoRequestData's requestId
     */
    public String resolve(int type, QueryParams params, boolean isBackgroundRequest) {
        String requestId = IdGenerator.getSessionUniqueStringId();
        InfoRequestData infoRequestData = new InfoRequestData(requestId, type, params, isBackgroundRequest);
        resolve(infoRequestData);
        return infoRequestData.getRequestId();
    }

    /**
     * Order results for the given InfoRequestData object
     *
     * @param infoRequestData the InfoRequestData object to fetch results for
     */
    public void resolve(InfoRequestData infoRequestData) {
        for (InfoPlugin infoPlugin : mInfoPlugins) {
            infoPlugin.resolve(infoRequestData);
        }
    }

    public void sendPlaybackEntryPostStruct(AuthenticatorUtils authenticatorUtils) {
        if (mNowPlaying != null && mNowPlaying != mLastPlaybackLogEntry) {
            mLastPlaybackLogEntry = mNowPlaying;
            long timeStamp = System.currentTimeMillis();
            HatchetPlaybackLogEntry playbackLogEntry = new HatchetPlaybackLogEntry();
            playbackLogEntry.albumString = mLastPlaybackLogEntry.getAlbum().getName();
            playbackLogEntry.artistString = mLastPlaybackLogEntry.getArtist().getName();
            if (playbackLogEntry.artistString.isEmpty()) {
                playbackLogEntry.artistString = "Unknown Artist";
            }
            playbackLogEntry.trackString = mLastPlaybackLogEntry.getName();
            if (playbackLogEntry.trackString.isEmpty()) {
                playbackLogEntry.trackString = "Unknown Title";
            }
            playbackLogEntry.timestamp = new Date(timeStamp);
            HatchetPlaybackLogPostStruct playbackLogPostStruct = new HatchetPlaybackLogPostStruct();
            playbackLogPostStruct.playbackLogEntry = playbackLogEntry;

            String requestId = IdGenerator.getSessionUniqueStringId();
            String jsonString = GsonHelper.get().toJson(playbackLogPostStruct);
            InfoRequestData infoRequestData = new InfoRequestData(requestId,
                    InfoRequestData.INFOREQUESTDATA_TYPE_PLAYBACKLOGENTRIES, null, InfoRequestData.HTTPTYPE_POST,
                    jsonString);
            DatabaseHelper.get().addOpToInfoSystemOpLog(infoRequestData, (int) (timeStamp / 1000));
            sendLoggedOps(authenticatorUtils);
        }
    }

    public void sendNowPlayingPostStruct(AuthenticatorUtils authenticatorUtils, Query query) {
        if (mNowPlaying != query) {
            sendPlaybackEntryPostStruct(authenticatorUtils);
            mNowPlaying = query;
            long timeStamp = System.currentTimeMillis();
            HatchetPlaybackLogEntry playbackLogEntry = new HatchetPlaybackLogEntry();
            playbackLogEntry.albumString = query.getAlbum().getName();
            playbackLogEntry.artistString = query.getArtist().getName();
            if (playbackLogEntry.artistString.isEmpty()) {
                playbackLogEntry.artistString = "Unknown Artist";
            }
            playbackLogEntry.trackString = query.getName();
            if (playbackLogEntry.trackString.isEmpty()) {
                playbackLogEntry.trackString = "Unknown Title";
            }
            playbackLogEntry.type = "nowplaying";
            playbackLogEntry.timestamp = new Date(timeStamp);
            HatchetPlaybackLogPostStruct playbackLogPostStruct = new HatchetPlaybackLogPostStruct();
            playbackLogPostStruct.playbackLogEntry = playbackLogEntry;

            String requestId = IdGenerator.getSessionUniqueStringId();
            String jsonString = GsonHelper.get().toJson(playbackLogPostStruct);
            InfoRequestData infoRequestData = new InfoRequestData(requestId,
                    InfoRequestData.INFOREQUESTDATA_TYPE_PLAYBACKLOGENTRIES, null, InfoRequestData.HTTPTYPE_POST,
                    jsonString);
            send(infoRequestData, authenticatorUtils);
        }
    }

    public InfoRequestData buildPlaylistPostStruct(String localId, String title) {
        HatchetPlaylistRequest request = new HatchetPlaylistRequest();
        request.title = title;
        HatchetPlaylistPostStruct struct = new HatchetPlaylistPostStruct();
        struct.playlist = request;

        String requestId = IdGenerator.getSessionUniqueStringId();
        String jsonString = GsonHelper.get().toJson(struct);
        QueryParams params = new QueryParams();
        params.playlist_local_id = localId;
        return new InfoRequestData(requestId, InfoRequestData.INFOREQUESTDATA_TYPE_PLAYLISTS, params,
                InfoRequestData.HTTPTYPE_POST, jsonString);
    }

    public void sendPlaylistPostStruct(AuthenticatorUtils authenticatorUtils, String localId, String title) {
        long timeStamp = System.currentTimeMillis();
        InfoRequestData infoRequestData = buildPlaylistPostStruct(localId, title);
        DatabaseHelper.get().addOpToInfoSystemOpLog(infoRequestData, (int) (timeStamp / 1000));
        sendLoggedOps(authenticatorUtils);
    }

    public InfoRequestData buildPlaylistEntriesPostStruct(String localPlaylistId, List<PlaylistEntry> entries) {
        HatchetPlaylistEntriesPostStruct struct = new HatchetPlaylistEntriesPostStruct();
        struct.playlistEntries = new ArrayList<>();
        for (PlaylistEntry entry : entries) {
            HatchetPlaylistEntriesRequest request = new HatchetPlaylistEntriesRequest();
            request.trackString = entry.getQuery().getName();
            request.artistString = entry.getArtist().getName();
            request.albumString = entry.getAlbum().getName();
            struct.playlistEntries.add(request);
        }

        String requestId = IdGenerator.getSessionUniqueStringId();
        String jsonString = GsonHelper.get().toJson(struct);
        QueryParams params = new QueryParams();
        params.playlist_local_id = localPlaylistId;
        return new InfoRequestData(requestId, InfoRequestData.INFOREQUESTDATA_TYPE_PLAYLISTS_PLAYLISTENTRIES,
                params, InfoRequestData.HTTPTYPE_POST, jsonString);
    }

    public void sendPlaylistEntriesPostStruct(AuthenticatorUtils authenticatorUtils, String localPlaylistId,
            List<PlaylistEntry> entries) {
        long timeStamp = System.currentTimeMillis();
        InfoRequestData infoRequestData = buildPlaylistEntriesPostStruct(localPlaylistId, entries);
        DatabaseHelper.get().addOpToInfoSystemOpLog(infoRequestData, (int) (timeStamp / 1000));
        sendLoggedOps(authenticatorUtils);
    }

    public void deletePlaylist(AuthenticatorUtils authenticatorUtils, String localPlaylistId) {
        long timeStamp = System.currentTimeMillis();
        String requestId = IdGenerator.getLifetimeUniqueStringId();
        QueryParams params = new QueryParams();
        params.playlist_local_id = localPlaylistId;
        InfoRequestData infoRequestData = new InfoRequestData(requestId,
                InfoRequestData.INFOREQUESTDATA_TYPE_PLAYLISTS, params, InfoRequestData.HTTPTYPE_DELETE, null);
        DatabaseHelper.get().addOpToInfoSystemOpLog(infoRequestData, (int) (timeStamp / 1000));
        sendLoggedOps(authenticatorUtils);
    }

    public void deletePlaylistEntry(AuthenticatorUtils authenticatorUtils, String localPlaylistId, String entryId) {
        long timeStamp = System.currentTimeMillis();
        String requestId = IdGenerator.getLifetimeUniqueStringId();
        QueryParams params = new QueryParams();
        params.entry_id = entryId;
        params.playlist_local_id = localPlaylistId;
        InfoRequestData infoRequestData = new InfoRequestData(requestId,
                InfoRequestData.INFOREQUESTDATA_TYPE_PLAYLISTS_PLAYLISTENTRIES, params,
                InfoRequestData.HTTPTYPE_DELETE, null);
        DatabaseHelper.get().addOpToInfoSystemOpLog(infoRequestData, (int) (timeStamp / 1000));
        sendLoggedOps(authenticatorUtils);
    }

    public void sendRelationshipPostStruct(AuthenticatorUtils authenticatorUtils, User user) {
        sendRelationshipPostStruct(authenticatorUtils, user.getId(), null, null, null);
    }

    public void sendRelationshipPostStruct(AuthenticatorUtils authenticatorUtils, Query query) {
        sendRelationshipPostStruct(authenticatorUtils, null, query.getName(), query.getArtist().getName(), null);
    }

    public void sendRelationshipPostStruct(AuthenticatorUtils authenticatorUtils, Artist artist) {
        sendRelationshipPostStruct(authenticatorUtils, null, null, artist.getName(), null);
    }

    public void sendRelationshipPostStruct(AuthenticatorUtils authenticatorUtils, Album album) {
        sendRelationshipPostStruct(authenticatorUtils, null, null, album.getArtist().getName(), album.getName());
    }

    public InfoRequestData buildRelationshipPostStruct(String user, String track, String artist, String album) {
        HatchetRelationshipStruct relationship = new HatchetRelationshipStruct();
        relationship.targetUser = user;
        relationship.targetTrackString = track;
        relationship.targetArtistString = artist;
        relationship.targetAlbumString = album;
        relationship.type = user != null ? HatchetInfoPlugin.HATCHET_RELATIONSHIPS_TYPE_FOLLOW
                : HatchetInfoPlugin.HATCHET_RELATIONSHIPS_TYPE_LOVE;
        HatchetRelationshipPostStruct struct = new HatchetRelationshipPostStruct();
        struct.relationShip = relationship;

        String requestId = IdGenerator.getSessionUniqueStringId();

        String jsonString = GsonHelper.get().toJson(struct);
        return new InfoRequestData(requestId, InfoRequestData.INFOREQUESTDATA_TYPE_RELATIONSHIPS, null,
                InfoRequestData.HTTPTYPE_POST, jsonString);
    }

    public void sendRelationshipPostStruct(AuthenticatorUtils authenticatorUtils, String user, String track,
            String artist, String album) {
        long timeStamp = System.currentTimeMillis();
        InfoRequestData infoRequestData = buildRelationshipPostStruct(user, track, artist, album);
        DatabaseHelper.get().addOpToInfoSystemOpLog(infoRequestData, (int) (timeStamp / 1000));
        sendLoggedOps(authenticatorUtils);
    }

    public void deleteRelationship(AuthenticatorUtils authenticatorUtils, String relationshipId) {
        long timeStamp = System.currentTimeMillis();
        String requestId = IdGenerator.getSessionUniqueStringId();
        QueryParams params = new QueryParams();
        params.relationship_id = relationshipId;
        InfoRequestData infoRequestData = new InfoRequestData(requestId,
                InfoRequestData.INFOREQUESTDATA_TYPE_RELATIONSHIPS, params, InfoRequestData.HTTPTYPE_DELETE, null);
        DatabaseHelper.get().addOpToInfoSystemOpLog(infoRequestData, (int) (timeStamp / 1000));
        sendLoggedOps(authenticatorUtils);
    }

    /**
     * Send the given InfoRequestData's data out to every service that can handle it
     *
     * @param infoRequestData    the InfoRequestData object to fetch results for
     * @param authenticatorUtils the AuthenticatorUtils object to fetch the appropriate access
     *                           tokens
     */
    private void send(InfoRequestData infoRequestData, AuthenticatorUtils authenticatorUtils) {
        mSentRequests.put(infoRequestData.getRequestId(), infoRequestData);
        for (InfoPlugin infoPlugin : mInfoPlugins) {
            infoPlugin.send(infoRequestData, authenticatorUtils);
        }
    }

    /**
     * Get the InfoRequestData with the given Id
     */
    public InfoRequestData getSentLoggedOpById(String requestId) {
        return mSentRequests.get(requestId);
    }

    /**
     * Method to enable InfoPlugins to report that the InfoRequestData objects with the given
     * requestIds have received their results
     */
    public void reportResults(InfoRequestData infoRequestData, boolean success) {
        ResultsEvent event = new ResultsEvent();
        event.mInfoRequestData = infoRequestData;
        event.mSuccess = success;
        EventBus.getDefault().post(event);
    }

    public synchronized void sendLoggedOps(AuthenticatorUtils authenticatorUtils) {
        List<InfoRequestData> loggedOps = DatabaseHelper.get().getLoggedOps();
        for (InfoRequestData loggedOp : loggedOps) {
            verifyLoggedOp(loggedOp);
        }
        loggedOps = DatabaseHelper.get().getLoggedOps();
        for (InfoRequestData loggedOp : loggedOps) {
            if (!mLoggedOpsMap.containsKey(loggedOp.getLoggedOpId())) {
                mLoggedOpsMap.put(loggedOp.getLoggedOpId(), loggedOp);
                if (loggedOp.getType() == InfoRequestData.INFOREQUESTDATA_TYPE_PLAYLISTS_PLAYLISTENTRIES
                        || (loggedOp.getHttpType() == InfoRequestData.HTTPTYPE_DELETE
                                && loggedOp.getType() == InfoRequestData.INFOREQUESTDATA_TYPE_PLAYLISTS)) {
                    mQueuedLoggedOps.add(loggedOp);
                } else {
                    if (loggedOp.getType() == InfoRequestData.INFOREQUESTDATA_TYPE_PLAYLISTS) {
                        mPlaylistsLoggedOpsMap.put(loggedOp.getLoggedOpId(), loggedOp);
                    }
                    send(loggedOp, authenticatorUtils);
                }
            }
        }
        trySendingQueuedOps();
    }

    public synchronized void onLoggedOpsSent(ArrayList<String> doneRequestsIds, boolean discard) {
        List<InfoRequestData> loggedOps = new ArrayList<>();
        HashSet<Integer> requestTypes = new HashSet<>();
        HashSet<String> playlistIds = new HashSet<>();
        for (String doneRequestId : doneRequestsIds) {
            if (mSentRequests.containsKey(doneRequestId)) {
                InfoRequestData loggedOp = mSentRequests.get(doneRequestId);
                loggedOps.add(loggedOp);
                requestTypes.add(loggedOp.getType());
                if (loggedOp.getType() == InfoRequestData.INFOREQUESTDATA_TYPE_PLAYLISTS_PLAYLISTENTRIES) {
                    playlistIds.add(loggedOp.getQueryParams().playlist_local_id);
                } else if (loggedOp.getType() == InfoRequestData.INFOREQUESTDATA_TYPE_PLAYLISTS) {
                    List<HatchetPlaylistEntries> results = loggedOp.getResultList(HatchetPlaylistEntries.class);
                    if (results != null && results.size() > 0) {
                        HatchetPlaylistEntries entries = results.get(0);
                        if (entries != null && entries.playlists.size() > 0) {
                            playlistIds.add(entries.playlists.get(0).id);
                            DatabaseHelper.get().updatePlaylistHatchetId(
                                    loggedOp.getQueryParams().playlist_local_id, entries.playlists.get(0).id);
                        }
                    }
                }
                mLoggedOpsMap.remove(loggedOp.getLoggedOpId());
            }
        }
        if (discard) {
            for (InfoRequestData loggedOp : loggedOps) {
                mPlaylistsLoggedOpsMap.remove(loggedOp.getLoggedOpId());
            }
            trySendingQueuedOps();
            DatabaseHelper.get().removeOpsFromInfoSystemOpLog(loggedOps);
            if (DatabaseHelper.get().getLoggedOpsCount() == 0) {
                if (!requestTypes.isEmpty()) {
                    OpLogIsEmptiedEvent event = new OpLogIsEmptiedEvent();
                    event.mRequestTypes = requestTypes;
                    event.mPlaylistIds = playlistIds;
                    EventBus.getDefault().post(event);
                }
            }
        }
    }

    private synchronized void trySendingQueuedOps() {
        if (mPlaylistsLoggedOpsMap.isEmpty()) {
            while (!mQueuedLoggedOps.isEmpty()) {
                InfoRequestData queuedLoggedOp = mQueuedLoggedOps.remove(0);
                QueryParams params = queuedLoggedOp.getQueryParams();
                String hatchetId = DatabaseHelper.get().getPlaylistHatchetId(params.playlist_local_id);
                if (hatchetId != null) {
                    if (queuedLoggedOp
                            .getType() == InfoRequestData.INFOREQUESTDATA_TYPE_PLAYLISTS_PLAYLISTENTRIES) {
                        if (queuedLoggedOp.getHttpType() == InfoRequestData.HTTPTYPE_POST) {
                            // Now that we know the hatchetId, we can add it to the playlistEntry
                            // object we POST to Hatchet
                            int newHatchetId = Integer.valueOf(hatchetId);
                            JsonElement element = GsonHelper.get().fromJson(queuedLoggedOp.getJsonStringToSend(),
                                    JsonElement.class);
                            if (element.isJsonObject()) {
                                JsonObject object = (JsonObject) element;
                                JsonObject playlistEntry = object.getAsJsonObject("playlistEntry");
                                if (playlistEntry != null) {
                                    // old way of posting playlistEntries (one per request)
                                    playlistEntry.addProperty("playlist", newHatchetId);
                                } else {
                                    // new way of posting playlistEntries (all at once)
                                    object.addProperty("playlist", newHatchetId);
                                }
                            }
                            queuedLoggedOp.setJsonStringToSend(GsonHelper.get().toJson(element));
                        } else if (queuedLoggedOp.getHttpType() == InfoRequestData.HTTPTYPE_DELETE) {
                            params.playlist_id = hatchetId;
                        }
                    } else {
                        params.playlist_id = hatchetId;
                    }
                    send(queuedLoggedOp,
                            AuthenticatorManager.get().getAuthenticatorUtils(TomahawkApp.PLUGINNAME_HATCHET));
                } else {
                    Log.e(TAG, "Hatchet sync - Couldn't send queued logged op, because the stored "
                            + "local playlist id was no longer valid");
                    discardLoggedOp(queuedLoggedOp);
                }
            }
        }
    }

    private void discardLoggedOp(InfoRequestData loggedOp) {
        mSentRequests.put(loggedOp.getRequestId(), loggedOp);
        ArrayList<String> doneRequestsIds = new ArrayList<>();
        doneRequestsIds.add(loggedOp.getRequestId());
        InfoSystem.get().onLoggedOpsSent(doneRequestsIds, true);
    }

    /**
     * Verify if the given loggedOp needs to be converted to a newer version. This is needed because
     * the Hatchet API changes.
     */
    private void verifyLoggedOp(InfoRequestData loggedOp) {
        InfoRequestData convertedLogOp = null;
        if (loggedOp.getType() == 1300) { // old v1 way of posting a socialAction
            JsonElement element = GsonHelper.get().fromJson(loggedOp.getJsonStringToSend(), JsonElement.class);
            if (element instanceof JsonObject) {
                JsonObject socialAction = ((JsonObject) element).getAsJsonObject("socialAction");
                String action = getAsString(socialAction, "action");
                if (action != null && action.equals("true")) {
                    String trackString = getAsString(socialAction, "trackString");
                    String artistString = getAsString(socialAction, "artistString");
                    String albumString = getAsString(socialAction, "albumString");
                    convertedLogOp = buildRelationshipPostStruct(null, trackString, artistString, albumString);
                } else {
                    // We have to discard the loggedOp since we don't have any way of getting the
                    // associated relationShipId. Therefore we are unable to delete this particular
                    // relationship.
                    DatabaseHelper.get().removeOpFromInfoSystemOpLog(loggedOp);
                }
            }
        } else if (loggedOp.getType() == 1001) {
            JsonElement element = GsonHelper.get().fromJson(loggedOp.getJsonStringToSend(), JsonElement.class);
            if (element instanceof JsonObject) {
                JsonElement playlist = ((JsonObject) element).get("playlist");
                if (playlist instanceof JsonObject && !((JsonObject) element).has("playlistEntry")) {
                    // It's definitely an old "create playlist"-struct
                    String title = getAsString((JsonObject) playlist, "title");
                    convertedLogOp = buildPlaylistPostStruct(loggedOp.getQueryParams().playlist_local_id, title);
                }
            }
        } else if (loggedOp.getType() == 1002) {
            JsonElement element = GsonHelper.get().fromJson(loggedOp.getJsonStringToSend(), JsonElement.class);
            if (element instanceof JsonObject) {
                JsonObject playlistEntry = ((JsonObject) element).getAsJsonObject("playlistEntry");
                if (playlistEntry != null) {
                    String trackString = getAsString(playlistEntry, "trackString");
                    String artistString = getAsString(playlistEntry, "artistString");
                    String albumString = getAsString(playlistEntry, "albumString");

                    Query query = Query.get(trackString, albumString, artistString, false, true);
                    PlaylistEntry entry = PlaylistEntry.get(loggedOp.getQueryParams().playlist_local_id, query,
                            IdGenerator.getLifetimeUniqueStringId());
                    List<PlaylistEntry> entries = new ArrayList<>();
                    entries.add(entry);
                    convertedLogOp = buildPlaylistEntriesPostStruct(loggedOp.getQueryParams().playlist_local_id,
                            entries);
                }
            }
        }
        if (convertedLogOp != null) {
            DatabaseHelper.get().removeOpFromInfoSystemOpLog(loggedOp);
            DatabaseHelper.get().addOpToInfoSystemOpLog(convertedLogOp, (int) System.currentTimeMillis() / 1000);
        }
    }

    private String getAsString(JsonObject object, String memberName) {
        JsonElement element = object.get(memberName);
        if (element != null && element.isJsonPrimitive()) {
            return element.getAsString();
        }
        return null;
    }
}