Ticket #8509: async_v4.patch

File async_v4.patch, 15.7 KB (added by udit, 6 years ago)

Patch for Asynchronous uploads in JOSM. This is a version_4.

  • src/org/openstreetmap/josm/actions/UploadAction.java

     
    99import java.util.LinkedList;
    1010import java.util.List;
    1111import java.util.Map;
     12import java.util.Optional;
    1213
    1314import javax.swing.JOptionPane;
    1415
     
    2425import org.openstreetmap.josm.data.osm.Changeset;
    2526import org.openstreetmap.josm.gui.HelpAwareOptionPane;
    2627import org.openstreetmap.josm.gui.MainApplication;
     28import org.openstreetmap.josm.gui.io.AsynchronousUploadPrimitivesTask;
    2729import org.openstreetmap.josm.gui.io.UploadDialog;
    2830import org.openstreetmap.josm.gui.io.UploadPrimitivesTask;
    2931import org.openstreetmap.josm.gui.layer.AbstractModifiableLayer;
     
    5759    private static final List<UploadHook> UPLOAD_HOOKS = new LinkedList<>();
    5860    private static final List<UploadHook> LATE_UPLOAD_HOOKS = new LinkedList<>();
    5961
     62    private static final String IS_ASYNC_UPLOAD_ENABLED = "asynchronous.upload";
     63
    6064    static {
    6165        /**
    6266         * Calls validator before upload.
     
    257261            hook.modifyChangesetTags(changesetTags);
    258262        }
    259263
    260         MainApplication.worker.execute(
    261                 new UploadPrimitivesTask(
    262                         UploadDialog.getUploadDialog().getUploadStrategySpecification(),
    263                         layer,
    264                         apiData,
    265                         cs
    266                 )
    267         );
     264        if (Main.pref.get(IS_ASYNC_UPLOAD_ENABLED).isEmpty() ||
     265                !Main.pref.get(IS_ASYNC_UPLOAD_ENABLED).equalsIgnoreCase("disabled")) {
     266            Optional <AsynchronousUploadPrimitivesTask> asyncUploadTask = AsynchronousUploadPrimitivesTask.createAsynchronousUploadTask(
     267                    UploadDialog.getUploadDialog().getUploadStrategySpecification(),
     268                    layer,
     269                    apiData,
     270                    cs);
     271
     272            if (asyncUploadTask.isPresent()) {
     273                MainApplication.worker.execute(asyncUploadTask.get());
     274            }
     275        } else {
     276            MainApplication.worker.execute(
     277                    new UploadPrimitivesTask(
     278                            UploadDialog.getUploadDialog().getUploadStrategySpecification(),
     279                            layer,
     280                            apiData,
     281                            cs));
     282        }
    268283    }
    269284
    270285    @Override
  • src/org/openstreetmap/josm/gui/io/AsynchronousUploadPrimitivesTask.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.io;
     3
     4import org.openstreetmap.josm.data.APIDataSet;
     5import org.openstreetmap.josm.data.osm.Changeset;
     6import org.openstreetmap.josm.gui.MainApplication;
     7import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
     8import org.openstreetmap.josm.gui.layer.OsmDataLayer;
     9import org.openstreetmap.josm.gui.progress.ProgressTaskId;
     10import org.openstreetmap.josm.gui.util.GuiHelper;
     11import org.openstreetmap.josm.io.UploadStrategySpecification;
     12import org.openstreetmap.josm.tools.I18n;
     13
     14import javax.swing.*;
     15import java.awt.*;
     16import java.util.Optional;
     17
     18/**
     19 * Task for uploading primitives using background worker threads
     20 * @author udit
     21 */
     22public class AsynchronousUploadPrimitivesTask extends UploadPrimitivesTask {
     23
     24    /**
     25     * Static instance
     26     */
     27    private static AsynchronousUploadPrimitivesTask asynchronousUploadPrimitivesTask = null;
     28
     29    /**
     30     * Member fields
     31     */
     32    private final ProgressTaskId taskId;
     33    private final OsmDataLayer uploadDataLayer;
     34
     35    /**
     36     * Private constructor to restrict creating more Asynchronous upload tasks
     37     *
     38     * @param uploadStrategySpecification
     39     * @param osmDataLayer
     40     * @param apiDataSet
     41     * @param changeset
     42     */
     43    private AsynchronousUploadPrimitivesTask(UploadStrategySpecification uploadStrategySpecification, OsmDataLayer osmDataLayer, APIDataSet apiDataSet, Changeset changeset) {
     44        super(uploadStrategySpecification,
     45                osmDataLayer,
     46                apiDataSet,
     47                changeset);
     48
     49        uploadDataLayer = osmDataLayer;
     50        // Create a ProgressTaskId for background upload
     51        taskId = new ProgressTaskId("core", "async-upload");
     52    }
     53
     54    /**
     55     * Creates an instance of AsynchronousUploadPrimitiveTask
     56     *
     57     * @param uploadStrategySpecification
     58     * @param dataLayer
     59     * @param apiDataSet
     60     * @param changeset
     61     * @return Optional<AsynchronousUploadPrimitivesTask>
     62     */
     63    public static Optional<AsynchronousUploadPrimitivesTask> createAsynchronousUploadTask
     64            (UploadStrategySpecification uploadStrategySpecification,
     65             OsmDataLayer dataLayer, APIDataSet apiDataSet, Changeset changeset) {
     66        synchronized (AsynchronousUploadPrimitivesTask.class) {
     67            if (asynchronousUploadPrimitivesTask != null) {
     68                if (!GraphicsEnvironment.isHeadless()) {
     69                    GuiHelper.runInEDTAndWait(() ->
     70                            JOptionPane.showMessageDialog(MainApplication.parent,
     71                                    I18n.tr("A background upload is already in progress. Kindly wait for it to finish before uploading new changes")));
     72                }
     73                return Optional.empty();
     74            } else {
     75                // Create an asynchronous upload task
     76                asynchronousUploadPrimitivesTask = new AsynchronousUploadPrimitivesTask(
     77                        uploadStrategySpecification,
     78                        dataLayer,
     79                        apiDataSet,
     80                        changeset);
     81                return Optional.ofNullable(asynchronousUploadPrimitivesTask);
     82            }
     83        }
     84    }
     85
     86    /**
     87     * Get the current upload task
     88     * @return Optional<AsynchronousUploadPrimitivesTask>
     89     */
     90    public static Optional<AsynchronousUploadPrimitivesTask> getCurrentAsynchronousUploadTask () {
     91        return Optional.ofNullable(asynchronousUploadPrimitivesTask);
     92    }
     93
     94    @Override
     95    public ProgressTaskId canRunInBackground() {
     96        return taskId;
     97    }
     98
     99    @Override
     100    protected void realRun() {
     101        // Lock the data layer before upload in EDT
     102        GuiHelper.runInEDTAndWait(() -> {
     103            // Remove the commands from the undo stack
     104            MainApplication.undoRedo.clean(uploadDataLayer.data);
     105            MainApplication.getLayerManager().prepareLayerForUpload(uploadDataLayer);
     106
     107            // Repainting the Layer List dialog to update the icon of the active layer
     108            LayerListDialog.getInstance().repaint();
     109        });
     110        super.realRun();
     111    }
     112
     113    @Override
     114    protected void cancel() {
     115        super.cancel();
     116        asynchronousUploadPrimitivesTask = null;
     117    }
     118
     119    @Override
     120    protected void finish() {
     121        try {
     122            // Unlock the data layer in EDT
     123            GuiHelper.runInEDTAndWait(() -> {
     124                MainApplication.getLayerManager().processLayerAfterUpload(uploadDataLayer);
     125                LayerListDialog.getInstance().repaint();
     126            });
     127            super.finish();
     128        } finally {
     129            asynchronousUploadPrimitivesTask = null;
     130        }
     131    }
     132}
     133 No newline at end of file
  • src/org/openstreetmap/josm/gui/layer/MainLayerManager.java

     
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.gui.layer;
    33
     4import org.openstreetmap.josm.data.osm.DataSet;
     5import org.openstreetmap.josm.gui.MainApplication;
     6import org.openstreetmap.josm.gui.util.GuiHelper;
     7
     8import javax.swing.*;
    49import java.util.ArrayList;
    510import java.util.Collection;
    611import java.util.List;
     
    712import java.util.ListIterator;
    813import java.util.concurrent.CopyOnWriteArrayList;
    914
    10 import org.openstreetmap.josm.data.osm.DataSet;
    11 import org.openstreetmap.josm.gui.util.GuiHelper;
     15import static org.openstreetmap.josm.tools.I18n.tr;
    1216
    1317/**
    1418 * This class extends the layer manager by adding an active and an edit layer.
     
    205209    }
    206210
    207211    /**
    208      * Set the active layer. If the layer is an OsmDataLayer, the edit layer is also changed.
     212     * Set the active layer iff the layer is not read only.
     213     * If the layer is an OsmDataLayer, the edit layer is also changed.
    209214     * @param layer The active layer.
    210215     */
    211216    public void setActiveLayer(final Layer layer) {
    212217        // we force this on to the EDT Thread to make events fire from there.
    213218        // The synchronization lock needs to be held by the EDT.
    214         GuiHelper.runInEDTAndWaitWithException(() -> realSetActiveLayer(layer));
     219        if (layer instanceof OsmDataLayer && ((OsmDataLayer) layer).isReadOnly()) {
     220            GuiHelper.runInEDT(() ->
     221                    JOptionPane.showMessageDialog(
     222                            MainApplication.parent,
     223                            tr("Trying to set a read only data layer as edit layer"),
     224                            tr("Warning"),
     225                            JOptionPane.WARNING_MESSAGE));
     226        } else {
     227            GuiHelper.runInEDTAndWaitWithException(() -> realSetActiveLayer(layer));
     228        }
    215229    }
    216230
    217231    protected synchronized void realSetActiveLayer(final Layer layer) {
     
    309323     * @return the currently active layer (may be null)
    310324     */
    311325    public synchronized Layer getActiveLayer() {
    312         return activeLayer;
     326        if (activeLayer instanceof OsmDataLayer) {
     327            if (!((OsmDataLayer)activeLayer).isReadOnly()) {
     328                return activeLayer;
     329            } else {
     330                return  null;
     331            }
     332        } else {
     333            return activeLayer;
     334        }
    313335    }
    314336
    315337    /**
     
    318340     * @return the current edit layer. May be null.
    319341     */
    320342    public synchronized OsmDataLayer getEditLayer() {
    321         return editLayer;
     343        if (editLayer != null && !editLayer.isReadOnly())
     344            return editLayer;
     345        else
     346            return null;
    322347    }
    323348
    324349    /**
     
    378403        activeLayerChangeListeners.clear();
    379404        layerAvailabilityListeners.clear();
    380405    }
     406
     407    public void prepareLayerForUpload (OsmDataLayer layer) {
     408
     409        GuiHelper.assertCallFromEdt();
     410        layer.setReadOnly();
     411
     412        // Reset only the edit layer as empty
     413        if (editLayer == layer) {
     414            ActiveLayerChangeEvent layerChangeEvent = new ActiveLayerChangeEvent(this, editLayer, activeLayer);
     415            editLayer = null;
     416            fireActiveLayerChange(layerChangeEvent);
     417        }
     418    }
     419
     420    public void processLayerAfterUpload (OsmDataLayer layer) {
     421        GuiHelper.assertCallFromEdt();
     422        layer.unsetReadOnly();
     423
     424        // Set the layer as edit layer
     425        // iff the edit layer is empty.
     426        if (editLayer == null) {
     427            ActiveLayerChangeEvent layerChangeEvent = new ActiveLayerChangeEvent(this, editLayer, activeLayer);
     428            editLayer = layer;
     429            fireActiveLayerChange(layerChangeEvent);
     430        }
     431    }
    381432}
  • src/org/openstreetmap/josm/gui/layer/OsmDataLayer.java

     
    3232import java.util.Set;
    3333import java.util.concurrent.CopyOnWriteArrayList;
    3434import java.util.concurrent.atomic.AtomicInteger;
     35import java.util.concurrent.locks.ReentrantReadWriteLock;
    3536import java.util.regex.Pattern;
    3637
    3738import javax.swing.AbstractAction;
     
    129130
    130131    private boolean requiresSaveToFile;
    131132    private boolean requiresUploadToServer;
     133    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    132134
    133135    /**
    134136     * List of validation errors in this layer.
     
    420422        if (isUploadDiscouraged() || data.getUploadPolicy() == UploadPolicy.BLOCKED) {
    421423            base.addOverlay(new ImageOverlay(new ImageProvider("warning-small"), 0.5, 0.5, 1.0, 1.0));
    422424        }
     425
     426        if (isReadOnly()) {
     427            // If the layer is read only then change the default icon to a clock
     428            base = new ImageProvider("clock").setMaxSize(ImageSizes.LAYER);
     429        }
    423430        return base.get();
    424431    }
    425432
     
    11421149        }
    11431150        super.setName(name);
    11441151    }
     1152
     1153    public void setReadOnly() {
     1154        lock.writeLock().lock();
     1155    }
     1156
     1157    public void unsetReadOnly() {
     1158        lock.writeLock().unlock();
     1159    }
     1160
     1161    public boolean isReadOnly() {
     1162        return lock.isWriteLocked();
     1163    }
    11451164}
  • test/unit/org/openstreetmap/josm/gui/io/AsynchronousUploadPrimitivesTaskTest.java

     
     1// License: GPL. For details, see LICENSE file.
     2package org.openstreetmap.josm.gui.io;
     3
     4import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
     5import org.junit.*;
     6import org.openstreetmap.josm.data.APIDataSet;
     7import org.openstreetmap.josm.data.coor.LatLon;
     8import org.openstreetmap.josm.data.osm.Changeset;
     9import org.openstreetmap.josm.data.osm.DataSet;
     10import org.openstreetmap.josm.data.osm.Node;
     11import org.openstreetmap.josm.data.osm.Way;
     12import org.openstreetmap.josm.gui.layer.OsmDataLayer;
     13import org.openstreetmap.josm.io.UploadStrategySpecification;
     14import org.openstreetmap.josm.testutils.JOSMTestRules;
     15
     16import java.util.Optional;
     17
     18public class AsynchronousUploadPrimitivesTaskTest {
     19
     20    private UploadStrategySpecification strategy;
     21    private OsmDataLayer layer;
     22    private APIDataSet toUpload;
     23    private Changeset changeset;
     24    private AsynchronousUploadPrimitivesTask uploadPrimitivesTask;
     25
     26    /**
     27     * Setup tests
     28     */
     29    @Rule
     30    @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD")
     31    public JOSMTestRules test = new JOSMTestRules();
     32
     33    @Before
     34    public void bootStrap() {
     35        DataSet dataSet = new DataSet();
     36        Node node1 = new Node();
     37        Node node2 = new Node();
     38        node1.setCoor(new LatLon(0, 0));
     39        node2.setCoor(new LatLon(30, 30));
     40        Way way = new Way();
     41        way.addNode(node1);
     42        way.addNode(node2);
     43        dataSet.addPrimitive(node1);
     44        dataSet.addPrimitive(node2);
     45        dataSet.addPrimitive(way);
     46
     47        toUpload = new APIDataSet(dataSet);
     48        layer = new OsmDataLayer(dataSet, "uploadTest", null);
     49        strategy = new UploadStrategySpecification();
     50        changeset = new Changeset();
     51        uploadPrimitivesTask = AsynchronousUploadPrimitivesTask.createAsynchronousUploadTask(strategy, layer, toUpload, changeset).get();
     52    }
     53
     54    @After
     55    public void tearDown () {
     56        toUpload = null;
     57        layer = null;
     58        strategy = null;
     59        changeset = null;
     60        uploadPrimitivesTask = null;
     61    }
     62
     63    @Test
     64    public void testSingleUploadInstance () {
     65        Optional<AsynchronousUploadPrimitivesTask> task = AsynchronousUploadPrimitivesTask.createAsynchronousUploadTask(strategy, layer, toUpload, changeset);
     66        Assert.assertNotNull(uploadPrimitivesTask);
     67        Assert.assertFalse(task.isPresent());
     68    }
     69}