com.frostwire.android.gui.dialogs.HandpickedTorrentDownloadDialog.java Source code

Java tutorial

Introduction

Here is the source code for com.frostwire.android.gui.dialogs.HandpickedTorrentDownloadDialog.java

Source

/*
 * Created by Angel Leon (@gubatron), Alden Torres (aldenml)
 * Copyright (c) 2011-2017, FrostWire(R). All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.frostwire.android.gui.dialogs;

import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.View;
import android.view.ViewGroup;

import com.frostwire.android.R;
import com.frostwire.android.core.MediaType;
import com.frostwire.android.gui.activities.MainActivity;
import com.frostwire.android.gui.services.Engine;
import com.frostwire.android.gui.transfers.TorrentFetcherDownload;
import com.frostwire.android.gui.transfers.TransferManager;
import com.frostwire.android.gui.util.UIUtils;
import com.frostwire.android.offers.Offers;
import com.frostwire.bittorrent.BTEngine;
import com.frostwire.jlibtorrent.FileStorage;
import com.frostwire.jlibtorrent.TcpEndpoint;
import com.frostwire.jlibtorrent.TorrentInfo;
import com.frostwire.jlibtorrent.swig.add_torrent_params;
import com.frostwire.jlibtorrent.swig.error_code;
import com.frostwire.jlibtorrent.swig.tcp_endpoint_vector;
import com.frostwire.transfers.Transfer;
import com.frostwire.util.JsonUtils;
import com.frostwire.util.Ref;

import org.apache.commons.io.FilenameUtils;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;

/**
 * @author gubatron
 * @author aldenml
 */
