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

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

fix #16935 - simplify/cleanup help topics of ToggleDialog/ToggleDialogAction

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