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

Last change on this file since 13133 was 13133, checked in by bastiK, 6 years ago

applied #8509 - Background uploading (patch by udit, minor changes)

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