1
\$\begingroup\$

I'm writing a torrent streaming client. The plan is to wrap WebTorrent-cli and provide a Java interface to observe download state, cancel or pause downloads etc.

Anyways, I came up with the following DownloadManagerImpl. I want the instances to this class be immutable, strictly enough that they remain thread safe if accessed from multiple threads. I'm using the builder pattern to create instances of DownloadManagerImpl as more fields can be added in the future. I would like to avoid any repetitions of fields. Really sorry for posting the partly documented (and yet unfinished:)) code, but here it is:

package hyena.dmanager;

import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.annotations.SerializedName;
import org.apache.commons.exec.DefaultExecutor;
import org.apache.commons.exec.Executor;

import java.nio.file.Path;
import java.nio.file.Paths;

/**
 * Default implementation for DownloadManager.
 *
 * @author Devashish Jaiswal
 * @see hyena.dmanager.DownloadManager
 */
public final class DownloadManagerImpl implements DownloadManager {
    private transient Executor exec;

    /**
     * Absolute path of the directory where files will be saved,
     * excluding the file name.
     */
    @SerializedName(JsonKey.SAVE_LOCATION)
    private Path mSaveTo;

    /** Magnet URL of the torrent. */
    @SerializedName(JsonKey.MAGNET_URL)
    private String mMagnetUrl;

    /**
     * Absolute path to vlc media player's install location. User
     * assistance might be required for this field.
     */
    @SerializedName(JsonKey.VLC_PATH)
    private Path mVlcPath;

    /**
     * WebTorrent has this tendency to hang in-between downloads if
     * the network is slow. On these situations, we kill this instance
     * of the downloader, save download state and compose a new task that
     * loads the state and resumes download.
     */
    @SerializedName(JsonKey.RESUME_TIMEOUT)
    private int mResumeAfterInactiveSeconds;

    /**
     * The index of the file to be downloaded in a multi-file torrent.
     * Set it to -ve if all files have to be downloaded, or if torrent
     * contains only a single file.
     */
    @SerializedName(JsonKey.FILE_INDEX)
    private int mFileIndex;

    private DownloadManagerImpl(){}

    @Override
    public void start() {

    }

    @Override
    public ProgressState getProgressState() {
        return null;
    }

    public String exportStateJson () {
        return new Gson().toJson(this);
    }


    private class JsonKey {
        static final String MAGNET_URL = "magent_url";
        static final String RESUME_TIMEOUT = "inactivity_resume_timeout";
        static final String SAVE_LOCATION = "save_location";
        static final String VLC_PATH = "vlc_path";
        static final String FILE_INDEX = "file_index";
    }

    public static class Builder {
        private boolean isCompositionComplete;
        private final DownloadManagerImpl mManager;

        public Builder () {
            isCompositionComplete = false;
            mManager = new DownloadManagerImpl();
            mManager.exec = new DefaultExecutor();
        }

        private synchronized boolean canModify() {
            if (isCompositionComplete) {
                // [TODO: Log the event or throw exception or do both]
                return false;
            }

            return true;
        }

        public Builder setSaveLocation (String absolutePath) {
            if (canModify()) {
                mManager.mSaveTo = Paths.get(absolutePath);
            }

            return this;
        }

        public Builder setMagnetUrl (String magnetUrl) {
            if (canModify()) {
                mManager.mMagnetUrl = magnetUrl;
            }

            return this;
        }

        public Builder setVlcPath (String absolutePath) {
            if (canModify()) {
                mManager.mVlcPath = Paths.get(absolutePath);
            }

            return this;
        }

        public Builder setInactivityResumeTimeout (int seconds) {
            if (canModify()) {
                mManager.mResumeAfterInactiveSeconds = seconds;
            }

            return this;
        }

        public Builder setFileIndex (int fileIndex) {
            if (canModify()) {
                mManager.mFileIndex = fileIndex;
            }

            return this;
        }

        public synchronized DownloadManager build () {
            isCompositionComplete = true;
            return mManager;
        }
    }

    public static DownloadManager buildFromState (String json) {
        JsonParser parser = new JsonParser();
        JsonObject stateObject = parser.parse(json).getAsJsonObject();

        return new DownloadManagerImpl.Builder()
                .setMagnetUrl(stateObject.get(JsonKey.MAGNET_URL).getAsString())
                .setInactivityResumeTimeout(stateObject.get(JsonKey.RESUME_TIMEOUT).getAsInt())
                .setSaveLocation(stateObject.get(JsonKey.SAVE_LOCATION).getAsString())
                .setVlcPath(stateObject.get(JsonKey.VLC_PATH).getAsString())
                .setFileIndex(stateObject.get(JsonKey.FILE_INDEX).getAsInt())
                .build();
    }
}

I highly appreciate you taking the time to review my code.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.