diff options
Diffstat (limited to 'src/main/java')
11 files changed, 917 insertions, 0 deletions
diff --git a/src/main/java/net/jesterpm/podcastuploader/PodcastUploader.java b/src/main/java/net/jesterpm/podcastuploader/PodcastUploader.java new file mode 100644 index 0000000..f386f0d --- /dev/null +++ b/src/main/java/net/jesterpm/podcastuploader/PodcastUploader.java @@ -0,0 +1,64 @@ +/* + * Copyright 2012 Jesse Morgan + */ + +package net.jesterpm.podcastuploader; + +import net.jesterpm.podcastuploader.config.Config; +import net.jesterpm.podcastuploader.control.ConfigureTask; +import net.jesterpm.podcastuploader.control.UploadTask; +import net.jesterpm.podcastuploader.ui.ConfigurationWindow; +import net.jesterpm.podcastuploader.ui.ProgressWindow; + +/** + * Application entry-point. + * + * @author Jesse Morgan <jesse@jesterpm.net> + */ +public class PodcastUploader { +    private static final String DEFAULT_CONFIG = System.getProperty("user.home")  +        + System.getProperty("file.separator") + ".podcastuploader"; + +    public static void main(String... args) { +        final Config appconfig = new Config(DEFAULT_CONFIG); + +        if (args.length == 0) { +            startConfigure(appconfig); + +        } else { +            if (args[0].equals("--help")) { +                printHelp(); +            } + +            startUpload(appconfig, args[0]); +        } +    }  + +    private static void printHelp() { +        System.out.println("PodcastUploader - Podcast upload utility."); +        System.out.println("Created by Jesse Morgan <jesse@jesterpm.net>"); +        System.out.println("http://jesterpm.net/projects/podcastuploader"); +        System.out.println(); +        System.out.println("Usage: PodcastUploader [directory]"); +        System.out.println( +                "When started with no arguments, the configuration dialog is opened."); +        System.out.println( +                "When started with one argument, it is assumed to be a directory\n" +                + "with a metadata.txt file with upload instructions.");  +        System.out.println(); +    } + +    private static void startConfigure(final Config appconfig) { +        ConfigurationWindow win = new ConfigurationWindow(); +        ConfigureTask task = new ConfigureTask(appconfig, win); + +        task.run(); +    } + +    public static void startUpload(final Config appconfig, final String dir) { +        ProgressWindow win = new ProgressWindow(); +        UploadTask task = new UploadTask(appconfig, win, dir); + +        task.run(); +    } +} diff --git a/src/main/java/net/jesterpm/podcastuploader/config/Config.java b/src/main/java/net/jesterpm/podcastuploader/config/Config.java new file mode 100644 index 0000000..b9260e0 --- /dev/null +++ b/src/main/java/net/jesterpm/podcastuploader/config/Config.java @@ -0,0 +1,137 @@ +/* + * Copyright 2012 Jesse Morgan + */ + +package net.jesterpm.podcastuploader.config; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.File; + +import java.text.ParseException; + +import java.util.Map; +import java.util.HashMap; + +/** + * Configuration and metadata parser. + * + * @author Jesse Morgan <jesse@jesterpm.net> + */ +public class Config { +    private final String mFilename; +    private Map<String, String> mConfig; + +    /** +     * States the parser can be in. +     */ +    private enum TokenizerState { +        OUT_OF_ENTRY, IN_ENTRY; +    } + +    /** +     * Create a new Config object based on the given file. +     */ +    public Config(final String filename) { +        mFilename = filename; +        mConfig = new HashMap<String, String>(); +        parse(); +    } + +    private void parse() { +        try { +            final File configfile = new File(mFilename); +            if (configfile.isFile()) { +                BufferedReader in = new BufferedReader(new FileReader(mFilename)); + +                int lineno = 0; +                String line; +                String key = ""; +                String value = ""; +                TokenizerState state = TokenizerState.OUT_OF_ENTRY; +                while ((line = in.readLine()) != null) { +                    lineno++; +                    if (line.length() == 0) { +                        continue; +                    } + +                    switch (state) { +                        case IN_ENTRY: +                            if (Character.isWhitespace(line.charAt(0))) { +                                value += "\n" + line.trim(); +                                break; + +                            } else { +                                // Beginning new entry. Save old and pass through. +                                mConfig.put(key, value); +                                key = value = ""; +                                state = TokenizerState.OUT_OF_ENTRY; +                                // NB Intentionally falling through... +                            } + +                        case OUT_OF_ENTRY: +                            if (line.charAt(0) == '#') { +                                continue; +                            } + +                            final int pos = line.indexOf(':'); +                            if (pos == -1) { +                                throw new ParseException("Missing : at line " + lineno, lineno); +                            } +                            key = line.substring(0, pos).trim().toLowerCase(); +                            if (key.length() == 0) { +                                throw new ParseException("Zero-length key on line " + lineno, +                                       lineno); +                            } +                            value = line.substring(pos + 1).trim(); +                            state = TokenizerState.IN_ENTRY; +                            break; +                    } +                } +                 +                // Catch last key/value +                if (state == TokenizerState.IN_ENTRY) { +                    mConfig.put(key, value); +                } + +                in.close(); +            } + +        } catch (final Exception e) { +            System.err.println("[ERROR] Failed to load config from " + mFilename + ". " +                    + e.getMessage()); +        } +    } + +    public void save() { +        try { +            BufferedWriter out = new BufferedWriter(new FileWriter(mFilename)); + +            for (Map.Entry<String, String> entry : mConfig.entrySet()) { +                out.write(entry.getKey()); +                out.write(": "); +                out.write(entry.getValue()); +                out.newLine(); +            } + +            out.close(); + +        } catch (final Exception e) { +            System.err.println("[ERROR] Failed to save configuration: " + e.getMessage()); +        } +    } + +    public String get(final String key) { +        return mConfig.get(key); +    } + +    public void put(final String key, final String obj) { +        mConfig.put(key, obj); +    } + +    public Map<String, String> getMap() { +        return mConfig; +    } +} diff --git a/src/main/java/net/jesterpm/podcastuploader/control/ConfigureTask.java b/src/main/java/net/jesterpm/podcastuploader/control/ConfigureTask.java new file mode 100644 index 0000000..0158155 --- /dev/null +++ b/src/main/java/net/jesterpm/podcastuploader/control/ConfigureTask.java @@ -0,0 +1,85 @@ +/* + * Copyright 2012 Jesse Morgan + */ + +package net.jesterpm.podcastuploader.control; + +import net.jesterpm.podcastuploader.config.Config; + +import net.jesterpm.podcastuploader.ui.Action; +import net.jesterpm.podcastuploader.ui.ConfigurationWindow; + +/** + * Controller for the ConfigurationWindow. + * + * @author Jesse Morgan <jesse@jesterpm.net> + */ +public class ConfigureTask { +    private final Config mAppConfig; +    private final ConfigurationWindow mWin; + +    public ConfigureTask(final Config appconfig, final ConfigurationWindow win) { +        mAppConfig = appconfig; +        mWin = win; + +        mWin.addSaveAction(new Action() { +            public void onAction() { +                populateConfig(); +                mAppConfig.save(); +                mWin.setVisible(false); +                System.exit(0); +            } +        }); + +        mWin.addCancelAction(new Action() { +            public void onAction() { +                mWin.setVisible(false); +                System.exit(0); +            } +        }); + +        mWin.addAuthorizeAction(new Action() { +            public void onAction() { +                populateConfig(); +                getAuthorization(); +            } +        }); + +        populateWindow(); +    }   + +    /** +     * Set the fields in the configuration window. +     */ +    private void populateWindow() { +        mWin.setAWSKey(mAppConfig.get("AWSAccessKeyId")); +        mWin.setAWSSecret(mAppConfig.get("AWSSecretKey")); +        mWin.setS3Bucket(mAppConfig.get("S3Bucket")); +        mWin.setMetadataServer(mAppConfig.get("MetadataURL")); +        mWin.setHasAuthKey(mAppConfig.get("MetadataAuthKey") != null); +    } + +    /** +     * Populate the config from the window. +     */ +    private void populateConfig() { +        mAppConfig.put("AWSAccessKeyId", mWin.getAWSKey()); +        mAppConfig.put("AWSSecretKey", mWin.getAWSSecret()); +        mAppConfig.put("S3Bucket", mWin.getS3Bucket()); +        mAppConfig.put("MetadataURL", mWin.getMetadataServer()); +    } + +    /** +     * Display the window. +     */ +    public void run() { +        mWin.setVisible(true); +    } + +    /** +     * Get an authorization token from the metadata service. +     */ +    private void getAuthorization() { + +    } +} diff --git a/src/main/java/net/jesterpm/podcastuploader/control/ObservableTask.java b/src/main/java/net/jesterpm/podcastuploader/control/ObservableTask.java new file mode 100644 index 0000000..381145e --- /dev/null +++ b/src/main/java/net/jesterpm/podcastuploader/control/ObservableTask.java @@ -0,0 +1,40 @@ +/* + * Copyright 2012 Jesse Morgan + */ + +package net.jesterpm.podcastuploader.control; + +import java.util.Observable; + +/** + * ProgressInterface is an abstract based class for any interface that + * provides progress notification. The class tracks the progress of multiple + * simultaneous tasks and aggregates their progress. + * + * @author Jesse Morgan <jesse@jesterpm.net> + */ +public abstract class ObservableTask extends Observable { +    private float mProgress; + +    public ObservableTask() { +        mProgress = 0; +    } + +    /** +     * @return The percentage complete as a float between 0 and 1. +     */ +    public float getProgress() { +        return mProgress; +    } + +    /** +     * Set the current progress. +     * +     * @param percentComplete The percentage complete as a float between 0 and 1. +     */ +    protected void setProgress(float percentComplete) { +        mProgress = percentComplete; +        setChanged(); +        notifyObservers(); +    } +} diff --git a/src/main/java/net/jesterpm/podcastuploader/control/PublishPodcastTask.java b/src/main/java/net/jesterpm/podcastuploader/control/PublishPodcastTask.java new file mode 100644 index 0000000..fdee900 --- /dev/null +++ b/src/main/java/net/jesterpm/podcastuploader/control/PublishPodcastTask.java @@ -0,0 +1,25 @@ +/* + * Copyright 2012 Jesse Morgan + */ + +package net.jesterpm.podcastuploader.control; + +import java.util.Map; + +import net.jesterpm.podcastuploader.config.Config; + +/** + * Task to publish podcast meta-data to a server. + * + * @author Jesse Morgan <jesse@jesterpm.net> + */ +class PublishPodcastTask implements Runnable { +    public PublishPodcastTask(final Config appConfig, final Map<String, String> metadata) { + +    } + +    @Override +    public void run() { + +    } +} diff --git a/src/main/java/net/jesterpm/podcastuploader/control/S3UploadTask.java b/src/main/java/net/jesterpm/podcastuploader/control/S3UploadTask.java new file mode 100644 index 0000000..8e9f9ab --- /dev/null +++ b/src/main/java/net/jesterpm/podcastuploader/control/S3UploadTask.java @@ -0,0 +1,86 @@ +/* + * Copyright 2012 Jesse Morgan + */ + +package net.jesterpm.podcastuploader.control; + +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.s3.AmazonS3Client;  + +import java.util.concurrent.ExecutorService; + +import net.jesterpm.podcastuploader.config.Config; + +/** + * Task for uploading a single file to S3. + * + * @author Jesse Morgan <jesse@jesterpm.net> + */ +public class S3UploadTask extends ObservableTask implements Runnable { +    private final ExecutorService mExecutor; + +    private final AmazonS3Client mClient; +     +    private final String mBucket; +    private final String mLocalFile; +    private final String mS3Key; + +    private int mCurrentChunk; +    private int mTotalChunks;  + +    /** +     * Prepare a new S3UploadTask. +     * +     * @param appConfig The current running appConfig. +     * @param localFile The name of the local file to upload. +     * @param remoteFile The key to use for the file in S3. +     */ +    public S3UploadTask(final Config appConfig, final String localFile, +           final String remoteFile, final ExecutorService executor) { + +        mExecutor = executor; + +        mClient = new AmazonS3Client(new BasicAWSCredentials( +                    appConfig.get("AWSAccessKeyId"), appConfig.get("AWSSecretKey"))); + +        mBucket = appConfig.get("S3Bucket"); +        mLocalFile = localFile; +        mS3Key = remoteFile; + +        mCurrentChunk = 0; +        mTotalChunks = 1;  // Avoid div-by-0 in getProgress() +    } + +    /** +     * Start the upload. +     */ +    @Override +    public void run() { +        // Create bucket if needed +        createBucket(); + +        // Start Upload +    } + +    /** +     * Create the S3 bucket if it doesn't already exist. +     */ +    private void createBucket() { + +    } + +    /** +     * @return The number of file parts uploaded over the total number of file parts. +     */ +    @Override +    public float getProgress() { +        return (float) mCurrentChunk / mTotalChunks; +    } + +    /** +     * @return The S3 key this object uploads to. +     */ +    public String getS3Key() { +        return mS3Key; +    } +} diff --git a/src/main/java/net/jesterpm/podcastuploader/control/UploadTask.java b/src/main/java/net/jesterpm/podcastuploader/control/UploadTask.java new file mode 100644 index 0000000..a5fdf05 --- /dev/null +++ b/src/main/java/net/jesterpm/podcastuploader/control/UploadTask.java @@ -0,0 +1,212 @@ +/* + * Copyright 2012 Jesse Morgan + */ + +package net.jesterpm.podcastuploader.control; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; + +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import net.jesterpm.podcastuploader.config.Config; +import net.jesterpm.podcastuploader.ui.ProgressInterface; + +/** + *  + * @author Jesse Morgan <jesse@jesterpm.net> + */ +public class UploadTask { +    /** +     * This is the filename of the metadata file with a path separator prefixed. +     */ +    private static final String METADATA_FILE = +        System.getProperty("file.separator") + "/metadata.txt"; + +    /** +     * Progress window. +     */ +    private final ProgressInterface mProgressInterface; + +    /** +     * Application config. +     */ +    private final Config mAppConfig; + +    /** +     * Podcast metadata file. +     */ +    private final Config mMetadata; + +    /** +     * Thread Pool used for and by the UploadTasks. +     */ +    private final ExecutorService mExecutor; + +    /** +     * UploadTask Constructor. +     * @param appconfig The application config +     * @param win The progress window user interface. +     * @param dir The directory to upload. +     */ +    public UploadTask(final Config appconfig, final ProgressInterface progressInterface, +           final String dir) { + +        mProgressInterface = progressInterface; +        mAppConfig = appconfig; + +        mMetadata = new Config(dir + METADATA_FILE); + +        mExecutor = Executors.newSingleThreadExecutor(); +    } + +    /** +     * Uploads the podcast described in the metadata file. +     * +     * Expected keys: +     *   date             The podcast date. +     *   title            The podcast title. +     *   author           The podcast author. +     * +     * Optional keys: +     *   series           The series the video is part of. +     *   video            Name of the video file. +     *   audio            Name of the audio file. +     *   video_lowres     Name of the low-res video file +     *   image            Image associated with the podcast +     *   mobileimage      Image for mobile phones +     *   description      Podcast description +     * +     * Video and/or audio is required, as without one of them there is nothing +     * to upload. +     */ +    public void run() { +        final String baseFilename; + +        try { +            DateFormat fmt = DateFormat.getDateInstance(DateFormat.SHORT); +            final Date date = fmt.parse(mMetadata.get("date")); + +            fmt = new SimpleDateFormat("yyyyMMdd"); +            baseFilename = fmt.format(date) + "-" + safeString(mMetadata.get("title")); + +        } catch (ParseException e) { +            throw new RuntimeException(e); +        } + +        // Build a list of files to upload. +        Map<String, S3UploadTask> files = getFilesToUpload(baseFilename); +        for (S3UploadTask task : files.values()) { +            mExecutor.submit(task); +            mProgressInterface.monitorTask(task); +        } + +        // Wait until all uploads complete. + +        // Publish the podcast metadata. +        Map<String, String> metadata = new HashMap<String, String>(mMetadata.getMap()); + +        for (Map.Entry<String, S3UploadTask> entry : files.entrySet()) { +            metadata.put(entry.getKey(), entry.getValue().getS3Key()); +        } + +        PublishPodcastTask task = new PublishPodcastTask(mAppConfig, metadata); +        task.run(); +    } + +    /** +     * Build the list of S3UploadTasks to execute. +     */ +    private Map<String, S3UploadTask> getFilesToUpload(final String basename) { +        Map<String, S3UploadTask> files = new HashMap<String, S3UploadTask>(); + +        String localFile; +        String remoteFile; + +        localFile = mMetadata.get("video"); +        if (localFile != null) { +            remoteFile = basename + "-video" + fileExtension(localFile); +            files.put("video", new S3UploadTask(mAppConfig, localFile,  +                        remoteFile, mExecutor)); +        } + +        localFile = mMetadata.get("video_lowres"); +        if (localFile != null) { +            remoteFile = basename + "-videolow" + fileExtension(localFile); +            files.put("video_lowres", new S3UploadTask(mAppConfig, localFile, +                    remoteFile, mExecutor)); +        } + +        localFile = mMetadata.get("audio"); +        if (localFile != null) { +            remoteFile = basename + "-audio" + fileExtension(localFile); +            files.put("audio", new S3UploadTask(mAppConfig, localFile, +                    remoteFile, mExecutor)); +        } +  +        localFile = mMetadata.get("image"); +        if (localFile != null) { +            remoteFile = basename + "-image" + fileExtension(localFile); +            files.put("image", new S3UploadTask(mAppConfig, localFile,  +                    remoteFile, mExecutor)); +        } + +        localFile = mMetadata.get("mobileimage"); +        if (localFile != null) { +            remoteFile = basename + "-mobileimage" + fileExtension(localFile); +            files.put("mobileimage", new S3UploadTask(mAppConfig, localFile, +                    remoteFile, mExecutor)); +        } + +        return files; +    } + +    /** +     * @return the extension from the given filename. +     */ +    private String fileExtension(final String file) { +        int pos = file.lastIndexOf('.'); +  +        if (pos == -1) { +            return ""; + +        } else { +            return file.substring(pos); +        } +    } + +    /** +     * Transform str into a URL safe string by substituting spaces with dashes +     * and dropping all other non-alphanumeric characters. +     * +     * @param str The String to transform. +     * @return The transformed string. +     */ +    private String safeString(String str) { +        char[] newStr = str.trim().toLowerCase().toCharArray(); +        boolean firstSpace = true; +        int p = 0; +        for (int i = 0; i < newStr.length; i++) { +            if (Character.isWhitespace(newStr[i])) { +                if (firstSpace) { +                    newStr[p++] = '-'; +                    firstSpace = false; +                } + +            } else if (Character.isLetterOrDigit(newStr[i])) { +                newStr[p++] = newStr[i]; +                firstSpace = true; + +            } else { +                firstSpace = true; +            } +        } +          +        return new String(newStr, 0, p);        +    } +} diff --git a/src/main/java/net/jesterpm/podcastuploader/ui/Action.java b/src/main/java/net/jesterpm/podcastuploader/ui/Action.java new file mode 100644 index 0000000..5ec4bde --- /dev/null +++ b/src/main/java/net/jesterpm/podcastuploader/ui/Action.java @@ -0,0 +1,17 @@ +/* + * Copyright 2012 Jesse Morgan + */ + +package net.jesterpm.podcastuploader.ui; + +/** + * Action handler for the UI. + * + * @author Jesse Morgan <jesse@jesterpm.net> + */ +public interface Action { +    /** +     * This method is called when the action is performed. +     */ +    public void onAction(); +} diff --git a/src/main/java/net/jesterpm/podcastuploader/ui/ConfigurationWindow.java b/src/main/java/net/jesterpm/podcastuploader/ui/ConfigurationWindow.java new file mode 100644 index 0000000..d0dc96d --- /dev/null +++ b/src/main/java/net/jesterpm/podcastuploader/ui/ConfigurationWindow.java @@ -0,0 +1,157 @@ +/* + * Copyright 2012 Jesse Morgan + */ + +package net.jesterpm.podcastuploader.ui; + +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.awt.Dimension; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; + +import javax.swing.border.EmptyBorder; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTextField; + +/** + * UI for the configuration window. + * + * @author Jesse Morgan <jesse@jesterpm.net> + */ +public class ConfigurationWindow extends JFrame { +    private final JTextField mAWSKey; +    private final JTextField mAWSSecret; +    private final JTextField mS3Bucket; +    private final JTextField mMetadataServer; +    private final JButton mAuthorize; +    private final JButton mSave; + +    public ConfigurationWindow() { +        super("Podcast Uploader Configuration"); + +        JPanel panel = new JPanel(); +        panel.setBorder(new EmptyBorder(10, 10, 10, 10)); +        panel.setLayout(new GridBagLayout()); +        add(panel); + +        mAWSKey = new JTextField(); +        mAWSSecret = new JTextField(); +        mS3Bucket = new JTextField(); +        mMetadataServer = new JTextField(); +        mAuthorize = new JButton("Authorize App"); +        mSave = new JButton("Save"); +        mSave.setDefaultCapable(true); + +        GridBagConstraints labelConstraint = new GridBagConstraints(); +        GridBagConstraints fieldConstraint = new GridBagConstraints(); + +        labelConstraint.gridx = 0; +        labelConstraint.gridy = GridBagConstraints.RELATIVE; + +        fieldConstraint.gridx = 1; +        fieldConstraint.gridy = GridBagConstraints.RELATIVE; +        fieldConstraint.fill = GridBagConstraints.HORIZONTAL; +        fieldConstraint.weightx = 1; + +        panel.add(new JLabel("Podcast Server:", JLabel.RIGHT), labelConstraint); +        panel.add(mMetadataServer, fieldConstraint); + +        panel.add(new JLabel("AWS Access Key:", JLabel.RIGHT), labelConstraint); +        panel.add(mAWSKey, fieldConstraint); +         +        panel.add(new JLabel("AWS Secret Key:", JLabel.RIGHT), labelConstraint); +        panel.add(mAWSSecret, fieldConstraint); + +        panel.add(new JLabel("S3 Bucket:", JLabel.RIGHT), labelConstraint); +        panel.add(mS3Bucket, fieldConstraint); + +        GridBagConstraints buttonConstraint = new GridBagConstraints(); +        buttonConstraint.gridy = 5; +        buttonConstraint.gridwidth = 2; +        buttonConstraint.weighty = 1; +        panel.add(mAuthorize, buttonConstraint); + +        buttonConstraint.gridx = 1; +        buttonConstraint.gridy = 7; +        buttonConstraint.gridwidth = 1; +        buttonConstraint.anchor = GridBagConstraints.LAST_LINE_END; +        panel.add(mSave, buttonConstraint); + +        pack(); +        Dimension d = getPreferredSize(); +        d.height += 20; +        d.width  += 50; +        setMinimumSize(d); +    }  + +    public void setAWSKey(final String value) { +        mAWSKey.setText(value); +    } + +    public String getAWSKey() { +        return mAWSKey.getText(); +    } + +    public void setAWSSecret(final String value) { +        mAWSSecret.setText(value); +    } + +    public String getAWSSecret() { +        return mAWSSecret.getText(); +    } + +    public void setS3Bucket(final String value) { +        mS3Bucket.setText(value); +    } + +    public String getS3Bucket() { +        return mS3Bucket.getText(); +    } + +    public void setMetadataServer(final String value) { +        mMetadataServer.setText(value); +    } + +    public String getMetadataServer() { +        return mMetadataServer.getText(); +    } + +    public void setHasAuthKey(final boolean value) { +        if (value) { +            mAuthorize.setText("Reauthorize App"); +         +        } else { +            mAuthorize.setText("Authorize App"); +        } +    } + +    public void addAuthorizeAction(final Action a) { +        mAuthorize.addActionListener(new ActionListener() { +            public void actionPerformed(final ActionEvent e) { +                a.onAction(); +            } +        }); +    } +     +    public void addSaveAction(final Action a) { +        mSave.addActionListener(new ActionListener() { +            public void actionPerformed(final ActionEvent e) { +                a.onAction(); +            } +        }); +    } + +    public void addCancelAction(final Action a) { +        addWindowListener(new WindowAdapter() { +            public void windowClosing(final WindowEvent e) { +                a.onAction(); +            } +        });  +    } +} diff --git a/src/main/java/net/jesterpm/podcastuploader/ui/ProgressInterface.java b/src/main/java/net/jesterpm/podcastuploader/ui/ProgressInterface.java new file mode 100644 index 0000000..3e85667 --- /dev/null +++ b/src/main/java/net/jesterpm/podcastuploader/ui/ProgressInterface.java @@ -0,0 +1,78 @@ +/* + * Copyright 2012 Jesse Morgan + */ + +package net.jesterpm.podcastuploader.ui; + +import java.util.HashMap; +import java.util.Map; +import java.util.Observer; +import java.util.Observable; + +import net.jesterpm.podcastuploader.control.ObservableTask; + +/** + * ProgressInterface is an abstract based class for any interface that + * provides progress notification. The class tracks the progress of multiple + * simultaneous tasks and aggregates their progress. + * + * @author Jesse Morgan <jesse@jesterpm.net> + */ +public abstract class ProgressInterface implements Observer { +    private Map<ObservableTask, Float> mProgressMap; + +    /** +     * Creates a new ProgressInterface monitoring nothing. +     */ +    public ProgressInterface() { +        mProgressMap = new HashMap<ObservableTask, Float>(); +    } +     +    /** +     * Begin monitoring the given task. +     * +     * @param task The ObservableTask to monitor. +     */ +    public void monitorTask(final ObservableTask task) { +        task.addObserver(this); +        mProgressMap.put(task, 0f); +        setProgress(getProgress()); +    } + +    /** +     * Called when the progress changes. +     * +     * @param progress The current percentage complete, +     *                 indicated by a float ranging from 0 to 1. +     */ +    protected abstract void setProgress(float percentComplete); + +    /** +     * Called when the progress for a task changes. +     * @param task The task that changed. +     * @param arg  Unused. +     */ +    @Override +    public void update(Observable task, Object arg) { +        ObservableTask observableTask = (ObservableTask) task; +        mProgressMap.put(observableTask, observableTask.getProgress()); +        setProgress(getProgress()); +    } + +    /** +     * @return A float ranging from 0 to 1 representing the aggregate progress. +     *         If the ProgressInterface is not monitoring any tasks, this will return 0. +     */ +    public float getProgress() { +        if (mProgressMap.size() == 0) { +            return 0; +        } + +        float totalProgress = 0; +        for (float taskProgress : mProgressMap.values()) { +            totalProgress += taskProgress; +        } + +        return totalProgress / mProgressMap.size(); +    } +} diff --git a/src/main/java/net/jesterpm/podcastuploader/ui/ProgressWindow.java b/src/main/java/net/jesterpm/podcastuploader/ui/ProgressWindow.java new file mode 100644 index 0000000..01daa83 --- /dev/null +++ b/src/main/java/net/jesterpm/podcastuploader/ui/ProgressWindow.java @@ -0,0 +1,16 @@ +/* + * Copyright 2012 Jesse Morgan + */ + +package net.jesterpm.podcastuploader.ui; + +/** + *  + * @author Jesse Morgan <jesse@jesterpm.net> + */ +public class ProgressWindow extends ProgressInterface { +    @Override +    public void setProgress(float percentComplete) { + +    } +}  | 
