Ticket #4498: DirectUpload.patch

File DirectUpload.patch, 23.5 KB (added by ax, 11 years ago)
  • UploadDataGuiPlugin.java

     
    1010
    1111import java.awt.event.ActionEvent;
    1212import java.awt.event.KeyEvent;
    13 import java.util.List;
    1413
    1514import org.openstreetmap.josm.Main;
    1615import org.openstreetmap.josm.actions.JosmAction;
    17 import org.openstreetmap.josm.gui.layer.GpxLayer;
    1816import org.openstreetmap.josm.plugins.Plugin;
    1917import org.openstreetmap.josm.plugins.PluginInformation;
    2018import org.openstreetmap.josm.tools.Shortcut;
     19
    2120/**
    2221 *
    23  * @author subhodip
     22 * @author subhodip, ax
    2423 */
    25 public class UploadDataGuiPlugin extends Plugin{
     24public class UploadDataGuiPlugin extends Plugin {
     25   
    2626    UploadAction openaction;
    2727
    2828    public UploadDataGuiPlugin(PluginInformation info) {
     
    3131        Main.main.menu.toolsMenu.add(openaction);
    3232    }
    3333
    34     class UploadAction extends JosmAction{
     34    class UploadAction extends JosmAction {
     35       
    3536        public UploadAction(){
    3637            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);
    3940        }
     41       
    4042        public void actionPerformed(ActionEvent e) {
    4143            UploadDataGui go = new UploadDataGui();
    4244            go.setVisible(true);
    4345        }
    4446
    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//        }
    6459    }
    65 }
    66  No newline at end of file
     60}
  • UploadDataGui.java

     
    1919import java.io.OutputStream;
    2020import java.net.HttpURLConnection;
    2121import 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;
    2622import java.text.DecimalFormat;
    2723import java.text.SimpleDateFormat;
     24import java.util.Collections;
    2825import java.util.Date;
     26import java.util.LinkedList;
     27import java.util.List;
    2928
    3029import javax.swing.JComboBox;
    3130import javax.swing.JLabel;
    3231import javax.swing.JPanel;
    33 import javax.swing.JTextField;
    3432
    3533import org.openstreetmap.josm.Main;
    3634import org.openstreetmap.josm.data.gpx.GpxData;
    3735import org.openstreetmap.josm.gui.ExtendedDialog;
    3836import org.openstreetmap.josm.gui.JMultilineLabel;
    39 import org.openstreetmap.josm.gui.MapView;
    4037import org.openstreetmap.josm.gui.PleaseWaitRunnable;
    41 import org.openstreetmap.josm.gui.layer.GpxLayer;
    42 import org.openstreetmap.josm.gui.layer.Layer;
    4338import org.openstreetmap.josm.gui.progress.ProgressMonitor;
     39import org.openstreetmap.josm.gui.widgets.HistoryComboBox;
    4440import org.openstreetmap.josm.io.GpxWriter;
    45 import org.openstreetmap.josm.tools.Base64;
    4641import org.openstreetmap.josm.tools.GBC;
    4742import org.openstreetmap.josm.tools.UrlLabel;
    4843
    4944/**
    5045 *
    51  * @author  subhodip, xeen
     46 * @author  subhodip, xeen, ax
    5247 */
    5348public class UploadDataGui extends ExtendedDialog {
     49   
    5450    /**
    5551     * This enum contains the possible values for the visibility field and their
    5652     * explanation. Provides some methods for easier handling.
     
    8682        }
    8783    }
    8884
    89 
    90     // User for log in when uploading trace
    91     private String username = Main.pref.get("osm-server.username");
    92     private String password = Main.pref.get("osm-server.password");
    93 
    9485    // Fields are declared here for easy access
    9586    // Do not remove the space in JMultilineLabel. Otherwise the label will be empty
    9687    // as we don't know its contents yet and therefore have a height of 0. This will
    9788    // lead to unnecessary scrollbars.
    9889    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;
    10293
    10394    // Constants used when generating upload request
    10495    private static final String API_VERSION = "0.6";
     
    10697    private static final String LINE_END = "\r\n";
    10798    private static final String uploadTraceText = tr("Upload Trace");
    10899
    109     // Filename and current date. Date will be used as fallback if filename not available
    110     private String datename = new SimpleDateFormat("yyMMddHHmmss").format(new Date());
    111     private String filename = "";
    112 
    113100    private boolean cancelled = false;
    114 
     101   
    115102    public UploadDataGui() {
    116103        // Initalizes ExtendedDialog
    117104        super(Main.parent,
     
    120107                true
    121108        );
    122109        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 
    124112        setContent(content);
    125113        setButtonIcons(new String[] { "uploadtrace.png", "cancel.png" });
    126114        setupDialog();
    127115
    128         buttons.get(0).setEnabled(!checkForGPXLayer());
     116        buttons.get(0).setEnabled(gpxData != null);
    129117    }
    130118
    131119    /**
     
    133121     * @return JPanel with components
    134122     */
    135123    private JPanel initComponents() {
     124        // visibilty
    136125        JLabel visibilityLabel = new JLabel(tr("Visibility"));
    137126        visibilityLabel.setToolTipText(tr("Defines the visibility of your trace for other OSM users."));
     127       
     128        visibilityCombo = new JComboBox();
     129        visibilityCombo.setEditable(false);
    138130        for(visibility v : visibility.values()) {
    139131            visibilityCombo.addItem(v.description);
    140132        }
     133        visibilityCombo.setSelectedItem(visibility.valueOf(Main.pref.get("directupload.visibility.last-used", visibility.PRIVATE.name())).description);
    141134        UrlLabel visiUrl = new UrlLabel(tr("http://wiki.openstreetmap.org/wiki/Visibility_of_GPS_traces"), tr("(What does that mean?)"));
    142135
     136        // description
    143137        JLabel descriptionLabel = new JLabel(tr("Description"));
     138        descriptionField = new HistoryComboBox();
    144139        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);
    145146
     147        // tags
    146148        JLabel tagsLabel = new JLabel(tr("Tags (comma delimited)"));
     149        tagsField = new HistoryComboBox();
    147150        tagsField.setToolTipText(tr("Please enter tags about your trace."));
    148151
     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
    149158        JPanel p = new JPanel(new GridBagLayout());
    150159
    151160        OutputDisplay.setMaxWidth(findMaxDialogSize().width-10);
     
    164173        return p;
    165174    }
    166175
    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);
    197188    }
    198189
    199190    /**
     
    204195     * @param GpxData The GPX Data to upload
    205196     */
    206197    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 ..."));
    208199        try {
    209             if(checkForErrors(username, password, description, gpxData))
     200            if (checkForErrors(description, gpxData)) {
    210201                return;
     202            }
    211203
    212204            // Clean description/tags from disallowed chars
    213             description = description.replaceAll("[&?/\\\\]"," ");
    214             tags = tags.replaceAll("[&?/\\\\.;]"," ");
     205            description = description.replaceAll("[&?/\\\\]", " ");
     206            tags = tags.replaceAll("[&?/\\\\.;]", " ");
    215207
    216208            // Set progress dialog to indeterminate while connecting
    217209            progressMonitor.indeterminateSubTask(tr("Connecting..."));
    218210
    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);
    227218
    228                 ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
    229                 HttpURLConnection conn = setupConnection(baos.size());
     219            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
     220            HttpURLConnection conn = setupConnection(baos.size());
    230221
    231                 progressMonitor.setTicksCount(baos.size());
    232                 progressMonitor.subTask(null);
     222            progressMonitor.setTicksCount(baos.size());
     223            progressMonitor.subTask(null);
    233224
    234                 try {
    235                     flushToServer(bais, conn.getOutputStream(), progressMonitor);
    236                 } catch(Exception e) {}
     225            flushToServer(bais, conn.getOutputStream(), progressMonitor);
    237226
    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"));
    248238                }
    249             } catch(Exception e) {
    250                 OutputDisplay.setText(tr("Error while uploading"));
    251                 e.printStackTrace();
    252239            }
    253         } finally {
     240        }
     241        catch (Exception e) {
     242            OutputDisplay.setText(tr("Error while uploading"));
     243            e.printStackTrace();
     244        }
     245        finally {
    254246            progressMonitor.finishTask();
    255247        }
    256248    }
     
    262254     * @return HttpURLConnection The set up conenction
    263255     */
    264256    private HttpURLConnection setupConnection(int contentLength) throws Exception {
    265         // Encode username and password
    266         CharsetEncoder encoder = Charset.forName("UTF-8").newEncoder();
    267         String auth = username + ":" + password;
    268         ByteBuffer bytes = encoder.encode(CharBuffer.wrap(auth));
    269257
    270258        // Upload URL
    271259        URL url = new URL("http://www.openstreetmap.org/api/" + API_VERSION + "/gpx/create");
     
    276264        c.setConnectTimeout(15000);
    277265        c.setRequestMethod("POST");
    278266        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
    280271        c.addRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);
    281272        c.addRequestProperty("Connection", "close"); // counterpart of keep-alive
    282273        c.addRequestProperty("Expect", "");
     
    366357    /**
    367358     * Checks for common errors and displays them in OutputDisplay if it finds any.
    368359     * Returns whether errors have been found or not.
    369      * @param String OSM username
    370      * @param String OSM password
    371360     * @param String GPX track description
    372361     * @param GpxData the GPX data to upload
    373362     * @return boolean true if errors have been found
    374363     */
    375     private boolean checkForErrors(String username, String password,
    376                                    String description, GpxData gpxData) {
     364    private boolean checkForErrors(String description, GpxData gpxData) {
    377365        String errors="";
    378366        if(description == null || description.length() == 0)
    379367            errors += tr("No description provided. Please provide some description.");
     
    381369        if(gpxData == null)
    382370            errors += tr("No GPX layer selected. Cannot upload a trace.");
    383371
    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 
    390372        OutputDisplay.setText(errors);
    391373        return errors.length() > 0;
    392374    }
    393375
    394376    /**
    395      * Checks if a GPX layer is selected and returns the result. Also writes an error
    396      * message to OutputDisplay if result is false.
    397      * @return boolean True, if /no/ GPX layer is selected
    398      */
    399     private boolean checkForGPXLayer() {
    400         if(Main.map == null
    401                 || Main.map.mapView == null
    402                 || Main.map.mapView.getActiveLayer() == null
    403                 || !(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     /**
    412377     * This creates the uploadTask that does the actual work and hands it to the main.worker to be executed.
    413378     */
    414379    private void setupUpload() {
    415         if(checkForGPXLayer()) return;
     380        final GpxData gpxData = UploadOsmConnection.getInstance().autoSelectTrace();
     381        if (gpxData == null) {
     382            return;
     383        }
    416384
    417385        // Disable Upload button so users can't just upload that track again
    418386        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());
    419393
     394        tagsField.addCurrentItemToHistory();
     395        Main.pref.putCollection("directupload.tags.history", tagsField.getHistory());
     396
    420397        PleaseWaitRunnable uploadTask = new PleaseWaitRunnable(tr("Uploading GPX Track")){
    421398            @Override protected void realRun() throws IOException {
    422399                  upload(descriptionField.getText(),
    423400                         tagsField.getText(),
    424401                         visibility.desc2visi(visibilityCombo.getSelectedItem()).toString(),
    425                          ((GpxLayer)Main.map.mapView.getActiveLayer()).data,
     402                         gpxData,
    426403                         progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false)
    427404                  );
    428405            }
     
    459436    private void writeGpxFile(ByteArrayOutputStream baos, String name, GpxData gpxData) throws IOException {
    460437        writeBoundary(baos);
    461438        writeString(baos, "Content-Disposition: form-data; name=\"" + name + "\"; ");
    462         writeString(baos, "filename=\"" + getFilename() + ".gpx" + "\"");
     439        writeString(baos, "filename=\"" + gpxData.storageFile.getName() + "\"");
    463440        writeLineEnd(baos);
    464441        writeString(baos, "Content-Type: application/octet-stream");
    465442        writeLineEnd(baos);
     
    497474    }
    498475
    499476    /**
    500      * Returns the filename of the GPX file to be upload. If not available, returns current date
    501      * as an alternative
    502      * @param String
    503      */
    504     private String getFilename() {
    505        return filename.equals("") ? datename : filename;
    506     }
    507 
    508     /**
    509477     * Overrides the default actions. Will not close the window when upload trace is clicked
    510478     */
    511479    @Override protected void buttonAction(int buttonIndex, ActionEvent evt) {
  • UploadOsmConnection.java

     
     1// ...
     2
     3package org.openstreetmap.josm.plugins.DirectUpload;
     4
     5import java.net.HttpURLConnection;
     6import java.util.List;
     7
     8import org.openstreetmap.josm.Main;
     9import org.openstreetmap.josm.data.gpx.GpxData;
     10import org.openstreetmap.josm.gui.MapView;
     11import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
     12import org.openstreetmap.josm.gui.layer.GpxLayer;
     13import org.openstreetmap.josm.gui.layer.Layer;
     14import org.openstreetmap.josm.io.OsmConnection;
     15import org.openstreetmap.josm.io.OsmTransferException;
     16
     17/**
     18 * Work-around and utility class for DirectUpload.
     19 *
     20 * @author ax
     21 */
     22public 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}