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.UndoRedoHandler;
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                GuiHelper.runInEDTAndWait(() ->
089                        JOptionPane.showMessageDialog(MainApplication.getMainFrame(),
090                                tr("A background upload is already in progress. " +
091                                        "Kindly wait for it to finish before uploading new changes")));
092                return Optional.empty();
093            } else {
094                // Create an asynchronous upload task
095                asynchronousUploadPrimitivesTask = new AsynchronousUploadPrimitivesTask(
096                        uploadStrategySpecification,
097                        dataLayer,
098                        apiDataSet,
099                        changeset);
100                return Optional.ofNullable(asynchronousUploadPrimitivesTask);
101            }
102        }
103    }
104
105    /**
106     * Get the current upload task
107     * @return {@literal Optional<AsynchronousUploadPrimitivesTask> }
108     */
109    public static Optional<AsynchronousUploadPrimitivesTask> getCurrentAsynchronousUploadTask() {
110        return Optional.ofNullable(asynchronousUploadPrimitivesTask);
111    }
112
113    @Override
114    public ProgressTaskId canRunInBackground() {
115        return taskId;
116    }
117
118    @Override
119    protected void realRun() {
120        // Lock the data layer before upload in EDT
121        GuiHelper.runInEDTAndWait(() -> {
122            // Remove the commands from the undo stack
123            UndoRedoHandler.getInstance().clean(uploadDataLayer.getDataSet());
124            MainApplication.getLayerManager().prepareLayerForUpload(uploadDataLayer);
125
126            // Repainting the Layer List dialog to update the icon of the active layer
127            LayerListDialog.getInstance().repaint();
128        });
129        super.realRun();
130    }
131
132    @Override
133    protected void cancel() {
134        super.cancel();
135        asynchronousUploadPrimitivesTask = null;
136    }
137
138    @Override
139    protected void finish() {
140        try {
141            // Unlock the data layer in EDT
142            GuiHelper.runInEDTAndWait(() -> {
143                MainApplication.getLayerManager().processLayerAfterUpload(uploadDataLayer);
144                LayerListDialog.getInstance().repaint();
145            });
146            super.finish();
147        } finally {
148            asynchronousUploadPrimitivesTask = null;
149        }
150    }
151}