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

Last change on this file since 13115 was 13028, checked in by Don-vip, 7 years ago

fix #15464 - Allow plugins to modify the changeset tags before upload (patch by rorym)

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