source: josm/trunk/src/org/openstreetmap/josm/actions/UploadAction.java@ 15652

Last change on this file since 15652 was 15513, checked in by Don-vip, 5 years ago

fix #18296 - make sure "Upload selection" is always enabled if required after data change event

  • Property svn:eol-style set to native
File size: 11.3 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.actions;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.tr;
6
7import java.awt.event.ActionEvent;
8import java.awt.event.KeyEvent;
9import java.util.LinkedList;
10import java.util.List;
11import java.util.Map;
12import java.util.Optional;
13
14import javax.swing.JOptionPane;
15
16import org.openstreetmap.josm.actions.upload.ApiPreconditionCheckerHook;
17import org.openstreetmap.josm.actions.upload.DiscardTagsHook;
18import org.openstreetmap.josm.actions.upload.FixDataHook;
19import org.openstreetmap.josm.actions.upload.RelationUploadOrderHook;
20import org.openstreetmap.josm.actions.upload.UploadHook;
21import org.openstreetmap.josm.actions.upload.ValidateUploadHook;
22import org.openstreetmap.josm.data.APIDataSet;
23import org.openstreetmap.josm.data.conflict.ConflictCollection;
24import org.openstreetmap.josm.data.osm.Changeset;
25import org.openstreetmap.josm.gui.HelpAwareOptionPane;
26import org.openstreetmap.josm.gui.MainApplication;
27import org.openstreetmap.josm.gui.Notification;
28import org.openstreetmap.josm.gui.io.AsynchronousUploadPrimitivesTask;
29import org.openstreetmap.josm.gui.io.UploadDialog;
30import org.openstreetmap.josm.gui.io.UploadPrimitivesTask;
31import org.openstreetmap.josm.gui.layer.AbstractModifiableLayer;
32import org.openstreetmap.josm.gui.layer.OsmDataLayer;
33import org.openstreetmap.josm.gui.util.GuiHelper;
34import org.openstreetmap.josm.io.ChangesetUpdater;
35import org.openstreetmap.josm.io.UploadStrategySpecification;
36import org.openstreetmap.josm.spi.preferences.Config;
37import org.openstreetmap.josm.tools.ImageProvider;
38import org.openstreetmap.josm.tools.Shortcut;
39import org.openstreetmap.josm.tools.Utils;
40
41/**
42 * Action that opens a connection to the osm server and uploads all changes.
43 *
44 * A dialog is displayed asking the user to specify a rectangle to grab.
45 * The url and account settings from the preferences are used.
46 *
47 * If the upload fails this action offers various options to resolve conflicts.
48 *
49 * @author imi
50 */
51public class UploadAction extends AbstractUploadAction {
52 /**
53 * The list of upload hooks. These hooks will be called one after the other
54 * when the user wants to upload data. Plugins can insert their own hooks here
55 * if they want to be able to veto an upload.
56 *
57 * Be default, the standard upload dialog is the only element in the list.
58 * Plugins should normally insert their code before that, so that the upload
59 * dialog is the last thing shown before upload really starts; on occasion
60 * however, a plugin might also want to insert something after that.
61 */
62 private static final List<UploadHook> UPLOAD_HOOKS = new LinkedList<>();
63 private static final List<UploadHook> LATE_UPLOAD_HOOKS = new LinkedList<>();
64
65 private static final String IS_ASYNC_UPLOAD_ENABLED = "asynchronous.upload";
66
67 static {
68 /**
69 * Calls validator before upload.
70 */
71 UPLOAD_HOOKS.add(new ValidateUploadHook());
72
73 /**
74 * Fixes database errors
75 */
76 UPLOAD_HOOKS.add(new FixDataHook());
77
78 /**
79 * Checks server capabilities before upload.
80 */
81 UPLOAD_HOOKS.add(new ApiPreconditionCheckerHook());
82
83 /**
84 * Adjusts the upload order of new relations
85 */
86 UPLOAD_HOOKS.add(new RelationUploadOrderHook());
87
88 /**
89 * Removes discardable tags like created_by on modified objects
90 */
91 LATE_UPLOAD_HOOKS.add(new DiscardTagsHook());
92 }
93
94 /**
95 * Registers an upload hook. Adds the hook at the first position of the upload hooks.
96 *
97 * @param hook the upload hook. Ignored if null.
98 */
99 public static void registerUploadHook(UploadHook hook) {
100 registerUploadHook(hook, false);
101 }
102
103 /**
104 * Registers an upload hook. Adds the hook at the first position of the upload hooks.
105 *
106 * @param hook the upload hook. Ignored if null.
107 * @param late true, if the hook should be executed after the upload dialog
108 * has been confirmed. Late upload hooks should in general succeed and not
109 * abort the upload.
110 */
111 public static void registerUploadHook(UploadHook hook, boolean late) {
112 if (hook == null) return;
113 if (late) {
114 if (!LATE_UPLOAD_HOOKS.contains(hook)) {
115 LATE_UPLOAD_HOOKS.add(0, hook);
116 }
117 } else {
118 if (!UPLOAD_HOOKS.contains(hook)) {
119 UPLOAD_HOOKS.add(0, hook);
120 }
121 }
122 }
123
124 /**
125 * Unregisters an upload hook. Removes the hook from the list of upload hooks.
126 *
127 * @param hook the upload hook. Ignored if null.
128 */
129 public static void unregisterUploadHook(UploadHook hook) {
130 if (hook == null) return;
131 if (UPLOAD_HOOKS.contains(hook)) {
132 UPLOAD_HOOKS.remove(hook);
133 }
134 if (LATE_UPLOAD_HOOKS.contains(hook)) {
135 LATE_UPLOAD_HOOKS.remove(hook);
136 }
137 }
138
139 /**
140 * Constructs a new {@code UploadAction}.
141 */
142 public UploadAction() {
143 super(tr("Upload data..."), "upload", tr("Upload all changes in the active data layer to the OSM server"),
144 Shortcut.registerShortcut("file:upload", tr("File: {0}", tr("Upload data")), KeyEvent.VK_UP, Shortcut.CTRL_SHIFT), true);
145 setHelpId(ht("/Action/Upload"));
146 }
147
148 @Override
149 protected void updateEnabledState() {
150 OsmDataLayer editLayer = getLayerManager().getEditLayer();
151 setEnabled(editLayer != null && editLayer.requiresUploadToServer());
152 }
153
154 /**
155 * Check whether the preconditions are met to upload data from a given layer, if applicable.
156 * @param layer layer to check
157 * @return {@code true} if the preconditions are met, or not applicable
158 * @see #checkPreUploadConditions(AbstractModifiableLayer, APIDataSet)
159 */
160 public static boolean checkPreUploadConditions(AbstractModifiableLayer layer) {
161 return checkPreUploadConditions(layer,
162 layer instanceof OsmDataLayer ? new APIDataSet(((OsmDataLayer) layer).getDataSet()) : null);
163 }
164
165 protected static void alertUnresolvedConflicts(OsmDataLayer layer) {
166 HelpAwareOptionPane.showOptionDialog(
167 MainApplication.getMainFrame(),
168 tr("<html>The data to be uploaded participates in unresolved conflicts of layer ''{0}''.<br>"
169 + "You have to resolve them first.</html>", Utils.escapeReservedCharactersHTML(layer.getName())
170 ),
171 tr("Warning"),
172 JOptionPane.WARNING_MESSAGE,
173 ht("/Action/Upload#PrimitivesParticipateInConflicts")
174 );
175 }
176
177 /**
178 * Warn user about discouraged upload, propose to cancel operation.
179 * @param layer incriminated layer
180 * @return true if the user wants to cancel, false if they want to continue
181 */
182 public static boolean warnUploadDiscouraged(AbstractModifiableLayer layer) {
183 return GuiHelper.warnUser(tr("Upload discouraged"),
184 "<html>" +
185 tr("You are about to upload data from the layer ''{0}''.<br /><br />"+
186 "Sending data from this layer is <b>strongly discouraged</b>. If you continue,<br />"+
187 "it may require you subsequently have to revert your changes, or force other contributors to.<br /><br />"+
188 "Are you sure you want to continue?", Utils.escapeReservedCharactersHTML(layer.getName()))+
189 "</html>",
190 ImageProvider.get("upload"), tr("Ignore this hint and upload anyway"));
191 }
192
193 /**
194 * Check whether the preconditions are met to upload data in <code>apiData</code>.
195 * Makes sure upload is allowed, primitives in <code>apiData</code> don't participate in conflicts and
196 * runs the installed {@link UploadHook}s.
197 *
198 * @param layer the source layer of the data to be uploaded
199 * @param apiData the data to be uploaded
200 * @return true, if the preconditions are met; false, otherwise
201 */
202 public static boolean checkPreUploadConditions(AbstractModifiableLayer layer, APIDataSet apiData) {
203 if (layer.isUploadDiscouraged() && warnUploadDiscouraged(layer)) {
204 return false;
205 }
206 if (layer instanceof OsmDataLayer) {
207 OsmDataLayer osmLayer = (OsmDataLayer) layer;
208 ConflictCollection conflicts = osmLayer.getConflicts();
209 if (apiData.participatesInConflict(conflicts)) {
210 alertUnresolvedConflicts(osmLayer);
211 return false;
212 }
213 }
214 // Call all upload hooks in sequence.
215 // FIXME: this should become an asynchronous task
216 //
217 if (apiData != null) {
218 for (UploadHook hook : UPLOAD_HOOKS) {
219 if (!hook.checkUpload(apiData))
220 return false;
221 }
222 }
223
224 return true;
225 }
226
227 /**
228 * Uploads data to the OSM API.
229 *
230 * @param layer the source layer for the data to upload
231 * @param apiData the primitives to be added, updated, or deleted
232 */
233 public void uploadData(final OsmDataLayer layer, APIDataSet apiData) {
234 if (apiData.isEmpty()) {
235 new Notification(tr("No changes to upload.")).show();
236 return;
237 }
238 if (!checkPreUploadConditions(layer, apiData))
239 return;
240
241 ChangesetUpdater.check();
242
243 final UploadDialog dialog = UploadDialog.getUploadDialog();
244 dialog.setChangesetTags(layer.getDataSet());
245 dialog.setUploadedPrimitives(apiData);
246 dialog.setVisible(true);
247 dialog.rememberUserInput();
248 if (dialog.isCanceled()) {
249 dialog.clean();
250 return;
251 }
252
253 for (UploadHook hook : LATE_UPLOAD_HOOKS) {
254 if (!hook.checkUpload(apiData)) {
255 dialog.clean();
256 return;
257 }
258 }
259
260 // Any hooks want to change the changeset tags?
261 Changeset cs = dialog.getChangeset();
262 Map<String, String> changesetTags = cs.getKeys();
263 for (UploadHook hook : UPLOAD_HOOKS) {
264 hook.modifyChangesetTags(changesetTags);
265 }
266 for (UploadHook hook : LATE_UPLOAD_HOOKS) {
267 hook.modifyChangesetTags(changesetTags);
268 }
269
270 UploadStrategySpecification uploadStrategySpecification = dialog.getUploadStrategySpecification();
271 dialog.clean();
272
273 if (Config.getPref().getBoolean(IS_ASYNC_UPLOAD_ENABLED, true)) {
274 Optional<AsynchronousUploadPrimitivesTask> asyncUploadTask = AsynchronousUploadPrimitivesTask.createAsynchronousUploadTask(
275 uploadStrategySpecification, layer, apiData, cs);
276
277 if (asyncUploadTask.isPresent()) {
278 MainApplication.worker.execute(asyncUploadTask.get());
279 }
280 } else {
281 MainApplication.worker.execute(new UploadPrimitivesTask(uploadStrategySpecification, layer, apiData, cs));
282 }
283 }
284
285 @Override
286 public void actionPerformed(ActionEvent e) {
287 if (!isEnabled())
288 return;
289 if (MainApplication.getMap() == null) {
290 new Notification(tr("Nothing to upload. Get some data first.")).show();
291 return;
292 }
293 APIDataSet apiData = new APIDataSet(getLayerManager().getEditDataSet());
294 uploadData(getLayerManager().getEditLayer(), apiData);
295 }
296}
Note: See TracBrowser for help on using the repository browser.