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