Index: UploadDataGuiPlugin.java =================================================================== --- UploadDataGuiPlugin.java (revision 24040) +++ UploadDataGuiPlugin.java (working copy) @@ -10,19 +10,19 @@ import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; -import java.util.List; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.actions.JosmAction; -import org.openstreetmap.josm.gui.layer.GpxLayer; import org.openstreetmap.josm.plugins.Plugin; import org.openstreetmap.josm.plugins.PluginInformation; import org.openstreetmap.josm.tools.Shortcut; + /** * - * @author subhodip + * @author subhodip, ax */ -public class UploadDataGuiPlugin extends Plugin{ +public class UploadDataGuiPlugin extends Plugin { + UploadAction openaction; public UploadDataGuiPlugin(PluginInformation info) { @@ -31,35 +31,30 @@ Main.main.menu.toolsMenu.add(openaction); } - class UploadAction extends JosmAction{ + class UploadAction extends JosmAction { + public UploadAction(){ super(tr("Upload Traces"), "UploadAction", tr("Uploads traces to openstreetmap.org"), - Shortcut.registerShortcut("tools:uploadtraces", tr("Tool: {0}", tr("Upload Traces")), - KeyEvent.VK_G, Shortcut.GROUP_MENU), false); + Shortcut.registerShortcut("tools:uploadtraces", tr("Tool: {0}", tr("Upload Traces")), + KeyEvent.VK_G, Shortcut.GROUP_MENU), false); } + public void actionPerformed(ActionEvent e) { UploadDataGui go = new UploadDataGui(); go.setVisible(true); } - @Override - protected void updateEnabledState() { - // enable button if there is "one active GpxLayer" or "exactly one GpxLayer in the list of all layers available" - if(Main.map == null - || Main.map.mapView == null - || Main.map.mapView.getActiveLayer() == null - || !(Main.map.mapView.getActiveLayer() instanceof GpxLayer)) { - setEnabled(false); - } else { - setEnabled(true); - } - - if(Main.map != null && Main.map.mapView.getNumLayers() > 1) { - List list = Main.map.mapView.getLayersOfType(GpxLayer.class); - if (list.size() == 1) - setEnabled(true); - } - - } + // because LayerListDialog doesn't provide a way to hook into "layer selection changed" + // but the layer selection (*not* activation) is how we determine the layer to be uploaded + // we have to let the upload trace menu always be enabled +// @Override +// protected void updateEnabledState() { +// // enable button if ... @see autoSelectTrace() +// if (UploadOsmConnection.getInstance().autoSelectTrace() == null) { +// setEnabled(false); +// } else { +// setEnabled(true); +// } +// } } -} \ No newline at end of file +} Index: UploadDataGui.java =================================================================== --- UploadDataGui.java (revision 24040) +++ UploadDataGui.java (working copy) @@ -19,38 +19,34 @@ import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; -import java.nio.ByteBuffer; -import java.nio.CharBuffer; -import java.nio.charset.Charset; -import java.nio.charset.CharsetEncoder; import java.text.DecimalFormat; import java.text.SimpleDateFormat; +import java.util.Collections; import java.util.Date; +import java.util.LinkedList; +import java.util.List; import javax.swing.JComboBox; import javax.swing.JLabel; import javax.swing.JPanel; -import javax.swing.JTextField; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.data.gpx.GpxData; import org.openstreetmap.josm.gui.ExtendedDialog; import org.openstreetmap.josm.gui.JMultilineLabel; -import org.openstreetmap.josm.gui.MapView; import org.openstreetmap.josm.gui.PleaseWaitRunnable; -import org.openstreetmap.josm.gui.layer.GpxLayer; -import org.openstreetmap.josm.gui.layer.Layer; import org.openstreetmap.josm.gui.progress.ProgressMonitor; +import org.openstreetmap.josm.gui.widgets.HistoryComboBox; import org.openstreetmap.josm.io.GpxWriter; -import org.openstreetmap.josm.tools.Base64; import org.openstreetmap.josm.tools.GBC; import org.openstreetmap.josm.tools.UrlLabel; /** * - * @author subhodip, xeen + * @author subhodip, xeen, ax */ public class UploadDataGui extends ExtendedDialog { + /** * This enum contains the possible values for the visibility field and their * explanation. Provides some methods for easier handling. @@ -86,19 +82,14 @@ } } - - // User for log in when uploading trace - private String username = Main.pref.get("osm-server.username"); - private String password = Main.pref.get("osm-server.password"); - // Fields are declared here for easy access // Do not remove the space in JMultilineLabel. Otherwise the label will be empty // as we don't know its contents yet and therefore have a height of 0. This will // lead to unnecessary scrollbars. private JMultilineLabel OutputDisplay = new JMultilineLabel(" "); - private JTextField descriptionField = new JTextField(50); - private JTextField tagsField = new JTextField(50); - private JComboBox visibilityCombo = new JComboBox(); + private HistoryComboBox descriptionField; + private HistoryComboBox tagsField; + private JComboBox visibilityCombo; // Constants used when generating upload request private static final String API_VERSION = "0.6"; @@ -106,12 +97,8 @@ private static final String LINE_END = "\r\n"; private static final String uploadTraceText = tr("Upload Trace"); - // Filename and current date. Date will be used as fallback if filename not available - private String datename = new SimpleDateFormat("yyMMddHHmmss").format(new Date()); - private String filename = ""; - private boolean cancelled = false; - + public UploadDataGui() { // Initalizes ExtendedDialog super(Main.parent, @@ -120,12 +107,13 @@ true ); JPanel content = initComponents(); - autoSelectTrace(); + GpxData gpxData = UploadOsmConnection.getInstance().autoSelectTrace(); + initTitleAndDescriptionFromGpxData(gpxData); // this is changing some dialog elements, so it (probably) must be before the following setContent(content); setButtonIcons(new String[] { "uploadtrace.png", "cancel.png" }); setupDialog(); - buttons.get(0).setEnabled(!checkForGPXLayer()); + buttons.get(0).setEnabled(gpxData != null); } /** @@ -133,19 +121,40 @@ * @return JPanel with components */ private JPanel initComponents() { + // visibilty JLabel visibilityLabel = new JLabel(tr("Visibility")); visibilityLabel.setToolTipText(tr("Defines the visibility of your trace for other OSM users.")); + + visibilityCombo = new JComboBox(); + visibilityCombo.setEditable(false); for(visibility v : visibility.values()) { visibilityCombo.addItem(v.description); } + visibilityCombo.setSelectedItem(visibility.valueOf(Main.pref.get("directupload.visibility.last-used", visibility.PRIVATE.name())).description); UrlLabel visiUrl = new UrlLabel(tr("http://wiki.openstreetmap.org/wiki/Visibility_of_GPS_traces"), tr("(What does that mean?)")); + // description JLabel descriptionLabel = new JLabel(tr("Description")); + descriptionField = new HistoryComboBox(); descriptionField.setToolTipText(tr("Please enter Description about your trace.")); + + List descHistory = new LinkedList(Main.pref.getCollection("directupload.description.history", new LinkedList())); + // we have to reverse the history, because ComboBoxHistory will reverse it againin addElement() + // XXX this should be handled in HistoryComboBox + Collections.reverse(descHistory); + descriptionField.setPossibleItems(descHistory); + // tags JLabel tagsLabel = new JLabel(tr("Tags (comma delimited)")); + tagsField = new HistoryComboBox(); tagsField.setToolTipText(tr("Please enter tags about your trace.")); + List tagsHistory = new LinkedList(Main.pref.getCollection("directupload.tags.history", new LinkedList())); + // we have to reverse the history, because ComboBoxHistory will reverse it againin addElement() + // XXX this should be handled in HistoryComboBox + Collections.reverse(tagsHistory); + tagsField.setPossibleItems(tagsHistory); + JPanel p = new JPanel(new GridBagLayout()); OutputDisplay.setMaxWidth(findMaxDialogSize().width-10); @@ -164,36 +173,19 @@ return p; } - /** - * This function will automatically select a GPX layer if it's the only one. - * If a GPX layer is found, its filename will be parsed and displayed - */ - private void autoSelectTrace() { - // If no GPX layer is selected, select one for the user if there is only one GPX layer - if(Main.map != null && Main.map.mapView != null) { - MapView mv=Main.map.mapView; - if(!(mv.getActiveLayer() instanceof GpxLayer)) { - Layer lastLayer=null; - int layerCount=0; - for (Layer l : mv.getAllLayers()) { - if(l instanceof GpxLayer) { - lastLayer = l; - layerCount++; - } - } - if(layerCount == 1) mv.setActiveLayer(lastLayer); - } - - if(mv.getActiveLayer() instanceof GpxLayer) { - GpxData data=((GpxLayer)Main.map.mapView.getActiveLayer()).data; - try { - filename = data.storageFile.getName() - .replaceAll("[&?/\\\\]"," ").replaceAll("(\\.[^.]*)$",""); - } catch(Exception e) { } - descriptionField.setText(getFilename()); - OutputDisplay.setText(tr("Selected track: {0}", getFilename())); - } - } + private void initTitleAndDescriptionFromGpxData(GpxData gpxData) { + // TODO Auto-generated method stub + String description, title; + try { + description = gpxData.storageFile.getName().replaceAll("[&?/\\\\]"," ").replaceAll("(\\.[^.]*)$",""); + title = tr("Selected track: {0}", gpxData.storageFile.getName()); + } + catch(Exception e) { + description = new SimpleDateFormat("yyMMddHHmmss").format(new Date()); + title = tr("No GPX layer selected. Cannot upload a trace."); + } + OutputDisplay.setText(title); + descriptionField.setText(description); } /** @@ -204,53 +196,54 @@ * @param GpxData The GPX Data to upload */ private void upload(String description, String tags, String visi, GpxData gpxData, ProgressMonitor progressMonitor) throws IOException { - progressMonitor.beginTask(null); + progressMonitor.beginTask(tr("Uploading trace ...")); try { - if(checkForErrors(username, password, description, gpxData)) + if (checkForErrors(description, gpxData)) { return; + } // Clean description/tags from disallowed chars - description = description.replaceAll("[&?/\\\\]"," "); - tags = tags.replaceAll("[&?/\\\\.;]"," "); + description = description.replaceAll("[&?/\\\\]", " "); + tags = tags.replaceAll("[&?/\\\\.;]", " "); // Set progress dialog to indeterminate while connecting progressMonitor.indeterminateSubTask(tr("Connecting...")); - try { - // Generate data for upload - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - writeGpxFile(baos, "file", gpxData); - writeField(baos, "description", description); - writeField(baos, "tags", (tags != null && tags.length() > 0) ? tags : ""); - writeField(baos, "visibility", visi); - writeString(baos, "--" + BOUNDARY + "--" + LINE_END); + // Generate data for upload + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + writeGpxFile(baos, "file", gpxData); + writeField(baos, "description", description); + writeField(baos, "tags", (tags != null && tags.length() > 0) ? tags : ""); + writeField(baos, "visibility", visi); + writeString(baos, "--" + BOUNDARY + "--" + LINE_END); - ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); - HttpURLConnection conn = setupConnection(baos.size()); + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + HttpURLConnection conn = setupConnection(baos.size()); - progressMonitor.setTicksCount(baos.size()); - progressMonitor.subTask(null); + progressMonitor.setTicksCount(baos.size()); + progressMonitor.subTask(null); - try { - flushToServer(bais, conn.getOutputStream(), progressMonitor); - } catch(Exception e) {} + flushToServer(bais, conn.getOutputStream(), progressMonitor); - if(cancelled) { - conn.disconnect(); - OutputDisplay.setText(tr("Upload cancelled")); - buttons.get(0).setEnabled(true); - cancelled = false; - } else { - boolean success = finishUpConnection(conn); - buttons.get(0).setEnabled(!success); - if(success) - buttons.get(1).setText(tr("Close")); + if (cancelled) { + conn.disconnect(); + OutputDisplay.setText(tr("Upload cancelled")); + buttons.get(0).setEnabled(true); + cancelled = false; + } + else { + boolean success = finishUpConnection(conn); + buttons.get(0).setEnabled(!success); + if (success) { + buttons.get(1).setText(tr("Close")); } - } catch(Exception e) { - OutputDisplay.setText(tr("Error while uploading")); - e.printStackTrace(); } - } finally { + } + catch (Exception e) { + OutputDisplay.setText(tr("Error while uploading")); + e.printStackTrace(); + } + finally { progressMonitor.finishTask(); } } @@ -262,10 +255,6 @@ * @return HttpURLConnection The set up conenction */ private HttpURLConnection setupConnection(int contentLength) throws Exception { - // Encode username and password - CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder(); - String auth = username + ":" + password; - ByteBuffer bytes = encoder.encode(CharBuffer.wrap(auth)); // Upload URL URL url = new URL("http://www.openstreetmap.org/api/" + API_VERSION + "/gpx/create"); @@ -276,7 +265,10 @@ c.setConnectTimeout(15000); c.setRequestMethod("POST"); c.setDoOutput(true); - c.addRequestProperty("Authorization", "Basic " + Base64.encode(bytes)); + // unfortunately, addAuth() is protected, so we need to subclass OsmConnection + // XXX make addAuth public. + UploadOsmConnection.getInstance().addAuthHack(c); + c.addRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY); c.addRequestProperty("Connection", "close"); // counterpart of keep-alive c.addRequestProperty("Expect", ""); @@ -366,14 +358,11 @@ /** * Checks for common errors and displays them in OutputDisplay if it finds any. * Returns whether errors have been found or not. - * @param String OSM username - * @param String OSM password * @param String GPX track description * @param GpxData the GPX data to upload * @return boolean true if errors have been found */ - private boolean checkForErrors(String username, String password, - String description, GpxData gpxData) { + private boolean checkForErrors(String description, GpxData gpxData) { String errors=""; if(description == null || description.length() == 0) errors += tr("No description provided. Please provide some description."); @@ -381,48 +370,37 @@ if(gpxData == null) errors += tr("No GPX layer selected. Cannot upload a trace."); - if(username == null || username.length() == 0) - errors += tr("No username provided."); - - if(password == null || password.length() == 0) - errors += tr("No password provided."); - OutputDisplay.setText(errors); return errors.length() > 0; } /** - * Checks if a GPX layer is selected and returns the result. Also writes an error - * message to OutputDisplay if result is false. - * @return boolean True, if /no/ GPX layer is selected - */ - private boolean checkForGPXLayer() { - if(Main.map == null - || Main.map.mapView == null - || Main.map.mapView.getActiveLayer() == null - || !(Main.map.mapView.getActiveLayer() instanceof GpxLayer)) { - OutputDisplay.setText(tr("No GPX layer selected. Cannot upload a trace.")); - return true; - } - return false; - } - - - /** * This creates the uploadTask that does the actual work and hands it to the main.worker to be executed. */ private void setupUpload() { - if(checkForGPXLayer()) return; + final GpxData gpxData = UploadOsmConnection.getInstance().autoSelectTrace(); + if (gpxData == null) { + return; + } // Disable Upload button so users can't just upload that track again buttons.get(0).setEnabled(false); + + // save history + Main.pref.put("directupload.visibility.last-used", visibility.desc2visi(visibilityCombo.getSelectedItem().toString()).name()); + + descriptionField.addCurrentItemToHistory(); + Main.pref.putCollection("directupload.description.history", descriptionField.getHistory()); + tagsField.addCurrentItemToHistory(); + Main.pref.putCollection("directupload.tags.history", tagsField.getHistory()); + PleaseWaitRunnable uploadTask = new PleaseWaitRunnable(tr("Uploading GPX Track")){ @Override protected void realRun() throws IOException { upload(descriptionField.getText(), tagsField.getText(), visibility.desc2visi(visibilityCombo.getSelectedItem()).toString(), - ((GpxLayer)Main.map.mapView.getActiveLayer()).data, + gpxData, progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false) ); } @@ -459,7 +437,7 @@ private void writeGpxFile(ByteArrayOutputStream baos, String name, GpxData gpxData) throws IOException { writeBoundary(baos); writeString(baos, "Content-Disposition: form-data; name=\"" + name + "\"; "); - writeString(baos, "filename=\"" + getFilename() + ".gpx" + "\""); + writeString(baos, "filename=\"" + gpxData.storageFile.getName() + "\""); writeLineEnd(baos); writeString(baos, "Content-Type: application/octet-stream"); writeLineEnd(baos); @@ -497,15 +475,6 @@ } /** - * Returns the filename of the GPX file to be upload. If not available, returns current date - * as an alternative - * @param String - */ - private String getFilename() { - return filename.equals("") ? datename : filename; - } - - /** * Overrides the default actions. Will not close the window when upload trace is clicked */ @Override protected void buttonAction(int buttonIndex, ActionEvent evt) { Index: UploadOsmConnection.java =================================================================== --- UploadOsmConnection.java (revision 0) +++ UploadOsmConnection.java (revision 0) @@ -0,0 +1,83 @@ +// ... + +package org.openstreetmap.josm.plugins.DirectUpload; + +import java.net.HttpURLConnection; +import java.util.List; + +import org.openstreetmap.josm.Main; +import org.openstreetmap.josm.data.gpx.GpxData; +import org.openstreetmap.josm.gui.MapView; +import org.openstreetmap.josm.gui.dialogs.LayerListDialog; +import org.openstreetmap.josm.gui.layer.GpxLayer; +import org.openstreetmap.josm.gui.layer.Layer; +import org.openstreetmap.josm.io.OsmConnection; +import org.openstreetmap.josm.io.OsmTransferException; + +/** + * Work-around and utility class for DirectUpload. + * + * @author ax + */ +public class UploadOsmConnection extends OsmConnection { + + // singleton, see http://en.wikipedia.org/wiki/Singleton_pattern#Traditional_simple_way + private static final UploadOsmConnection INSTANCE = new UploadOsmConnection(); + + // Private constructor prevents instantiation from other classes + private UploadOsmConnection() { + } + + public static UploadOsmConnection getInstance() { + return UploadOsmConnection.INSTANCE; + } + + // make protected OsmConnection::addAuth() available to others + public void addAuthHack(HttpURLConnection connection) throws OsmTransferException { + super.addAuth(connection); + } + + /** + * find which gpx layer holds the trace to upload. layers are tried in this order: + * + * 1. selected (*not* active - think "zoom to layer"), from first to last + * 2. not selectd - if there is only one + * 3. active + * + * @return data of the selected gpx layer, or null if there is none + */ + GpxData autoSelectTrace() { + if (Main.map != null && Main.map.mapView != null) { + MapView mv = Main.map.mapView; +// List allLayers = new ArrayList(mv.getAllLayersAsList()); // modifiable + List selectedLayers = LayerListDialog.getInstance().getModel().getSelectedLayers(); + List gpxLayersRemaining = mv.getLayersOfType(GpxLayer.class); + gpxLayersRemaining.removeAll(selectedLayers); + GpxLayer traceLayer = null; + // find the first gpx layer inside selected layers + for (Layer l : LayerListDialog.getInstance().getModel().getSelectedLayers()) { + if (l instanceof GpxLayer) { + traceLayer = (GpxLayer) l; + break; + } + } + if (traceLayer == null) { + // if there is none, try the none selected gpx layers. if there is only one, use it. + if (gpxLayersRemaining.size() == 1) { + traceLayer = gpxLayersRemaining.get(0); + } + // active layer + else if (mv.getActiveLayer() instanceof GpxLayer) { + traceLayer = (GpxLayer) mv.getActiveLayer(); + } + } + + if (traceLayer != null) { + GpxData data = traceLayer.data; + return data; + } + } + + return null; + } +}