public final class HandpickedTorrentDownloadDialog
        extends AbstractConfirmListDialog<HandpickedTorrentDownloadDialog.TorrentFileEntry> {

    private TorrentInfo torrentInfo;
    private String magnetUri;
    private long torrentFetcherDownloadTokenId;
    private static final String BUNDLE_KEY_TORRENT_INFO_DATA = "torrentInfoData";
    private static final String BUNDLE_KEY_MAGNET_URI = "magnetUri";
    private static final String BUNDLE_KEY_TORRENT_FETCHER_DOWNLOAD_TOKEN_ID = "torrentFetcherDownloadTokenId";

    // non-void constructors must be avoided when creating dialogs. use setArguments instead
    public HandpickedTorrentDownloadDialog() {
        super();
    }

    public static HandpickedTorrentDownloadDialog newInstance(Context ctx, TorrentInfo tinfo, String magnetUri,
            long torrentFetcherDownloadTokenId) {
        //
        // ideas:  - pre-selected file(s) to just check the one(s)
        //         - passing a file path
        //         - passing a byte[] to create the tinfo from.

        final HandpickedTorrentDownloadDialog dlg = new HandpickedTorrentDownloadDialog();

        // this creates a bundle that gets passed to setArguments(). It's supposed to be ready
        // before the dialog is attached to the underlying activity, after we attach to it, then
        // we are able to use such Bundle to create our adapter.
        final TorrentFileEntryList torrentInfoList = getTorrentInfoList(tinfo.files());
        boolean[] allChecked = new boolean[torrentInfoList.list.size()];
        for (int i = 0; i < allChecked.length; i++) {
            allChecked[i] = true;
        }

        dlg.prepareArguments(R.drawable.download_icon, tinfo.name(),
                ctx.getString(R.string.pick_the_files_you_want_to_download_from_this_torrent),
                JsonUtils.toJson(torrentInfoList), SelectionMode.MULTIPLE_SELECTION);
        final Bundle arguments = dlg.getArguments();
        arguments.putByteArray(BUNDLE_KEY_TORRENT_INFO_DATA, tinfo.bencode());
        arguments.putString(BUNDLE_KEY_MAGNET_URI, magnetUri);
        arguments.putLong(BUNDLE_KEY_TORRENT_FETCHER_DOWNLOAD_TOKEN_ID, torrentFetcherDownloadTokenId);
        arguments.putBooleanArray(BUNDLE_KEY_CHECKED_OFFSETS, allChecked);

        return dlg;
    }

    private static TorrentFileEntryList getTorrentInfoList(FileStorage fileStorage) {
        TorrentFileEntryList entryList = new TorrentFileEntryList();
        if (fileStorage != null && fileStorage.numFiles() > 0) {
            int n = fileStorage.numFiles();
            for (int i = 0; i < n; i++) {
                entryList.add(new TorrentFileEntry(i, fileStorage.fileName(i), fileStorage.filePath(i),
                        fileStorage.fileSize(i)));
            }
        }
        return entryList;
    }

    @Override
    protected View.OnClickListener createOnYesListener() {
        return new OnStartDownloadsClickListener(getActivity(), this);
    }

    @Override
    public List<TorrentFileEntry> deserializeData(String listDataInJSON) {
        final TorrentFileEntryList torrentFileEntryList = JsonUtils.toObject(listDataInJSON,
                TorrentFileEntryList.class);
        return torrentFileEntryList.list;
    }

    @Override
    public ConfirmListDialogDefaultAdapter<TorrentFileEntry> createAdapter(Context context,
            List<TorrentFileEntry> listData, SelectionMode selectionMode, Bundle bundle) {
        Collections.sort(listData, new NameComparator());
        return new HandpickedTorrentFileEntriesDialogAdapter(context, listData, selectionMode);
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        if (outState != null && torrentInfo != null) {
            outState.putByteArray(BUNDLE_KEY_TORRENT_INFO_DATA, torrentInfo.bencode());
            outState.putString(BUNDLE_KEY_MAGNET_URI, magnetUri);
            outState.putLong(BUNDLE_KEY_TORRENT_FETCHER_DOWNLOAD_TOKEN_ID, torrentFetcherDownloadTokenId);
        }
        super.onSaveInstanceState(outState); //saves the torrentInfo in bytes.
    }

    @Override
    protected void initComponents(Dialog dlg, Bundle savedInstanceState) {
        byte[] torrentInfoData;
        Bundle arguments = getArguments();
        if (this.torrentInfo == null && arguments != null
                && (torrentInfoData = arguments.getByteArray(BUNDLE_KEY_TORRENT_INFO_DATA)) != null) {
            torrentInfo = TorrentInfo.bdecode(torrentInfoData);
            magnetUri = arguments.getString(BUNDLE_KEY_MAGNET_URI, null);
            torrentFetcherDownloadTokenId = arguments.getLong(BUNDLE_KEY_TORRENT_FETCHER_DOWNLOAD_TOKEN_ID);
            if (torrentFetcherDownloadTokenId != -1) {
                setOnCancelListener(new OnCancelDownloadsClickListener(this));
            }
            setOnYesListener(new OnStartDownloadsClickListener(dlg.getContext(), this));
        }

        super.initComponents(dlg, savedInstanceState);
    }

    private void removeTorrentFetcherDownloadFromTransfers() {
        // if I was made and there was a TorrentFetcherDownload waiting for me in TransferManager
        if (torrentFetcherDownloadTokenId != -1) {
            List<Transfer> transfers = TransferManager.instance().getTransfers();
            if (transfers != null && !transfers.isEmpty()) {
                for (Transfer i : transfers) {
                    if (i instanceof TorrentFetcherDownload) {
                        TorrentFetcherDownload tempTransfer = (TorrentFetcherDownload) i;
                        if (tempTransfer.tokenId == torrentFetcherDownloadTokenId) {
                            TransferManager.instance().remove(i);
                            return;
                        }
                    }
                }
            }
        }
    }

    private TorrentInfo getTorrentInfo() {
        return torrentInfo;
    }

    public String getMagnetUri() {
        return magnetUri;
    }

    private static class TorrentFileEntryList {
        final List<TorrentFileEntry> list = new ArrayList<>();

        public void add(TorrentFileEntry entry) {
            list.add(entry);
        }
    }

    static class TorrentFileEntry {
        private final int index;
        private final String name;
        private final String path;
        private final long size;

        TorrentFileEntry(int index, String name, String path, long size) {
            this.index = index;
            this.name = name;
            this.path = path;
            this.size = size;
        }

        public int getIndex() {
            return index;
        }

        public String getDisplayName() {
            return name;
        }

        public String getPath() {
            return path;
        }

        public long getSize() {
            return size;
        }
    }

    private class HandpickedTorrentFileEntriesDialogAdapter
            extends ConfirmListDialogDefaultAdapter<TorrentFileEntry> {

        HandpickedTorrentFileEntriesDialogAdapter(Context context, List<TorrentFileEntry> list,
                SelectionMode selectionMode) {
            super(context, list, selectionMode);
        }

        @Override
        public CharSequence getItemTitle(TorrentFileEntry data) {
            return data.getDisplayName();
        }

        @Override
        public long getItemSize(TorrentFileEntry data) {
            return data.getSize();
        }

        @Override
        public CharSequence getItemThumbnailUrl(TorrentFileEntry data) {
            return null;
        }

        @Override
        public int getItemThumbnailResourceId(TorrentFileEntry data) {
            return MediaType.getFileTypeIconId(FilenameUtils.getExtension(data.getPath()));
        }

        @Override
        public View getView(int position, View view, ViewGroup parent) {
            return super.getView(position, view, parent);
        }

        @Override
        public String getCheckedSum() {
            if (checked == null || checked.isEmpty()) {
                return null;
            }
            long totalBytes = 0;
            for (TorrentFileEntry entry : (Set<TorrentFileEntry>) checked) {
                totalBytes += entry.getSize();
            }
            return UIUtils.getBytesInHuman(totalBytes);
        }
    }

    private static class OnStartDownloadsClickListener implements View.OnClickListener {
        private final WeakReference<Context> ctxRef;
        private WeakReference<AbstractConfirmListDialog> dlgRef;

        OnStartDownloadsClickListener(Context ctx, AbstractConfirmListDialog dlg) {
            ctxRef = new WeakReference<>(ctx);
            dlgRef = new WeakReference<>(dlg);
        }

        public void setDialog(AbstractConfirmListDialog dlg) {
            dlgRef = new WeakReference<>(dlg);
        }

        @Override
        public void onClick(View v) {
            if (Ref.alive(ctxRef) && Ref.alive(dlgRef)) {
                final AbstractConfirmListDialog dlg = dlgRef.get();

                final AbstractConfirmListDialog.SelectionMode selectionMode = dlg.getSelectionMode();
                List<TorrentFileEntry> checked = (selectionMode == AbstractConfirmListDialog.SelectionMode.NO_SELECTION)
                        ? (List<TorrentFileEntry>) dlg.getList()
                        : new ArrayList<>();

                if (checked.isEmpty()) {
                    checked.addAll(dlg.getChecked());
                }

                if (!checked.isEmpty()) {
                    try {
                        dlg.dismiss();
                    } catch (Throwable ignored) {
                        // FragmentManager might throw illegal state exception after dialog is dismissed checking state loss
                    }
                    startTorrentPartialDownload(checked);

                    if (ctxRef.get() instanceof Activity) {
                        Offers.showInterstitialOfferIfNecessary((Activity) ctxRef.get(),
                                Offers.PLACEMENT_INTERSTITIAL_MAIN, false, false);
                    }
                }
            }
        }

        private void startTorrentPartialDownload(List<TorrentFileEntry> results) {
            if (!Ref.alive(ctxRef) || !Ref.alive(dlgRef) || results == null || dlgRef.get().getList() == null
                    || results.size() > dlgRef.get().getList().size()) {
                return;
            }

            final boolean[] selection = new boolean[((HandpickedTorrentDownloadDialog) dlgRef.get()).getList()
                    .size()];
            for (TorrentFileEntry selectedFileEntry : results) {
                selection[selectedFileEntry.getIndex()] = true;
            }

            Engine.instance().getThreadPool().execute(() -> {
                try {
                    // there is a still a chance of reference getting null, this is in
                    // the background
                    if (!Ref.alive(ctxRef) || !Ref.alive(dlgRef)) {
                        return;
                    }
                    Context ctx = ctxRef.get();
                    HandpickedTorrentDownloadDialog dlg = (HandpickedTorrentDownloadDialog) dlgRef.get();

                    String magnet = dlg.getMagnetUri();
                    List<TcpEndpoint> peers = parsePeers(magnet);
                    BTEngine.getInstance().download(dlg.getTorrentInfo(), null, selection, peers,
                            TransferManager.instance().isDeleteStartedTorrentEnabled());
                    UIUtils.showTransfersOnDownloadStart(ctx);
                    dlg.removeTorrentFetcherDownloadFromTransfers();
                    MainActivity.refreshTransfers(ctx);
                } catch (Throwable ignored) {
                }
            });

            TransferManager.instance().incrementStartedTransfers();
        }

        private static List<TcpEndpoint> parsePeers(String magnet) {
            if (magnet == null || magnet.isEmpty() || magnet.startsWith("http")) {
                return Collections.emptyList();
            }

            // TODO: replace this with the public API
            error_code ec = new error_code();
            add_torrent_params params = add_torrent_params.parse_magnet_uri(magnet, ec);
            tcp_endpoint_vector v = params.get_peers();
            int size = (int) v.size();
            ArrayList<TcpEndpoint> l = new ArrayList<>();

            for (int i = 0; i < size; i++) {
                l.add(new TcpEndpoint(v.get(i)));
            }

            return l;
        }
    }

    private static class OnCancelDownloadsClickListener implements DialogInterface.OnCancelListener {

        private final WeakReference<HandpickedTorrentDownloadDialog> dlgRef;

        OnCancelDownloadsClickListener(HandpickedTorrentDownloadDialog dlg) {
            dlgRef = Ref.weak(dlg);
        }

        @Override
        public void onCancel(DialogInterface dialogInterface) {
            if (Ref.alive(dlgRef)) {
                HandpickedTorrentDownloadDialog handpickedTorrentDownloadDialog = dlgRef.get();
                handpickedTorrentDownloadDialog.removeTorrentFetcherDownloadFromTransfers();
                // can't use dlgRef.get().getContext() since that call exists only for API >= 23
                MainActivity.refreshTransfers(dlgRef.get().getDialog().getContext());
            }
        }
    }

    private static class NameComparator implements Comparator<TorrentFileEntry> {
        @Override
        public int compare(TorrentFileEntry left, TorrentFileEntry right) {
            return left.getDisplayName().compareTo(right.getDisplayName());
        }
    }

}