com.frostwire.android.gui.transfers.TransferManager.java Source code

Java tutorial

Introduction

Here is the source code for com.frostwire.android.gui.transfers.TransferManager.java

Source

/*
 * Created by Angel Leon (@gubatron), Alden Torres (aldenml)
 * Copyright (c) 2011-2018, 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.transfers;

import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Environment;
import android.os.Looper;
import android.os.StatFs;

import com.frostwire.android.R;
import com.frostwire.android.core.ConfigurationManager;
import com.frostwire.android.core.Constants;
import com.frostwire.android.gui.NetworkManager;
import com.frostwire.android.gui.services.Engine;
import com.frostwire.bittorrent.BTDownload;
import com.frostwire.bittorrent.BTEngine;
import com.frostwire.bittorrent.BTEngineAdapter;
import com.frostwire.search.HttpSearchResult;
import com.frostwire.search.SearchResult;
import com.frostwire.search.soundcloud.SoundcloudSearchResult;
import com.frostwire.search.torrent.TorrentCrawledSearchResult;
import com.frostwire.search.torrent.TorrentSearchResult;
import com.frostwire.search.youtube.YouTubeCrawledSearchResult;
import com.frostwire.transfers.BittorrentDownload;
import com.frostwire.transfers.HttpDownload;
import com.frostwire.transfers.SoundcloudDownload;
import com.frostwire.transfers.Transfer;
import com.frostwire.transfers.YouTubeDownload;
import com.frostwire.util.Logger;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArrayList;

import static com.frostwire.android.util.Asyncs.async;

/**
 * @author gubatron
 * @author aldenml
 */
public final class TransferManager {

    private static final Logger LOG = Logger.getLogger(TransferManager.class);

    private final List<Transfer> httpDownloads;
    private final List<BittorrentDownload> bittorrentDownloadsList;
    private final Map<String, BittorrentDownload> bittorrentDownloadsMap;
    private int downloadsToReview;
    private int startedTransfers = 0;
    private final Object alreadyDownloadingMonitor = new Object();
    private final SharedPreferences.OnSharedPreferenceChangeListener onSharedPreferenceChangeListener;
    private volatile static TransferManager instance;

    public static TransferManager instance() {
        if (instance == null) {
            instance = new TransferManager();
        }
        return instance;
    }

    private TransferManager() {
        onSharedPreferenceChangeListener = (sharedPreferences, key) -> onPreferenceChanged(key);
        registerPreferencesChangeListener();
        this.httpDownloads = new CopyOnWriteArrayList<>();
        this.bittorrentDownloadsList = new CopyOnWriteArrayList<>();
        this.bittorrentDownloadsMap = new HashMap<>(0);
        this.downloadsToReview = 0;
        async(this::loadTorrentsTask);
    }

    public void reset() {
        registerPreferencesChangeListener();
        clearTransfers();
        async(this::loadTorrentsTask);
    }

    public void onShutdown() {
        clearTransfers();
        unregisterPreferencesChangeListener();
    }

    private void clearTransfers() {
        this.httpDownloads.clear();
        this.bittorrentDownloadsList.clear();
        this.bittorrentDownloadsMap.clear();
        this.downloadsToReview = 0;
    }

    /**
     * Is it using the SD Card's private (non-persistent after uninstall) app folder to save
     * downloaded files?
     */
    public static boolean isUsingSDCardPrivateStorage() {
        String primaryPath = Environment.getExternalStorageDirectory().getAbsolutePath();
        String currentPath = ConfigurationManager.instance().getStoragePath();

        return !primaryPath.equals(currentPath);
    }

    public List<Transfer> getTransfers() {
        List<Transfer> transfers = new ArrayList<>();

        if (httpDownloads != null) {
            transfers.addAll(httpDownloads);
        }

        if (bittorrentDownloadsList != null) {
            transfers.addAll(bittorrentDownloadsList);
        }

        return transfers;
    }

    public BittorrentDownload getBittorrentDownload(String infoHash) {
        return bittorrentDownloadsMap.get(infoHash);
    }

