001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.io;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.GraphicsEnvironment;
007import java.util.Optional;
008
009import javax.swing.JOptionPane;
010
011import org.openstreetmap.josm.data.APIDataSet;
012import org.openstreetmap.josm.data.osm.Changeset;
013import org.openstreetmap.josm.gui.MainApplication;
014import org.openstreetmap.josm.gui.dialogs.LayerListDialog;
015import org.openstreetmap.josm.gui.layer.OsmDataLayer;
016import org.openstreetmap.josm.gui.progress.ProgressTaskId;
017import org.openstreetmap.josm.gui.util.GuiHelper;
018import org.openstreetmap.josm.io.UploadStrategySpecification;
019
020/**
021 * Task for uploading primitives using background worker threads. The actual upload is delegated to the
022 * {@link UploadPrimitivesTask}. This class is a wrapper over that to make the background upload process safe. There
023 * can only be one instance of this class, hence background uploads are limited to one at a time. This class also
024 * changes the editLayer of {@link org.openstreetmap.josm.gui.layer.MainLayerManager} to null during upload so that
025 * any changes to the uploading layer are prohibited.
026 *
027 * @author udit
028 * @since 13133
029 */
030public final class AsynchronousUploadPrimitivesTask extends UploadPrimitivesTask {
031
032    /**
033     * Static instance
034     */
035    private static AsynchronousUploadPrimitivesTask asynchronousUploadPrimitivesTask;
036
037    /**
038     * Member fields
039     */
040    private final ProgressTaskId taskId;
041    private final OsmDataLayer uploadDataLayer;
042
043    /**
044     * Private constructor to restrict creating more Asynchronous upload tasks
045     *
046     * @param uploadStrategySpecification UploadStrategySpecification for the DataLayer
047     * @param osmDataLayer Datalayer to be uploaded
048     * @param apiDataSet ApiDataSet that contains the primitives to be uploaded
049     * @param changeset Changeset for the datalayer
050     *
051     * @throws IllegalArgumentException if layer is null
052     * @throws IllegalArgumentException if toUpload is null
053     * @throws IllegalArgumentException if strategy is null
054     * @throws IllegalArgumentException if changeset is null
055     */
056    private AsynchronousUploadPrimitivesTask(UploadStrategySpecification uploadStrategySpecification,
057                                             OsmDataLayer osmDataLayer, APIDataSet apiDataSet, Changeset changeset) {
058        super(uploadStrategySpecification,
059                osmDataLayer,
060                apiDataSet,
061                changeset);
062
063        uploadDataLayer = osmDataLayer;
064        // Create a ProgressTaskId for background upload
065        taskId = new ProgressTaskId("core", "async-upload");
066    }
067
068    /**
069     * Creates an instance of AsynchronousUploadPrimitiveTask
070     *
071     * @param uploadStrategySpecification UploadStrategySpecification for the DataLayer
072     * @param dataLayer Datalayer to be uploaded
073     * @param apiDataSet ApiDataSet that contains the primitives to be uploaded
074     * @param changeset Changeset for the datalayer
075     * @return Returns an {@literal Optional<AsynchronousUploadPrimitivesTask> } if there is no
076     * background upload in progress. Otherwise returns an {@literal Optional.empty()}
077     *
078     * @throws IllegalArgumentException if layer is null
079     * @throws IllegalArgumentException if toUpload is null
080     * @throws IllegalArgumentException if strategy is null
081     * @throws IllegalArgumentException if changeset is null
082     */
083    public static Optional<AsynchronousUploadPrimitivesTask> createAsynchronousUploadTask(
084            UploadStrategySpecification uploadStrategySpecification,
085             OsmDataLayer dataLayer, APIDataSet apiDataSet, Changeset changeset) {
086        synchronized (AsynchronousUploadPrimitivesTask.class) {
087            if (asynchronousUploadPrimitivesTask != null) {
088                if (!GraphicsEnvironment.isHeadless()) {
089                    GuiHelper.runInEDTAndWait(() ->
090                            JOptionPane.showMessageDialog(MainApplication.parent,
091                                    tr("A background upload is already in progress. " +
092                                            "Kindly wait for it to finish before uploading new changes")));
093                }
094                return Optional.empty();
095            } else {
096                // Create an asynchronous upload task
097                asynchronousUploadPrimitivesTask = new AsynchronousUploadPrimitivesTask(
098                        uploadStrategySpecification,
099                        dataLayer,
100                        apiDataSet,
101                        changeset);
102                return Optional.ofNullable(asynchronousUploadPrimitivesTask);
103            }
104        }
105    }
106
107    /**
108     * Get the current upload task
109     * @return {@literal Optional<AsynchronousUploadPrimitivesTask> }
110     */
111    public static Optional<AsynchronousUploadPrimitivesTask> getCurrentAsynchronousUploadTask() {
112        return Optional.ofNullable(asynchronousUploadPrimitivesTask);
113    }
114
115    @Override
116    public ProgressTaskId canRunInBackground() {
117        return taskId;
118    }
119
120    @Override
121    protected void realRun() {
122        // Lock the data layer before upload in EDT
123        GuiHelper.runInEDTAndWait(() -> {
124            // Remove the commands from the undo stack
125            MainApplication.undoRedo.clean(uploadDataLayer.data);
126            MainApplication.getLayerManager().prepareLayerForUpload(uploadDataLayer);
127
128            // Repainting the Layer List dialog to update the icon of the active layer
129            LayerListDialog.getInstance().repaint();
130        });
131        super.realRun();
132    }
133
134    @Override
135    protected void cancel() {
136        super.cancel();
137        asynchronousUploadPrimitivesTask = null;
138    }
139
140    @Override
141    protected void finish() {
142        try {
143            // Unlock the data layer in EDT
144            GuiHelper.runInEDTAndWait(() -> {
145                MainApplication.getLayerManager().processLayerAfterUpload(uploadDataLayer);
146                LayerListDialog.getInstance().repaint();
147            });
148            super.finish();
149        } finally {
150            asynchronousUploadPrimitivesTask = null;
151        }
152    }
153}