Ticket #4498: DirectUpload.patch
File DirectUpload.patch, 23.5 KB (added by , 15 years ago) |
---|
-
UploadDataGuiPlugin.java
10 10 11 11 import java.awt.event.ActionEvent; 12 12 import java.awt.event.KeyEvent; 13 import java.util.List;14 13 15 14 import org.openstreetmap.josm.Main; 16 15 import org.openstreetmap.josm.actions.JosmAction; 17 import org.openstreetmap.josm.gui.layer.GpxLayer;18 16 import org.openstreetmap.josm.plugins.Plugin; 19 17 import org.openstreetmap.josm.plugins.PluginInformation; 20 18 import org.openstreetmap.josm.tools.Shortcut; 19 21 20 /** 22 21 * 23 * @author subhodip 22 * @author subhodip, ax 24 23 */ 25 public class UploadDataGuiPlugin extends Plugin{ 24 public class UploadDataGuiPlugin extends Plugin { 25 26 26 UploadAction openaction; 27 27 28 28 public UploadDataGuiPlugin(PluginInformation info) { … … 31 31 Main.main.menu.toolsMenu.add(openaction); 32 32 } 33 33 34 class UploadAction extends JosmAction{ 34 class UploadAction extends JosmAction { 35 35 36 public UploadAction(){ 36 37 super(tr("Upload Traces"), "UploadAction", tr("Uploads traces to openstreetmap.org"), 37 Shortcut.registerShortcut("tools:uploadtraces", tr("Tool: {0}", tr("Upload Traces")),38 KeyEvent.VK_G, Shortcut.GROUP_MENU), false);38 Shortcut.registerShortcut("tools:uploadtraces", tr("Tool: {0}", tr("Upload Traces")), 39 KeyEvent.VK_G, Shortcut.GROUP_MENU), false); 39 40 } 41 40 42 public void actionPerformed(ActionEvent e) { 41 43 UploadDataGui go = new UploadDataGui(); 42 44 go.setVisible(true); 43 45 } 44 46 45 @Override 46 protected void updateEnabledState() { 47 // enable button if there is "one active GpxLayer" or "exactly one GpxLayer in the list of all layers available" 48 if(Main.map == null 49 || Main.map.mapView == null 50 || Main.map.mapView.getActiveLayer() == null 51 || !(Main.map.mapView.getActiveLayer() instanceof GpxLayer)) { 52 setEnabled(false); 53 } else { 54 setEnabled(true); 55 } 56 57 if(Main.map != null && Main.map.mapView.getNumLayers() > 1) { 58 List<GpxLayer> list = Main.map.mapView.getLayersOfType(GpxLayer.class); 59 if (list.size() == 1) 60 setEnabled(true); 61 } 62 63 } 47 // because LayerListDialog doesn't provide a way to hook into "layer selection changed" 48 // but the layer selection (*not* activation) is how we determine the layer to be uploaded 49 // we have to let the upload trace menu always be enabled 50 // @Override 51 // protected void updateEnabledState() { 52 // // enable button if ... @see autoSelectTrace() 53 // if (UploadOsmConnection.getInstance().autoSelectTrace() == null) { 54 // setEnabled(false); 55 // } else { 56 // setEnabled(true); 57 // } 58 // } 64 59 } 65 } 66 No newline at end of file 60 } -
UploadDataGui.java
19 19 import java.io.OutputStream; 20 20 import java.net.HttpURLConnection; 21 21 import java.net.URL; 22 import java.nio.ByteBuffer;23 import java.nio.CharBuffer;24 import java.nio.charset.Charset;25 import java.nio.charset.CharsetEncoder;26 22 import java.text.DecimalFormat; 27 23 import java.text.SimpleDateFormat; 24 import java.util.Collections; 28 25 import java.util.Date; 26 import java.util.LinkedList; 27 import java.util.List; 29 28 30 29 import javax.swing.JComboBox; 31 30 import javax.swing.JLabel; 32 31 import javax.swing.JPanel; 33 import javax.swing.JTextField;34 32 35 33 import org.openstreetmap.josm.Main; 36 34 import org.openstreetmap.josm.data.gpx.GpxData; 37 35 import org.openstreetmap.josm.gui.ExtendedDialog; 38 36 import org.openstreetmap.josm.gui.JMultilineLabel; 39 import org.openstreetmap.josm.gui.MapView;40 37 import org.openstreetmap.josm.gui.PleaseWaitRunnable; 41 import org.openstreetmap.josm.gui.layer.GpxLayer;42 import org.openstreetmap.josm.gui.layer.Layer;43 38 import org.openstreetmap.josm.gui.progress.ProgressMonitor; 39 import org.openstreetmap.josm.gui.widgets.HistoryComboBox; 44 40 import org.openstreetmap.josm.io.GpxWriter; 45 import org.openstreetmap.josm.tools.Base64;46 41 import org.openstreetmap.josm.tools.GBC; 47 42 import org.openstreetmap.josm.tools.UrlLabel; 48 43 49 44 /** 50 45 * 51 * @author subhodip, xeen 46 * @author subhodip, xeen, ax 52 47 */ 53 48 public class UploadDataGui extends ExtendedDialog { 49 54 50 /** 55 51 * This enum contains the possible values for the visibility field and their 56 52 * explanation. Provides some methods for easier handling. … … 86 82 } 87 83 } 88 84 89 90 // User for log in when uploading trace91 private String username = Main.pref.get("osm-server.username");92 private String password = Main.pref.get("osm-server.password");93 94 85 // Fields are declared here for easy access 95 86 // Do not remove the space in JMultilineLabel. Otherwise the label will be empty 96 87 // as we don't know its contents yet and therefore have a height of 0. This will 97 88 // lead to unnecessary scrollbars. 98 89 private JMultilineLabel OutputDisplay = new JMultilineLabel(" "); 99 private JTextField descriptionField = new JTextField(50);100 private JTextField tagsField = new JTextField(50);101 private JComboBox visibilityCombo = new JComboBox();90 private HistoryComboBox descriptionField; 91 private HistoryComboBox tagsField; 92 private JComboBox visibilityCombo; 102 93 103 94 // Constants used when generating upload request 104 95 private static final String API_VERSION = "0.6"; … … 106 97 private static final String LINE_END = "\r\n"; 107 98 private static final String uploadTraceText = tr("Upload Trace"); 108 99 109 // Filename and current date. Date will be used as fallback if filename not available110 private String datename = new SimpleDateFormat("yyMMddHHmmss").format(new Date());111 private String filename = "";112 113 100 private boolean cancelled = false; 114 101 115 102 public UploadDataGui() { 116 103 // Initalizes ExtendedDialog 117 104 super(Main.parent, … … 120 107 true 121 108 ); 122 109 JPanel content = initComponents(); 123 autoSelectTrace(); 110 GpxData gpxData = UploadOsmConnection.getInstance().autoSelectTrace(); 111 initTitleAndDescriptionFromGpxData(gpxData); // this is changing some dialog elements, so it (probably) must be before the following 124 112 setContent(content); 125 113 setButtonIcons(new String[] { "uploadtrace.png", "cancel.png" }); 126 114 setupDialog(); 127 115 128 buttons.get(0).setEnabled( !checkForGPXLayer());116 buttons.get(0).setEnabled(gpxData != null); 129 117 } 130 118 131 119 /** … … 133 121 * @return JPanel with components 134 122 */ 135 123 private JPanel initComponents() { 124 // visibilty 136 125 JLabel visibilityLabel = new JLabel(tr("Visibility")); 137 126 visibilityLabel.setToolTipText(tr("Defines the visibility of your trace for other OSM users.")); 127 128 visibilityCombo = new JComboBox(); 129 visibilityCombo.setEditable(false); 138 130 for(visibility v : visibility.values()) { 139 131 visibilityCombo.addItem(v.description); 140 132 } 133 visibilityCombo.setSelectedItem(visibility.valueOf(Main.pref.get("directupload.visibility.last-used", visibility.PRIVATE.name())).description); 141 134 UrlLabel visiUrl = new UrlLabel(tr("http://wiki.openstreetmap.org/wiki/Visibility_of_GPS_traces"), tr("(What does that mean?)")); 142 135 136 // description 143 137 JLabel descriptionLabel = new JLabel(tr("Description")); 138 descriptionField = new HistoryComboBox(); 144 139 descriptionField.setToolTipText(tr("Please enter Description about your trace.")); 140 141 List<String> descHistory = new LinkedList<String>(Main.pref.getCollection("directupload.description.history", new LinkedList<String>())); 142 // we have to reverse the history, because ComboBoxHistory will reverse it againin addElement() 143 // XXX this should be handled in HistoryComboBox 144 Collections.reverse(descHistory); 145 descriptionField.setPossibleItems(descHistory); 145 146 147 // tags 146 148 JLabel tagsLabel = new JLabel(tr("Tags (comma delimited)")); 149 tagsField = new HistoryComboBox(); 147 150 tagsField.setToolTipText(tr("Please enter tags about your trace.")); 148 151 152 List<String> tagsHistory = new LinkedList<String>(Main.pref.getCollection("directupload.tags.history", new LinkedList<String>())); 153 // we have to reverse the history, because ComboBoxHistory will reverse it againin addElement() 154 // XXX this should be handled in HistoryComboBox 155 Collections.reverse(tagsHistory); 156 tagsField.setPossibleItems(tagsHistory); 157 149 158 JPanel p = new JPanel(new GridBagLayout()); 150 159 151 160 OutputDisplay.setMaxWidth(findMaxDialogSize().width-10); … … 164 173 return p; 165 174 } 166 175 167 /** 168 * This function will automatically select a GPX layer if it's the only one. 169 * If a GPX layer is found, its filename will be parsed and displayed 170 */ 171 private void autoSelectTrace() { 172 // If no GPX layer is selected, select one for the user if there is only one GPX layer 173 if(Main.map != null && Main.map.mapView != null) { 174 MapView mv=Main.map.mapView; 175 if(!(mv.getActiveLayer() instanceof GpxLayer)) { 176 Layer lastLayer=null; 177 int layerCount=0; 178 for (Layer l : mv.getAllLayers()) { 179 if(l instanceof GpxLayer) { 180 lastLayer = l; 181 layerCount++; 182 } 183 } 184 if(layerCount == 1) mv.setActiveLayer(lastLayer); 185 } 186 187 if(mv.getActiveLayer() instanceof GpxLayer) { 188 GpxData data=((GpxLayer)Main.map.mapView.getActiveLayer()).data; 189 try { 190 filename = data.storageFile.getName() 191 .replaceAll("[&?/\\\\]"," ").replaceAll("(\\.[^.]*)$",""); 192 } catch(Exception e) { } 193 descriptionField.setText(getFilename()); 194 OutputDisplay.setText(tr("Selected track: {0}", getFilename())); 195 } 196 } 176 private void initTitleAndDescriptionFromGpxData(GpxData gpxData) { 177 String description, title; 178 try { 179 description = gpxData.storageFile.getName().replaceAll("[&?/\\\\]"," ").replaceAll("(\\.[^.]*)$",""); 180 title = tr("Selected track: {0}", gpxData.storageFile.getName()); 181 } 182 catch(Exception e) { 183 description = new SimpleDateFormat("yyMMddHHmmss").format(new Date()); 184 title = tr("No GPX layer selected. Cannot upload a trace."); 185 } 186 OutputDisplay.setText(title); 187 descriptionField.setText(description); 197 188 } 198 189 199 190 /** … … 204 195 * @param GpxData The GPX Data to upload 205 196 */ 206 197 private void upload(String description, String tags, String visi, GpxData gpxData, ProgressMonitor progressMonitor) throws IOException { 207 progressMonitor.beginTask( null);198 progressMonitor.beginTask(tr("Uploading trace ...")); 208 199 try { 209 if (checkForErrors(username, password, description, gpxData))200 if (checkForErrors(description, gpxData)) { 210 201 return; 202 } 211 203 212 204 // Clean description/tags from disallowed chars 213 description = description.replaceAll("[&?/\\\\]", " ");214 tags = tags.replaceAll("[&?/\\\\.;]", " ");205 description = description.replaceAll("[&?/\\\\]", " "); 206 tags = tags.replaceAll("[&?/\\\\.;]", " "); 215 207 216 208 // Set progress dialog to indeterminate while connecting 217 209 progressMonitor.indeterminateSubTask(tr("Connecting...")); 218 210 219 try { 220 // Generate data for upload 221 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 222 writeGpxFile(baos, "file", gpxData); 223 writeField(baos, "description", description); 224 writeField(baos, "tags", (tags != null && tags.length() > 0) ? tags : ""); 225 writeField(baos, "visibility", visi); 226 writeString(baos, "--" + BOUNDARY + "--" + LINE_END); 211 // Generate data for upload 212 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 213 writeGpxFile(baos, "file", gpxData); 214 writeField(baos, "description", description); 215 writeField(baos, "tags", (tags != null && tags.length() > 0) ? tags : ""); 216 writeField(baos, "visibility", visi); 217 writeString(baos, "--" + BOUNDARY + "--" + LINE_END); 227 218 228 229 219 ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); 220 HttpURLConnection conn = setupConnection(baos.size()); 230 221 231 232 222 progressMonitor.setTicksCount(baos.size()); 223 progressMonitor.subTask(null); 233 224 234 try { 235 flushToServer(bais, conn.getOutputStream(), progressMonitor); 236 } catch(Exception e) {} 225 flushToServer(bais, conn.getOutputStream(), progressMonitor); 237 226 238 if(cancelled) { 239 conn.disconnect(); 240 OutputDisplay.setText(tr("Upload cancelled")); 241 buttons.get(0).setEnabled(true); 242 cancelled = false; 243 } else { 244 boolean success = finishUpConnection(conn); 245 buttons.get(0).setEnabled(!success); 246 if(success) 247 buttons.get(1).setText(tr("Close")); 227 if (cancelled) { 228 conn.disconnect(); 229 OutputDisplay.setText(tr("Upload cancelled")); 230 buttons.get(0).setEnabled(true); 231 cancelled = false; 232 } 233 else { 234 boolean success = finishUpConnection(conn); 235 buttons.get(0).setEnabled(!success); 236 if (success) { 237 buttons.get(1).setText(tr("Close")); 248 238 } 249 } catch(Exception e) {250 OutputDisplay.setText(tr("Error while uploading"));251 e.printStackTrace();252 239 } 253 } finally { 240 } 241 catch (Exception e) { 242 OutputDisplay.setText(tr("Error while uploading")); 243 e.printStackTrace(); 244 } 245 finally { 254 246 progressMonitor.finishTask(); 255 247 } 256 248 } … … 262 254 * @return HttpURLConnection The set up conenction 263 255 */ 264 256 private HttpURLConnection setupConnection(int contentLength) throws Exception { 265 // Encode username and password266 CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();267 String auth = username + ":" + password;268 ByteBuffer bytes = encoder.encode(CharBuffer.wrap(auth));269 257 270 258 // Upload URL 271 259 URL url = new URL("http://www.openstreetmap.org/api/" + API_VERSION + "/gpx/create"); … … 276 264 c.setConnectTimeout(15000); 277 265 c.setRequestMethod("POST"); 278 266 c.setDoOutput(true); 279 c.addRequestProperty("Authorization", "Basic " + Base64.encode(bytes)); 267 // unfortunately, addAuth() is protected, so we need to subclass OsmConnection 268 // XXX make addAuth public. 269 UploadOsmConnection.getInstance().addAuthHack(c); 270 280 271 c.addRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY); 281 272 c.addRequestProperty("Connection", "close"); // counterpart of keep-alive 282 273 c.addRequestProperty("Expect", ""); … … 366 357 /** 367 358 * Checks for common errors and displays them in OutputDisplay if it finds any. 368 359 * Returns whether errors have been found or not. 369 * @param String OSM username370 * @param String OSM password371 360 * @param String GPX track description 372 361 * @param GpxData the GPX data to upload 373 362 * @return boolean true if errors have been found 374 363 */ 375 private boolean checkForErrors(String username, String password, 376 String description, GpxData gpxData) { 364 private boolean checkForErrors(String description, GpxData gpxData) { 377 365 String errors=""; 378 366 if(description == null || description.length() == 0) 379 367 errors += tr("No description provided. Please provide some description."); … … 381 369 if(gpxData == null) 382 370 errors += tr("No GPX layer selected. Cannot upload a trace."); 383 371 384 if(username == null || username.length() == 0)385 errors += tr("No username provided.");386 387 if(password == null || password.length() == 0)388 errors += tr("No password provided.");389 390 372 OutputDisplay.setText(errors); 391 373 return errors.length() > 0; 392 374 } 393 375 394 376 /** 395 * Checks if a GPX layer is selected and returns the result. Also writes an error396 * message to OutputDisplay if result is false.397 * @return boolean True, if /no/ GPX layer is selected398 */399 private boolean checkForGPXLayer() {400 if(Main.map == null401 || Main.map.mapView == null402 || Main.map.mapView.getActiveLayer() == null403 || !(Main.map.mapView.getActiveLayer() instanceof GpxLayer)) {404 OutputDisplay.setText(tr("No GPX layer selected. Cannot upload a trace."));405 return true;406 }407 return false;408 }409 410 411 /**412 377 * This creates the uploadTask that does the actual work and hands it to the main.worker to be executed. 413 378 */ 414 379 private void setupUpload() { 415 if(checkForGPXLayer()) return; 380 final GpxData gpxData = UploadOsmConnection.getInstance().autoSelectTrace(); 381 if (gpxData == null) { 382 return; 383 } 416 384 417 385 // Disable Upload button so users can't just upload that track again 418 386 buttons.get(0).setEnabled(false); 387 388 // save history 389 Main.pref.put("directupload.visibility.last-used", visibility.desc2visi(visibilityCombo.getSelectedItem().toString()).name()); 390 391 descriptionField.addCurrentItemToHistory(); 392 Main.pref.putCollection("directupload.description.history", descriptionField.getHistory()); 419 393 394 tagsField.addCurrentItemToHistory(); 395 Main.pref.putCollection("directupload.tags.history", tagsField.getHistory()); 396 420 397 PleaseWaitRunnable uploadTask = new PleaseWaitRunnable(tr("Uploading GPX Track")){ 421 398 @Override protected void realRun() throws IOException { 422 399 upload(descriptionField.getText(), 423 400 tagsField.getText(), 424 401 visibility.desc2visi(visibilityCombo.getSelectedItem()).toString(), 425 ((GpxLayer)Main.map.mapView.getActiveLayer()).data,402 gpxData, 426 403 progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false) 427 404 ); 428 405 } … … 459 436 private void writeGpxFile(ByteArrayOutputStream baos, String name, GpxData gpxData) throws IOException { 460 437 writeBoundary(baos); 461 438 writeString(baos, "Content-Disposition: form-data; name=\"" + name + "\"; "); 462 writeString(baos, "filename=\"" + g etFilename() + ".gpx"+ "\"");439 writeString(baos, "filename=\"" + gpxData.storageFile.getName() + "\""); 463 440 writeLineEnd(baos); 464 441 writeString(baos, "Content-Type: application/octet-stream"); 465 442 writeLineEnd(baos); … … 497 474 } 498 475 499 476 /** 500 * Returns the filename of the GPX file to be upload. If not available, returns current date501 * as an alternative502 * @param String503 */504 private String getFilename() {505 return filename.equals("") ? datename : filename;506 }507 508 /**509 477 * Overrides the default actions. Will not close the window when upload trace is clicked 510 478 */ 511 479 @Override protected void buttonAction(int buttonIndex, ActionEvent evt) { -
UploadOsmConnection.java
1 // ... 2 3 package org.openstreetmap.josm.plugins.DirectUpload; 4 5 import java.net.HttpURLConnection; 6 import java.util.List; 7 8 import org.openstreetmap.josm.Main; 9 import org.openstreetmap.josm.data.gpx.GpxData; 10 import org.openstreetmap.josm.gui.MapView; 11 import org.openstreetmap.josm.gui.dialogs.LayerListDialog; 12 import org.openstreetmap.josm.gui.layer.GpxLayer; 13 import org.openstreetmap.josm.gui.layer.Layer; 14 import org.openstreetmap.josm.io.OsmConnection; 15 import org.openstreetmap.josm.io.OsmTransferException; 16 17 /** 18 * Work-around and utility class for DirectUpload. 19 * 20 * @author ax 21 */ 22 public class UploadOsmConnection extends OsmConnection { 23 24 // singleton, see http://en.wikipedia.org/wiki/Singleton_pattern#Traditional_simple_way 25 private static final UploadOsmConnection INSTANCE = new UploadOsmConnection(); 26 27 // Private constructor prevents instantiation from other classes 28 private UploadOsmConnection() { 29 } 30 31 public static UploadOsmConnection getInstance() { 32 return UploadOsmConnection.INSTANCE; 33 } 34 35 // make protected OsmConnection::addAuth() available to others 36 public void addAuthHack(HttpURLConnection connection) throws OsmTransferException { 37 addAuth(connection); 38 } 39 40 /** 41 * find which gpx layer holds the trace to upload. layers are tried in this order: 42 * 43 * 1. selected (*not* active - think "zoom to layer"), from first to last 44 * 2. not selectd - if there is only one 45 * 3. active 46 * 47 * @return data of the selected gpx layer, or null if there is none 48 */ 49 GpxData autoSelectTrace() { 50 if (Main.map != null && Main.map.mapView != null) { 51 MapView mv = Main.map.mapView; 52 // List<Layer> allLayers = new ArrayList<Layer>(mv.getAllLayersAsList()); // modifiable 53 List<Layer> selectedLayers = LayerListDialog.getInstance().getModel().getSelectedLayers(); 54 List<GpxLayer> gpxLayersRemaining = mv.getLayersOfType(GpxLayer.class); 55 gpxLayersRemaining.removeAll(selectedLayers); 56 GpxLayer traceLayer = null; 57 // find the first gpx layer inside selected layers 58 for (Layer l : LayerListDialog.getInstance().getModel().getSelectedLayers()) { 59 if (l instanceof GpxLayer) { 60 traceLayer = (GpxLayer) l; 61 break; 62 } 63 } 64 if (traceLayer == null) { 65 // if there is none, try the none selected gpx layers. if there is only one, use it. 66 if (gpxLayersRemaining.size() == 1) { 67 traceLayer = gpxLayersRemaining.get(0); 68 } 69 // active layer 70 else if (mv.getActiveLayer() instanceof GpxLayer) { 71 traceLayer = (GpxLayer) mv.getActiveLayer(); 72 } 73 } 74 75 if (traceLayer != null) { 76 GpxData data = traceLayer.data; 77 return data; 78 } 79 } 80 81 return null; 82 } 83 }