github.popeen.dsub.fragments.SelectPodcastsFragment.java Source code

Java tutorial

Introduction

Here is the source code for github.popeen.dsub.fragments.SelectPodcastsFragment.java

Source

/*
  This file is part of Subsonic.
   Subsonic 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.
   Subsonic 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 Subsonic. If not, see <http://www.gnu.org/licenses/>.
   Copyright 2015 (C) Scott Jackson
*/
package github.popeen.dsub.fragments;

import android.content.ClipData;
import android.content.ClipboardManager;
import android.content.Context;
import android.content.res.Resources;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v7.widget.GridLayoutManager;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;

import org.json.JSONArray;
import org.json.JSONObject;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import github.popeen.dsub.R;
import github.popeen.dsub.adapter.SectionAdapter;
import github.popeen.dsub.domain.MusicDirectory;
import github.popeen.dsub.domain.PodcastChannel;
import github.popeen.dsub.domain.PodcastEpisode;
import github.popeen.dsub.domain.ServerInfo;
import github.popeen.dsub.service.MusicService;
import github.popeen.dsub.service.MusicServiceFactory;
import github.popeen.dsub.service.OfflineException;
import github.popeen.dsub.service.ServerTooOldException;
import github.popeen.dsub.util.FileUtil;
import github.popeen.dsub.util.KakaduaUtil;
import github.popeen.dsub.util.ProgressListener;
import github.popeen.dsub.util.SyncUtil;
import github.popeen.dsub.util.Constants;
import github.popeen.dsub.util.LoadingTask;
import github.popeen.dsub.util.SilentBackgroundTask;
import github.popeen.dsub.util.UserUtil;
import github.popeen.dsub.util.Util;
import github.popeen.dsub.adapter.PodcastChannelAdapter;
import github.popeen.dsub.view.UpdateView;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static android.content.ClipDescription.MIMETYPE_TEXT_PLAIN;
import static android.content.Context.CLIPBOARD_SERVICE;

public class SelectPodcastsFragment extends SelectRecyclerFragment<Serializable> {
    private static final String TAG = SelectPodcastsFragment.class.getSimpleName();

    private boolean hasCoverArt;
    private MusicDirectory newestEpisodes;

    @Override
    public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        hasCoverArt = ServerInfo.checkServerVersion(context, "1.13") || Util.isOffline(context);
        if (Util.getPreferences(context).getBoolean(Constants.PREFERENCES_KEY_LARGE_ALBUM_ART, true)
                && hasCoverArt) {
            largeAlbums = true;
        }

