From c5df48d96b5e48845a85f1495d0ed5ab1d1cc37b Mon Sep 17 00:00:00 2001 From: Jesse Morgan Date: Wed, 26 Apr 2017 21:39:07 -0700 Subject: Resurrecting my abandoned podcast upload tool and switching to Maven. --- .../jesterpm/podcastuploader/PodcastUploader.java | 64 +++++++ .../jesterpm/podcastuploader/config/Config.java | 137 +++++++++++++ .../podcastuploader/control/ConfigureTask.java | 85 +++++++++ .../podcastuploader/control/ObservableTask.java | 40 ++++ .../control/PublishPodcastTask.java | 25 +++ .../podcastuploader/control/S3UploadTask.java | 86 +++++++++ .../podcastuploader/control/UploadTask.java | 212 +++++++++++++++++++++ .../net/jesterpm/podcastuploader/ui/Action.java | 17 ++ .../podcastuploader/ui/ConfigurationWindow.java | 157 +++++++++++++++ .../podcastuploader/ui/ProgressInterface.java | 78 ++++++++ .../podcastuploader/ui/ProgressWindow.java | 16 ++ 11 files changed, 917 insertions(+) create mode 100644 src/main/java/net/jesterpm/podcastuploader/PodcastUploader.java create mode 100644 src/main/java/net/jesterpm/podcastuploader/config/Config.java create mode 100644 src/main/java/net/jesterpm/podcastuploader/control/ConfigureTask.java create mode 100644 src/main/java/net/jesterpm/podcastuploader/control/ObservableTask.java create mode 100644 src/main/java/net/jesterpm/podcastuploader/control/PublishPodcastTask.java create mode 100644 src/main/java/net/jesterpm/podcastuploader/control/S3UploadTask.java create mode 100644 src/main/java/net/jesterpm/podcastuploader/control/UploadTask.java create mode 100644 src/main/java/net/jesterpm/podcastuploader/ui/Action.java create mode 100644 src/main/java/net/jesterpm/podcastuploader/ui/ConfigurationWindow.java create mode 100644 src/main/java/net/jesterpm/podcastuploader/ui/ProgressInterface.java create mode 100644 src/main/java/net/jesterpm/podcastuploader/ui/ProgressWindow.java (limited to 'src/main/java') 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 + */ +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 "); + 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 + */ +public class Config { + private final String mFilename; + private Map 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(); + 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 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 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 + */ +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 + */ +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 + */ +class PublishPodcastTask implements Runnable { + public PublishPodcastTask(final Config appConfig, final Map 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 + */ +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 + */ +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 files = getFilesToUpload(baseFilename); + for (S3UploadTask task : files.values()) { + mExecutor.submit(task); + mProgressInterface.monitorTask(task); + } + + // Wait until all uploads complete. + + // Publish the podcast metadata. + Map metadata = new HashMap(mMetadata.getMap()); + + for (Map.Entry 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 getFilesToUpload(final String basename) { + Map files = new HashMap(); + + 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 + */ +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 + */ +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 + */ +public abstract class ProgressInterface implements Observer { + private Map mProgressMap; + + /** + * Creates a new ProgressInterface monitoring nothing. + */ + public ProgressInterface() { + mProgressMap = new HashMap(); + } + + /** + * 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 + */ +public class ProgressWindow extends ProgressInterface { + @Override + public void setProgress(float percentComplete) { + + } +} -- cgit v1.2.3