source: josm/trunk/src/org/openstreetmap/josm/gui/io/UploadPrimitivesTask.java@ 12452

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

fix #9799 - Don't check for unread messages while upload is in progress, as upload process is then broken

  • Property svn:eol-style set to native
File size: 16.5 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.io;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.CheckParameterUtil.ensureParameterNotNull;
6import static org.openstreetmap.josm.tools.I18n.tr;
7import static org.openstreetmap.josm.tools.I18n.trn;
8
9import java.lang.reflect.InvocationTargetException;
10import java.util.HashSet;
11import java.util.Set;
12
13import javax.swing.JOptionPane;
14import javax.swing.SwingUtilities;
15
16import org.openstreetmap.josm.Main;
17import org.openstreetmap.josm.data.APIDataSet;
18import org.openstreetmap.josm.data.osm.Changeset;
19import org.openstreetmap.josm.data.osm.ChangesetCache;
20import org.openstreetmap.josm.data.osm.IPrimitive;
21import org.openstreetmap.josm.data.osm.Node;
22import org.openstreetmap.josm.data.osm.OsmPrimitive;
23import org.openstreetmap.josm.data.osm.Relation;
24import org.openstreetmap.josm.data.osm.Way;
25import org.openstreetmap.josm.gui.DefaultNameFormatter;
26import org.openstreetmap.josm.gui.HelpAwareOptionPane;
27import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec;
28import org.openstreetmap.josm.gui.Notification;
29import org.openstreetmap.josm.gui.layer.OsmDataLayer;
30import org.openstreetmap.josm.gui.progress.ProgressMonitor;
31import org.openstreetmap.josm.gui.util.GuiHelper;
32import org.openstreetmap.josm.io.ChangesetClosedException;
33import org.openstreetmap.josm.io.MessageNotifier;
34import org.openstreetmap.josm.io.OsmApi;
35import org.openstreetmap.josm.io.OsmApiPrimitiveGoneException;
36import org.openstreetmap.josm.io.OsmServerWriter;
37import org.openstreetmap.josm.io.OsmTransferCanceledException;
38import org.openstreetmap.josm.io.OsmTransferException;
39import org.openstreetmap.josm.tools.ImageProvider;
40
41/**
42 * The task for uploading a collection of primitives.
43 * @since 2599
44 */
45public class UploadPrimitivesTask extends AbstractUploadTask {
46 private boolean uploadCanceled;
47 private Exception lastException;
48 private final APIDataSet toUpload;
49 private OsmServerWriter writer;
50 private final OsmDataLayer layer;
51 private Changeset changeset;
52 private final Set<IPrimitive> processedPrimitives;
53 private final UploadStrategySpecification strategy;
54
55 /**
56 * Creates the task
57 *
58 * @param strategy the upload strategy. Must not be null.
59 * @param layer the OSM data layer for which data is uploaded. Must not be null.
60 * @param toUpload the collection of primitives to upload. Set to the empty collection if null.
61 * @param changeset the changeset to use for uploading. Must not be null. changeset.getId()
62 * can be 0 in which case the upload task creates a new changeset
63 * @throws IllegalArgumentException if layer is null
64 * @throws IllegalArgumentException if toUpload is null
65 * @throws IllegalArgumentException if strategy is null
66 * @throws IllegalArgumentException if changeset is null
67 */
68 public UploadPrimitivesTask(UploadStrategySpecification strategy, OsmDataLayer layer, APIDataSet toUpload, Changeset changeset) {
69 super(tr("Uploading data for layer ''{0}''", layer.getName()), false /* don't ignore exceptions */);
70 ensureParameterNotNull(layer, "layer");
71 ensureParameterNotNull(strategy, "strategy");
72 ensureParameterNotNull(changeset, "changeset");
73 this.toUpload = toUpload;
74 this.layer = layer;
75 this.changeset = changeset;
76 this.strategy = strategy;
77 this.processedPrimitives = new HashSet<>();
78 }
79
80 protected MaxChangesetSizeExceededPolicy askMaxChangesetSizeExceedsPolicy() {
81 ButtonSpec[] specs = new ButtonSpec[] {
82 new ButtonSpec(
83 tr("Continue uploading"),
84 ImageProvider.get("upload"),
85 tr("Click to continue uploading to additional new changesets"),
86 null /* no specific help text */
87 ),
88 new ButtonSpec(
89 tr("Go back to Upload Dialog"),
90 ImageProvider.get("dialogs", "uploadproperties"),
91 tr("Click to return to the Upload Dialog"),
92 null /* no specific help text */
93 ),
94 new ButtonSpec(
95 tr("Abort"),
96 ImageProvider.get("cancel"),
97 tr("Click to abort uploading"),
98 null /* no specific help text */
99 )
100 };
101 int numObjectsToUploadLeft = toUpload.getSize() - processedPrimitives.size();
102 String msg1 = tr("The server reported that the current changeset was closed.<br>"
103 + "This is most likely because the changesets size exceeded the max. size<br>"
104 + "of {0} objects on the server ''{1}''.",
105 OsmApi.getOsmApi().getCapabilities().getMaxChangesetSize(),
106 OsmApi.getOsmApi().getBaseUrl()
107 );
108 String msg2 = trn(
109 "There is {0} object left to upload.",
110 "There are {0} objects left to upload.",
111 numObjectsToUploadLeft,
112 numObjectsToUploadLeft
113 );
114 String msg3 = tr(
115 "Click ''<strong>{0}</strong>'' to continue uploading to additional new changesets.<br>"
116 + "Click ''<strong>{1}</strong>'' to return to the upload dialog.<br>"
117 + "Click ''<strong>{2}</strong>'' to abort uploading and return to map editing.<br>",
118 specs[0].text,
119 specs[1].text,
120 specs[2].text
121 );
122 String msg = "<html>" + msg1 + "<br><br>" + msg2 +"<br><br>" + msg3 + "</html>";
123 int ret = HelpAwareOptionPane.showOptionDialog(
124 Main.parent,
125 msg,
126 tr("Changeset is full"),
127 JOptionPane.WARNING_MESSAGE,
128 null, /* no special icon */
129 specs,
130 specs[0],
131 ht("/Action/Upload#ChangesetFull")
132 );
133 switch(ret) {
134 case 0: return MaxChangesetSizeExceededPolicy.AUTOMATICALLY_OPEN_NEW_CHANGESETS;
135 case 1: return MaxChangesetSizeExceededPolicy.FILL_ONE_CHANGESET_AND_RETURN_TO_UPLOAD_DIALOG;
136 case 2:
137 case JOptionPane.CLOSED_OPTION:
138 default: return MaxChangesetSizeExceededPolicy.ABORT;
139 }
140 }
141
142 /**
143 * Opens a new changeset.
144 */
145 protected void openNewChangeset() {
146 // make sure the current changeset is removed from the upload dialog.
147 ChangesetCache.getInstance().update(changeset);
148 Changeset newChangeSet = new Changeset();
149 newChangeSet.setKeys(this.changeset.getKeys());
150 this.changeset = newChangeSet;
151 }
152
153 protected boolean recoverFromChangesetFullException() throws OsmTransferException {
154 if (toUpload.getSize() - processedPrimitives.size() == 0) {
155 strategy.setPolicy(MaxChangesetSizeExceededPolicy.ABORT);
156 return false;
157 }
158 if (strategy.getPolicy() == null || strategy.getPolicy().equals(MaxChangesetSizeExceededPolicy.ABORT)) {
159 strategy.setPolicy(askMaxChangesetSizeExceedsPolicy());
160 }
161 switch(strategy.getPolicy()) {
162 case AUTOMATICALLY_OPEN_NEW_CHANGESETS:
163 // prepare the state of the task for a next iteration in uploading.
164 closeChangesetIfRequired();
165 openNewChangeset();
166 toUpload.removeProcessed(processedPrimitives);
167 return true;
168 case ABORT:
169 case FILL_ONE_CHANGESET_AND_RETURN_TO_UPLOAD_DIALOG:
170 default:
171 // don't continue - finish() will send the user back to map editing or upload dialog
172 return false;
173 }
174 }
175
176 /**
177 * Retries to recover the upload operation from an exception which was thrown because
178 * an uploaded primitive was already deleted on the server.
179 *
180 * @param e the exception throw by the API
181 * @param monitor a progress monitor
182 * @throws OsmTransferException if we can't recover from the exception
183 */
184 protected void recoverFromGoneOnServer(OsmApiPrimitiveGoneException e, ProgressMonitor monitor) throws OsmTransferException {
185 if (!e.isKnownPrimitive()) throw e;
186 OsmPrimitive p = layer.data.getPrimitiveById(e.getPrimitiveId(), e.getPrimitiveType());
187 if (p == null) throw e;
188 if (p.isDeleted()) {
189 // we tried to delete an already deleted primitive.
190 final String msg;
191 final String displayName = p.getDisplayName(DefaultNameFormatter.getInstance());
192 if (p instanceof Node) {
193 msg = tr("Node ''{0}'' is already deleted. Skipping object in upload.", displayName);
194 } else if (p instanceof Way) {
195 msg = tr("Way ''{0}'' is already deleted. Skipping object in upload.", displayName);
196 } else if (p instanceof Relation) {
197 msg = tr("Relation ''{0}'' is already deleted. Skipping object in upload.", displayName);
198 } else {
199 msg = tr("Object ''{0}'' is already deleted. Skipping object in upload.", displayName);
200 }
201 monitor.appendLogMessage(msg);
202 Main.warn(msg);
203 processedPrimitives.addAll(writer.getProcessedPrimitives());
204 processedPrimitives.add(p);
205 toUpload.removeProcessed(processedPrimitives);
206 return;
207 }
208 // exception was thrown because we tried to *update* an already deleted
209 // primitive. We can't resolve this automatically. Re-throw exception,
210 // a conflict is going to be created later.
211 throw e;
212 }
213
214 protected void cleanupAfterUpload() {
215 // we always clean up the data, even in case of errors. It's possible the data was
216 // partially uploaded. Better run on EDT.
217 Runnable r = () -> {
218 layer.cleanupAfterUpload(processedPrimitives);
219 layer.onPostUploadToServer();
220 ChangesetCache.getInstance().update(changeset);
221 };
222
223 try {
224 SwingUtilities.invokeAndWait(r);
225 } catch (InterruptedException e) {
226 Main.trace(e);
227 lastException = e;
228 Thread.currentThread().interrupt();
229 } catch (InvocationTargetException e) {
230 Main.trace(e);
231 lastException = new OsmTransferException(e.getCause());
232 }
233 }
234
235 @Override
236 protected void realRun() {
237 try {
238 MessageNotifier.stop();
239 uploadloop: while (true) {
240 try {
241 getProgressMonitor().subTask(
242 trn("Uploading {0} object...", "Uploading {0} objects...", toUpload.getSize(), toUpload.getSize()));
243 synchronized (this) {
244 writer = new OsmServerWriter();
245 }
246 writer.uploadOsm(strategy, toUpload.getPrimitives(), changeset, getProgressMonitor().createSubTaskMonitor(1, false));
247
248 // if we get here we've successfully uploaded the data. Exit the loop.
249 break;
250 } catch (OsmTransferCanceledException e) {
251 Main.error(e);
252 uploadCanceled = true;
253 break uploadloop;
254 } catch (OsmApiPrimitiveGoneException e) {
255 // try to recover from 410 Gone
256 recoverFromGoneOnServer(e, getProgressMonitor());
257 } catch (ChangesetClosedException e) {
258 if (writer != null) {
259 processedPrimitives.addAll(writer.getProcessedPrimitives()); // OsmPrimitive in => OsmPrimitive out
260 }
261 switch(e.getSource()) {
262 case UPLOAD_DATA:
263 // Most likely the changeset is full. Try to recover and continue
264 // with a new changeset, but let the user decide first (see
265 // recoverFromChangesetFullException)
266 if (recoverFromChangesetFullException()) {
267 continue;
268 }
269 lastException = e;
270 break uploadloop;
271 case UNSPECIFIED:
272 case UPDATE_CHANGESET:
273 default:
274 // The changeset was closed when we tried to update it. Probably, our
275 // local list of open changesets got out of sync with the server state.
276 // The user will have to select another open changeset.
277 // Rethrow exception - this will be handled later.
278 changeset.setOpen(false);
279 throw e;
280 }
281 } finally {
282 if (writer != null) {
283 processedPrimitives.addAll(writer.getProcessedPrimitives());
284 }
285 synchronized (this) {
286 writer = null;
287 }
288 }
289 }
290 // if required close the changeset
291 closeChangesetIfRequired();
292 } catch (OsmTransferException e) {
293 if (uploadCanceled) {
294 Main.info(tr("Ignoring caught exception because upload is canceled. Exception is: {0}", e.toString()));
295 } else {
296 lastException = e;
297 }
298 } finally {
299 if (MessageNotifier.PROP_NOTIFIER_ENABLED.get()) {
300 MessageNotifier.start();
301 }
302 }
303 if (uploadCanceled && processedPrimitives.isEmpty()) return;
304 cleanupAfterUpload();
305 }
306
307 private void closeChangesetIfRequired() throws OsmTransferException {
308 if (strategy.isCloseChangesetAfterUpload() && changeset != null && !changeset.isNew() && changeset.isOpen()) {
309 OsmApi.getOsmApi().closeChangeset(changeset, progressMonitor.createSubTaskMonitor(0, false));
310 }
311 }
312
313 @Override protected void finish() {
314
315 // depending on the success of the upload operation and on the policy for
316 // multi changeset uploads this will sent the user back to the appropriate
317 // place in JOSM, either
318 // - to an error dialog
319 // - to the Upload Dialog
320 // - to map editing
321 GuiHelper.runInEDT(() -> {
322 // if the changeset is still open after this upload we want it to be selected on the next upload
323 ChangesetCache.getInstance().update(changeset);
324 if (changeset != null && changeset.isOpen()) {
325 UploadDialog.getUploadDialog().setSelectedChangesetForNextUpload(changeset);
326 }
327 if (uploadCanceled) return;
328 if (lastException == null) {
329 new Notification(
330 "<h3>" + tr("Upload successful!") + "</h3>")
331 .setIcon(ImageProvider.get("misc", "check_large"))
332 .show();
333 return;
334 }
335 if (lastException instanceof ChangesetClosedException) {
336 ChangesetClosedException e = (ChangesetClosedException) lastException;
337 if (e.getSource().equals(ChangesetClosedException.Source.UPDATE_CHANGESET)) {
338 handleFailedUpload(lastException);
339 return;
340 }
341 if (strategy.getPolicy() == null)
342 /* do nothing if unknown policy */
343 return;
344 if (e.getSource().equals(ChangesetClosedException.Source.UPLOAD_DATA)) {
345 switch(strategy.getPolicy()) {
346 case ABORT:
347 break; /* do nothing - we return to map editing */
348 case AUTOMATICALLY_OPEN_NEW_CHANGESETS:
349 break; /* do nothing - we return to map editing */
350 case FILL_ONE_CHANGESET_AND_RETURN_TO_UPLOAD_DIALOG:
351 // return to the upload dialog
352 //
353 toUpload.removeProcessed(processedPrimitives);
354 UploadDialog.getUploadDialog().setUploadedPrimitives(toUpload);
355 UploadDialog.getUploadDialog().setVisible(true);
356 break;
357 }
358 } else {
359 handleFailedUpload(lastException);
360 }
361 } else {
362 handleFailedUpload(lastException);
363 }
364 });
365 }
366
367 @Override protected void cancel() {
368 uploadCanceled = true;
369 synchronized (this) {
370 if (writer != null) {
371 writer.cancel();
372 }
373 }
374 }
375}
Note: See TracBrowser for help on using the repository browser.