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

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

fix #8039, fix #10456: final fixes for the read-only/locked layers:

  • rename "read-only" to "locked" (in XML and Java classes/interfaces)
  • add a new download policy (true/never) to allow private layers forbidding only to download data, but allowing everything else

This leads to:

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