    private boolean alreadyDownloading(String detailsUrl) {
        synchronized (alreadyDownloadingMonitor) {
            for (Transfer dt : httpDownloads) {
                if (dt.isDownloading()) {
                    if (dt.getName() != null && dt.getName().equals(detailsUrl)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    private boolean isAlreadyDownloadingTorrentByUri(String uri) {
        synchronized (alreadyDownloadingMonitor) {
            for (Transfer dt : httpDownloads) {
                if (dt instanceof TorrentFetcherDownload) {
                    String torrentUri = ((TorrentFetcherDownload) dt).getTorrentUri();
                    if (torrentUri != null && torrentUri.equals(uri)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

    public Transfer download(SearchResult sr) {
        Transfer transfer = null;

        if (isBittorrentSearchResultAndMobileDataSavingsOn(sr)) {
            return new InvalidBittorrentDownload(R.string.torrent_transfer_aborted_on_mobile_data);
        }

        if (isMobileAndDataSavingsOn()) {
            return new InvalidDownload(R.string.cloud_download_aborted_on_mobile_data);
        }

        if (alreadyDownloading(sr.getDetailsUrl())) {
            transfer = new ExistingDownload();
        } else {
            incrementStartedTransfers();
        }

        if (sr instanceof TorrentSearchResult) {
            transfer = newBittorrentDownload((TorrentSearchResult) sr);
        } else if (sr instanceof HttpSlideSearchResult) {
            transfer = newHttpDownload((HttpSlideSearchResult) sr);
        } else if (sr instanceof YouTubeCrawledSearchResult) {
            transfer = newYouTubeDownload((YouTubeCrawledSearchResult) sr);
        } else if (sr instanceof SoundcloudSearchResult) {
            transfer = newSoundcloudDownload((SoundcloudSearchResult) sr);
        } else if (sr instanceof HttpSearchResult) {
            transfer = newHttpDownload((HttpSearchResult) sr);
        }

        return transfer;
    }

    public void clearComplete() {
        List<Transfer> transfers = getTransfers();
        for (Transfer transfer : transfers) {
            if (transfer != null && transfer.isComplete()) {
                if (transfer instanceof BittorrentDownload) {
                    BittorrentDownload bd = (BittorrentDownload) transfer;
                    if (bd.isPaused()) {
                        bd.remove(false);
                    }
                } else {
                    transfer.remove(false);
                }
            }
        }
    }

    public int getActiveDownloads() {
        int count = 0;
        for (BittorrentDownload d : bittorrentDownloadsList) {
            if (!d.isComplete() && d.isDownloading()) {
                count++;
            }
        }
        for (Transfer d : httpDownloads) {
            if (!d.isComplete() && d.isDownloading()) {
                count++;
            }
        }
        return count;
    }

    public int getActiveUploads() {
        int count = 0;
        for (BittorrentDownload d : bittorrentDownloadsList) {
            if (d.isFinished() && !d.isPaused()) {
                count++;
            }
        }
        return count;
    }

    public long getDownloadsBandwidth() {
        if (BTEngine.ctx == null) {
            // too early
            return 0;
        }
        long torrentDownloadsBandwidth = BTEngine.getInstance().downloadRate();
        long peerDownloadsBandwidth = 0;
        for (Transfer d : httpDownloads) {
            peerDownloadsBandwidth += d.getDownloadSpeed();
        }
        return torrentDownloadsBandwidth + peerDownloadsBandwidth;
    }

    public double getUploadsBandwidth() {
        if (BTEngine.ctx == null) {
            // too early
            return 0;
        }
        return BTEngine.getInstance().uploadRate();
    }

    public int getDownloadsToReview() {
        return downloadsToReview;
    }

    public void incrementDownloadsToReview() {
        downloadsToReview++;
    }

    public void clearDownloadsToReview() {
        downloadsToReview = 0;
    }

    public void stopSeedingTorrents() {
        for (BittorrentDownload d : bittorrentDownloadsList) {
            if (d.isSeeding() || d.isComplete()) {
                d.pause();
            }
        }
    }

    public boolean remove(Transfer transfer) {
        if (transfer instanceof BittorrentDownload) {
            bittorrentDownloadsMap.remove(((BittorrentDownload) transfer).getInfoHash());
            return bittorrentDownloadsList.remove(transfer);
        } else if (transfer instanceof Transfer) {
            return httpDownloads.remove(transfer);
        }
        return false;
    }

    public void pauseTorrents() {
        for (BittorrentDownload d : bittorrentDownloadsList) {
            if (!d.isSeeding()) {
                d.pause();
            }
        }
    }

    public BittorrentDownload downloadTorrent(String uri) {
        return downloadTorrent(uri, null, null);
    }

    public BittorrentDownload downloadTorrent(String uri, TorrentFetcherListener fetcherListener) {
        return downloadTorrent(uri, fetcherListener, null);
    }

    public BittorrentDownload downloadTorrent(String uri, TorrentFetcherListener fetcherListener,
            String tempDownloadTitle) {
        String url = uri.trim();
        try {
            if (url.contains("urn%3Abtih%3A")) {
                //fixes issue #129: over-encoded url coming from intent
                url = url.replace("urn%3Abtih%3A", "urn:btih:");
            }

            if (isAlreadyDownloadingTorrentByUri(url)) {
                return null;
            }

            Uri u = Uri.parse(url);
            String scheme = u.getScheme();
            if (!scheme.equalsIgnoreCase("file") && !scheme.equalsIgnoreCase("http")
                    && !scheme.equalsIgnoreCase("https") && !scheme.equalsIgnoreCase("magnet")) {
                LOG.warn("Invalid URI scheme: " + u.toString());
                return new InvalidBittorrentDownload(R.string.torrent_scheme_download_not_supported);
            }

            BittorrentDownload download = null;

            if (fetcherListener == null) {
                if (scheme.equalsIgnoreCase("file")) {
                    BTEngine.getInstance().download(new File(u.getPath()), null, null);
                } else if (scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https")
                        || scheme.equalsIgnoreCase("magnet")) {
                    download = new TorrentFetcherDownload(this,
                            new TorrentUrlInfo(u.toString(), tempDownloadTitle));
                    bittorrentDownloadsList.add(download);
                    bittorrentDownloadsMap.put(download.getInfoHash(), download);
                }
            } else {
                if (scheme.equalsIgnoreCase("file")) {
                    fetcherListener.onTorrentInfoFetched(FileUtils.readFileToByteArray(new File(u.getPath())), null,
                            -1);
                } else if (scheme.equalsIgnoreCase("http") || scheme.equalsIgnoreCase("https")
                        || scheme.equalsIgnoreCase("magnet")) {
                    // this executes the listener method when it fetches the bytes.
                    download = new TorrentFetcherDownload(this, new TorrentUrlInfo(u.toString(), tempDownloadTitle),
                            fetcherListener);
                    bittorrentDownloadsList.add(download);
                    bittorrentDownloadsMap.put(download.getInfoHash(), download);
                    incrementStartedTransfers();
                    return download;
                }
                return null;
            }

            incrementStartedTransfers();
            return download;
        } catch (Throwable e) {
            LOG.warn("Error creating download from uri: " + url, e);
            return new InvalidBittorrentDownload(R.string.torrent_scheme_download_not_supported);
        }
    }

    private static BittorrentDownload createBittorrentDownload(TransferManager manager, TorrentSearchResult sr) {
        if (sr instanceof TorrentCrawledSearchResult) {
            TorrentCrawledSearchResult torrentCrawledSearchResult = (TorrentCrawledSearchResult) sr;
            BTEngine.getInstance().download(torrentCrawledSearchResult, null,
                    manager.isDeleteStartedTorrentEnabled());
        } else if (sr.getTorrentUrl() != null) {
            return new TorrentFetcherDownload(manager, new TorrentSearchResultInfo(sr));
        }

        return null;
    }

    private BittorrentDownload newBittorrentDownload(TorrentSearchResult sr) {
        try {
            BittorrentDownload bittorrentDownload = createBittorrentDownload(this, sr);
            if (bittorrentDownload != null) {
                bittorrentDownloadsList.add(bittorrentDownload);
                bittorrentDownloadsMap.put(bittorrentDownload.getInfoHash(), bittorrentDownload);
            }
            return bittorrentDownload;
        } catch (Throwable e) {
            LOG.warn("Error creating download from search result: " + sr);
            return new InvalidBittorrentDownload(R.string.empty_string);
        }
    }

    private HttpDownload newHttpDownload(HttpSlideSearchResult sr) {
        HttpDownload download = new UIHttpDownload(this, sr.slide());

        httpDownloads.add(download);
        download.start();

        return download;
    }

    private Transfer newYouTubeDownload(YouTubeCrawledSearchResult sr) {
        YouTubeDownload download = new UIYouTubeDownload(this, sr);

        httpDownloads.add(download);
        download.start();

        return download;
    }

    private Transfer newSoundcloudDownload(SoundcloudSearchResult sr) {
        SoundcloudDownload download = new UISoundcloudDownload(this, sr);

        httpDownloads.add(download);
        download.start();

        return download;
    }

    private Transfer newHttpDownload(HttpSearchResult sr) {
        HttpDownload download = new UIHttpDownload(this, sr);

        httpDownloads.add(download);
        download.start();

        return download;
    }

    public boolean isBittorrentDownload(Transfer transfer) {
        return transfer instanceof UIBittorrentDownload || transfer instanceof TorrentFetcherDownload;
    }

    public boolean isMobileAndDataSavingsOn() {
        return NetworkManager.instance().isDataMobileUp()
                && ConfigurationManager.instance().getBoolean(Constants.PREF_KEY_NETWORK_USE_WIFI_ONLY);
    }

    public boolean isBittorrentSearchResultAndMobileDataSavingsOn(SearchResult sr) {
        return sr instanceof TorrentSearchResult && isMobileAndDataSavingsOn();
    }

    public boolean isBittorrentDownloadAndMobileDataSavingsOn(Transfer transfer) {
        return isBittorrentDownload(transfer) && isMobileAndDataSavingsOn();
    }

    public boolean isBittorrentDownloadAndMobileDataSavingsOff(Transfer transfer) {
        return isBittorrentDownload(transfer) && isMobileAndDataSavingsOn();
    }

    public boolean isBittorrentDisconnected() {
        return Engine.instance().isStopped() || Engine.instance().isStopping()
                || Engine.instance().isDisconnected();
    }

    public boolean isDeleteStartedTorrentEnabled() {
        return ConfigurationManager.instance().getBoolean(Constants.PREF_KEY_TORRENT_DELETE_STARTED_TORRENT_FILES);
    }

    public static boolean isResumable(BittorrentDownload bt) {
        // torrents that are finished because seeding is
        // not enabled, are actually paused
        if (bt.isFinished()) {
            ConfigurationManager CM = ConfigurationManager.instance();
            if (!CM.isSeedFinishedTorrents()) {
                // this implies !isSeedingEnabledOnlyForWifi
                return false;
            }
            boolean isSeedingEnabledOnlyForWifi = CM.isSeedingEnabledOnlyForWifi();
            // TODO: find a better way to express relationship with isSeedingEnabled
            if (isSeedingEnabledOnlyForWifi && !NetworkManager.instance().isDataWIFIUp()) {
                return false;
            }
        }

        return bt.isPaused();
    }

    public void resumeResumableTransfers() {
        List<Transfer> transfers = getTransfers();

        if (!isMobileAndDataSavingsOn()) {
            for (Transfer t : transfers) {
                if (t instanceof BittorrentDownload) {
                    BittorrentDownload bt = (BittorrentDownload) t;

                    if (!isResumable(bt)) {
                        continue;
                    }

                    if (bt.isPaused() && !bt.isFinished()) {
                        bt.resume();
                    }
                } else if (t instanceof HttpDownload) {
                    // TODO: review this feature taking care of the SD limitations
                    /*if (t.getName().contains("archive.org")) {
                        if (!t.isComplete() && !((HttpDownload) t).isDownloading()) {
                    ((HttpDownload) t).start(true);
                        }
                    }*/
                }
            }
        }
    }

    public void seedFinishedTransfers() {
        List<Transfer> transfers = getTransfers();

        if (!isMobileAndDataSavingsOn()) {
            for (Transfer t : transfers) {
                if (t instanceof BittorrentDownload) {
                    BittorrentDownload bt = (BittorrentDownload) t;

                    if (!isResumable(bt)) {
                        continue;
                    }

                    if (bt.isFinished()) {
                        bt.resume();
                    }
                } else if (t instanceof HttpDownload) {
                    // TODO: review this feature taking care of the SD limitations
                    /*if (t.getName().contains("archive.org")) {
                        if (!t.isComplete() && !((HttpDownload) t).isDownloading()) {
                    ((HttpDownload) t).start(true);
                        }
                    }*/
                }
            }
        }
    }

    public boolean isHttpDownloadInProgress() {
        for (Transfer httpDownload : httpDownloads) {
            if (httpDownload.isDownloading()) {
                return true;
            }
        }
        return false;
    }

    /**
     * Stops all HttpDownloads (Cloud and Wi-Fi)
     */
    public void stopHttpTransfers() {
        List<Transfer> transfers = new ArrayList<>();
        transfers.addAll(httpDownloads);
        for (Transfer t : transfers) {
            if (t != null && !t.isComplete() && t.isDownloading()) {
                t.remove(false);
            }
        }
    }

    public int incrementStartedTransfers() {
        return ++startedTransfers;
    }

    public void resetStartedTransfers() {
        startedTransfers = 0;
    }

    public int startedTransfers() {
        return startedTransfers;
    }

    static long getCurrentMountAvailableBytes() {
        StatFs stat = new StatFs(ConfigurationManager.instance().getStoragePath());
        return ((long) stat.getBlockSize() * (long) stat.getAvailableBlocks());
    }

    private void registerPreferencesChangeListener() {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            Engine.instance().getThreadPool().execute(() -> ConfigurationManager.instance()
                    .registerOnPreferenceChange(onSharedPreferenceChangeListener));
        } else {
            ConfigurationManager.instance().registerOnPreferenceChange(onSharedPreferenceChangeListener);
        }
    }

    private void unregisterPreferencesChangeListener() {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            Engine.instance().getThreadPool().execute(() -> ConfigurationManager.instance()
                    .unregisterOnPreferenceChange(onSharedPreferenceChangeListener));
        } else {
            ConfigurationManager.instance().unregisterOnPreferenceChange(onSharedPreferenceChangeListener);
        }
    }

    private void onPreferenceChanged(String key) {
        //LOG.info("onPreferenceChanged(key="+key+")");
        Engine.instance().getThreadPool().execute(() -> {
            BTEngine e = BTEngine.getInstance();
            ConfigurationManager CM = ConfigurationManager.instance();
            if (key.equals(Constants.PREF_KEY_TORRENT_MAX_DOWNLOAD_SPEED)) {
                e.downloadRateLimit((int) CM.getLong(key));
            } else if (key.equals(Constants.PREF_KEY_TORRENT_MAX_UPLOAD_SPEED)) {
                e.uploadRateLimit((int) CM.getLong(key));
            } else if (key.equals(Constants.PREF_KEY_TORRENT_MAX_DOWNLOADS)) {
                e.maxActiveDownloads((int) CM.getLong(key));
            } else if (key.equals(Constants.PREF_KEY_TORRENT_MAX_UPLOADS)) {
                e.maxActiveSeeds((int) CM.getLong(key));
            } else if (key.equals(Constants.PREF_KEY_TORRENT_MAX_TOTAL_CONNECTIONS)) {
                e.maxConnections((int) CM.getLong(key));
            } else if (key.equals(Constants.PREF_KEY_TORRENT_MAX_PEERS)) {
                e.maxPeers((int) CM.getLong(key));
            }
        });

    }

    private void loadTorrentsTask() {
        bittorrentDownloadsList.clear();
        bittorrentDownloadsMap.clear();
        final BTEngine btEngine = BTEngine.getInstance();
        btEngine.setListener(new BTEngineAdapter() {
            @Override
            public void downloadAdded(BTEngine engine, BTDownload dl) {
                String name = dl.getName();
                if (name != null && name.contains("fetch_magnet")) {
                    return;
                }
                File savePath = dl.getSavePath();
                if (savePath != null && savePath.toString().contains("fetch_magnet")) {
                    return;
                }
                UIBittorrentDownload uiBittorrentDownload = new UIBittorrentDownload(TransferManager.this, dl);
                bittorrentDownloadsList.add(uiBittorrentDownload);
                bittorrentDownloadsMap.put(dl.getInfoHash(), uiBittorrentDownload);
            }

            @Override
            public void downloadUpdate(BTEngine engine, BTDownload dl) {
                try {
                    BittorrentDownload bittorrentDownload = bittorrentDownloadsMap.get(dl.getInfoHash());
                    if (bittorrentDownload instanceof UIBittorrentDownload) {
                        UIBittorrentDownload bt = (UIBittorrentDownload) bittorrentDownload;
                        bt.updateUI(dl);
                    }
                } catch (Throwable e) {
                    LOG.error("Error updating bittorrent download", e);
                }
            }
        });
        btEngine.restoreDownloads();
    }
}