        if (bundle != null && serialize) {
            newestEpisodes = (MusicDirectory) bundle.getSerializable(Constants.FRAGMENT_LIST2);
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        if (serialize) {
            outState.putSerializable(Constants.FRAGMENT_LIST2, newestEpisodes);
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (super.onOptionsItemSelected(item)) {
            return true;
        }
        SubsonicFragment fragment;
        Bundle args;
        FragmentTransaction trans;
        switch (item.getItemId()) {
        case R.id.menu_check:
            refreshPodcasts();
            break;
        case R.id.menu_add_podcast:
            addNewPodcast();
            break;
        }

        return false;
    }

    @Override
    public void onCreateContextMenu(Menu menu, MenuInflater menuInflater, UpdateView<Serializable> updateView,
            Serializable item) {
        if (item instanceof PodcastChannel) {
            PodcastChannel channel = (PodcastChannel) item;
            if (!Util.isOffline(context) && UserUtil.canPodcast()) {
                menuInflater.inflate(R.menu.select_podcasts_context, menu);

                if (SyncUtil.isSyncedPodcast(context, channel.getId())) {
                    menu.removeItem(R.id.podcast_menu_sync);
                } else {
                    menu.removeItem(R.id.podcast_menu_stop_sync);
                }
            } else {
                menuInflater.inflate(R.menu.select_podcasts_context_offline, menu);
            }
        } else {
            onCreateContextMenuSupport(menu, menuInflater, updateView, item);
        }
        recreateContextMenu(menu);
    }

    @Override
    public boolean onContextItemSelected(MenuItem menuItem, UpdateView<Serializable> updateView,
            Serializable item) {
        if (item instanceof PodcastChannel) {
            PodcastChannel channel = (PodcastChannel) item;

            switch (menuItem.getItemId()) {
            case R.id.podcast_menu_sync:
                syncPodcast(channel);
                break;
            case R.id.podcast_menu_stop_sync:
                stopSyncPodcast(channel);
                break;
            case R.id.podcast_channel_info:
                displayPodcastInfo(channel);
                break;
            case R.id.podcast_channel_delete:
                deletePodcast(channel);
                break;
            }
        } else {
            return onContextItemSelected(menuItem, item);
        }

        return true;
    }

    @Override
    public int getOptionsMenu() {
        return (UserUtil.canPodcast() && !Util.isOffline(context)) ? R.menu.select_podcasts
                : R.menu.abstract_top_menu;
    }

    @Override
    public SectionAdapter getAdapter(List<Serializable> channels) {
        if (newestEpisodes == null || newestEpisodes.getChildrenSize() == 0) {
            return new PodcastChannelAdapter(context, channels, hasCoverArt ? getImageLoader() : null, this,
                    largeAlbums);
        } else {
            Resources res = context.getResources();
            List<String> headers = Arrays.asList(res.getString(R.string.main_albums_newest),
                    res.getString(R.string.select_podcasts_channels));

            List<MusicDirectory.Entry> episodes = newestEpisodes.getChildren(false, true);
            List<Serializable> serializableEpisodes = new ArrayList<>();
            serializableEpisodes.addAll(episodes);

            List<List<Serializable>> sections = new ArrayList<>();
            sections.add(serializableEpisodes);
            sections.add(channels);

            return new PodcastChannelAdapter(context, headers, sections,
                    ServerInfo.checkServerVersion(context, "1.13") ? getImageLoader() : null, this, largeAlbums);
        }
    }

    @Override
    public List<Serializable> getObjects(MusicService musicService, boolean refresh, ProgressListener listener)
            throws Exception {
        List<PodcastChannel> channels = musicService.getPodcastChannels(refresh, context, listener);

        if (!Util.isOffline(context) && ServerInfo.hasNewestPodcastEpisodes(context)) {
            try {
                newestEpisodes = musicService.getNewestPodcastEpisodes(refresh, context, listener, 10);

                for (MusicDirectory.Entry entry : newestEpisodes.getChildren()) {
                    for (PodcastChannel channel : channels) {
                        if (channel.getId().equals(entry.getParent())) {
                            PodcastEpisode episode = (PodcastEpisode) entry;

                            // Update with information normally done in PodcastEntryParser
                            episode.setArtist(channel.getName());
                            episode.setCoverArt(channel.getCoverArt());
                            episode.setPath(FileUtil.getPodcastPath(context, episode));
                            break;
                        }
                    }
                }
            } catch (Exception e) {
                Log.e(TAG, "Failed to download newest episodes", e);
                newestEpisodes = null;
            }
        } else {
            newestEpisodes = null;
        }

        List<Serializable> serializableList = new ArrayList<>();
        serializableList.addAll(channels);

        return serializableList;
    }

    @Override
    public int getTitleResource() {
        return R.string.button_bar_podcasts;
    }

    @Override
    public void onItemClicked(UpdateView<Serializable> updateView, Serializable item) {
        if (item instanceof PodcastChannel) {
            PodcastChannel channel = (PodcastChannel) item;
            if ("error".equals(channel.getStatus())) {
                Util.toast(context,
                        context.getResources().getString(R.string.select_podcasts_invalid_podcast_channel,
                                channel.getErrorMessage() == null ? "error" : channel.getErrorMessage()));
            } else if ("downloading".equals(channel.getStatus())) {
                Util.toast(context, R.string.select_podcasts_initializing);
            } else {
                SubsonicFragment fragment = new SelectDirectoryFragment();
                Bundle args = new Bundle();
                args.putString(Constants.INTENT_EXTRA_NAME_PODCAST_ID, channel.getId());
                args.putString(Constants.INTENT_EXTRA_NAME_PODCAST_NAME, channel.getName());
                args.putString(Constants.INTENT_EXTRA_NAME_PODCAST_DESCRIPTION, channel.getDescription());
                fragment.setArguments(args);

                replaceFragment(fragment);
            }
        } else {
            PodcastEpisode episode = (PodcastEpisode) item;

            String status = episode.getStatus();
            if ("error".equals(status)) {
                Util.toast(context, R.string.select_podcasts_error);
                return;
            } else if (!"completed".equals(status)) {
                Util.toast(context, R.string.select_podcasts_skipped);
                return;
            }

            onSongPress(Arrays.asList((MusicDirectory.Entry) episode), episode, false);
        }
    }

    @Override
    public GridLayoutManager.SpanSizeLookup getSpanSizeLookup(final GridLayoutManager gridLayoutManager) {
        return new GridLayoutManager.SpanSizeLookup() {
            @Override
            public int getSpanSize(int position) {
                SectionAdapter adapter = getCurrentAdapter();
                if (adapter != null) {
                    int viewType = getCurrentAdapter().getItemViewType(position);
                    if (viewType == SectionAdapter.VIEW_TYPE_HEADER
                            || viewType == PodcastChannelAdapter.VIEW_TYPE_PODCAST_EPISODE
                            || viewType == PodcastChannelAdapter.VIEW_TYPE_PODCAST_LEGACY) {
                        return gridLayoutManager.getSpanCount();
                    } else {
                        return 1;
                    }
                } else {
                    return 1;
                }
            }
        };
    }

    @Override
    public void onFinishRefresh() {
        Bundle args = getArguments();
        if (args != null) {
            String podcastId = args.getString(Constants.INTENT_EXTRA_NAME_ID, null);
            if (podcastId != null && objects != null) {
                for (Serializable ser : objects) {
                    if (ser instanceof PodcastChannel) {
                        PodcastChannel podcast = (PodcastChannel) ser;
                        if (podcastId.equals(podcast.getId())) {
                            onItemClicked(null, podcast);
                            break;
                        }
                    }
                }
            }
        }
    }

    public void refreshPodcasts() {
        new SilentBackgroundTask<Void>(context) {
            @Override
            protected Void doInBackground() throws Throwable {
                MusicService musicService = MusicServiceFactory.getMusicService(context);
                musicService.refreshPodcasts(context, null);
                return null;
            }

            @Override
            protected void done(Void result) {
                Util.toast(context, R.string.select_podcasts_refreshing);
            }

            @Override
            protected void error(Throwable error) {
                Util.toast(context, getErrorMessage(error), false);
            }
        }.execute();
    }

    private void addNewPodcast() {
        View dialogView = context.getLayoutInflater().inflate(R.layout.create_podcast, null);
        final TextView urlBox = (TextView) dialogView.findViewById(R.id.create_podcast_url);

        //If a string is in clipboard and it contains http or https, add it to the textbox
        ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
        if (clipboard.hasPrimaryClip() && clipboard.getPrimaryClipDescription().hasMimeType(MIMETYPE_TEXT_PLAIN)) {

            String url = clipboard.getPrimaryClip().getItemAt(0).getText().toString();
            if (url.contains("http")) {
                urlBox.setText(url.trim());
            }
        }

        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setTitle(R.string.menu_add_podcast).setView(dialogView)
                .setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int id) {
                        String url = urlBox.getText().toString();
                        addNewPodcast(url);
                    }
                }).setNegativeButton(R.string.common_cancel, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int id) {
                        dialog.cancel();
                    }
                }).setCancelable(true);

        AlertDialog dialog = builder.create();
        dialog.show();
    }

    private void addNewPodcast(final String url) {
        new LoadingTask<Void>(context, false) {
            @Override
            protected Void doInBackground() throws Throwable {
                String url2 = getRssFeed(url);
                MusicService musicService = MusicServiceFactory.getMusicService(context);
                musicService.createPodcastChannel(url2, context, null);
                return null;
            }

            @Override
            protected void done(Void result) {
                refresh();
            }

            @Override
            protected void error(Throwable error) {
                String msg;
                if (error instanceof OfflineException || error instanceof ServerTooOldException) {
                    msg = getErrorMessage(error);
                } else {
                    msg = context.getResources().getString(R.string.select_podcasts_created_error) + " "
                            + getErrorMessage(error);
                }

                Util.toast(context, msg, false);
            }
        }.execute();
    }

    private String getRssFeed(String url) {
        String url2 = url;
        try {
            if (url.toLowerCase().contains("itunes.apple.com")) {
                Pattern pattern = Pattern.compile("/id([0-9]*)");
                Matcher matcher = pattern.matcher(url);
                if (matcher.find()) {
                    try {
                        String raw = KakaduaUtil.http_get_contents(
                                "https://itunes.apple.com/lookup?id=" + matcher.group(1) + "&entity=podcast");
                        url2 = new JSONObject(raw).getJSONArray("results").getJSONObject(0).getString("feedUrl");
                        Log.w("podcast", url2);
                    } catch (Exception e) {
                        Log.w("podcast", e.toString());
                    }
                }

            } else if (url.toLowerCase().contains("soundcloud.com")) {
                try {
                    Document doc = Jsoup.connect(url).get();
                    Elements metas = doc.getElementsByTag("meta");
                    for (Element meta : metas) {
                        if (meta.attr("property").equals("al:android:url")) {
                            String id = meta.attr("content").replace("soundcloud://users:", "");
                            url2 = "https://feeds.soundcloud.com/users/soundcloud:users:" + id + "/sounds.rss";
                            Log.w("podcast", url2);
                        }
                    }
                } catch (Exception e) {
                    Log.w("podcast", e.toString());
                }
            } else if (url.toLowerCase().contains("player.fm/series")) {
                try {
                    Document doc = Jsoup.connect(url).get();
                    Elements links = doc.select(".blatant");
                    for (Element link : links) {
                        if (link.text().equals("Public Feed")) {
                            url2 = link.attr("href");
                            Log.w("podcast", url2);
                        }
                    }
                } catch (Exception e) {
                    Log.w("podcast", e.toString());
                }
            } else if (url.toLowerCase().contains("acast.com") || url.toLowerCase().contains("podbean.com")) {
                /*
                TODO,
                This uses a standard tag that most podcasting websites support.
                Make it always try this if the url entered is not a valid feed or a website with a specific conversion above like for example iTunes.
                If it fails it needs to prevent the app from adding it to the server and then show an error message
                 */
                try {
                    Document doc = Jsoup.connect(url).get();
                    Elements links = doc.select("link");
                    for (Element link : links) {
                        if (link.attr("type").equals("application/rss+xml")) {
                            url2 = link.attr("href");
                            Log.w("podcast", url2);
                        }
                    }
                } catch (Exception e) {
                    Log.w("podcast", e.toString());
                }
            }
        } catch (Exception e) {
            Log.w("podcast", e.toString());
        }
        return url2;
    }

    private void displayPodcastInfo(final PodcastChannel channel) {
        List<Integer> headers = new ArrayList<>();
        List<String> details = new ArrayList<>();

        if (channel.getName() != null) {
            headers.add(R.string.details_title);
            details.add(channel.getName());
        }

        headers.add(R.string.details_url);
        details.add(channel.getUrl());
        headers.add(R.string.details_status);
        details.add(channel.getStatus());

        if (channel.getErrorMessage() != null) {
            headers.add(R.string.details_error);
            details.add(channel.getErrorMessage());
        }
        if (channel.getDescription() != null) {
            headers.add(R.string.details_description);
            details.add(channel.getDescription());
        }

        Util.showDetailsDialog(context, R.string.details_title_podcast, headers, details);
    }

    private void deletePodcast(final PodcastChannel channel) {
        Util.confirmDialog(context, R.string.common_delete, channel.getName(),
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        new LoadingTask<Void>(context, false) {
                            @Override
                            protected Void doInBackground() throws Throwable {
                                MusicService musicService = MusicServiceFactory.getMusicService(context);
                                musicService.deletePodcastChannel(channel.getId(), context, null);
                                stopSyncPodcast(channel);
                                return null;
                            }

                            @Override
                            protected void done(Void result) {
                                adapter.removeItem(channel);
                                Util.toast(context, context.getResources()
                                        .getString(R.string.select_podcasts_deleted, channel.getName()));
                            }

                            @Override
                            protected void error(Throwable error) {
                                String msg;
                                if (error instanceof OfflineException || error instanceof ServerTooOldException) {
                                    msg = getErrorMessage(error);
                                } else {
                                    msg = context.getResources().getString(R.string.select_podcasts_deleted_error,
                                            channel.getName()) + " " + getErrorMessage(error);
                                }

                                Util.toast(context, msg, false);
                            }
                        }.execute();
                    }
                });
    }

    private void syncPodcast(final PodcastChannel podcast) {
        new LoadingTask<MusicDirectory>(context, false) {
            @Override
            protected MusicDirectory doInBackground() throws Throwable {
                MusicService musicService = MusicServiceFactory.getMusicService(context);
                return musicService.getPodcastEpisodes(true, podcast.getId(), context, this);
            }

            @Override
            protected void done(MusicDirectory result) {
                List<String> existingEpisodes = new ArrayList<String>();
                for (MusicDirectory.Entry entry : result.getChildren()) {
                    String id = entry.getId();
                    if (id != null) {
                        existingEpisodes.add(entry.getId());
                    }
                }

                SyncUtil.addSyncedPodcast(context, podcast.getId(), existingEpisodes);
            }
        }.execute();
    }

    private void stopSyncPodcast(PodcastChannel podcast) {
        SyncUtil.removeSyncedPodcast(context, podcast.getId());
    }
}