[2711] | 1 | // License: GPL. For details, see LICENSE file.
|
---|
| 2 | package org.openstreetmap.josm.gui.io;
|
---|
| 3 |
|
---|
| 4 | import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
|
---|
| 5 | import static org.openstreetmap.josm.tools.CheckParameterUtil.ensureParameterNotNull;
|
---|
| 6 | import static org.openstreetmap.josm.tools.I18n.tr;
|
---|
| 7 | import static org.openstreetmap.josm.tools.I18n.trn;
|
---|
| 8 |
|
---|
| 9 | import java.lang.reflect.InvocationTargetException;
|
---|
| 10 | import java.util.HashSet;
|
---|
[6316] | 11 | import java.util.Set;
|
---|
[2711] | 12 |
|
---|
| 13 | import javax.swing.JOptionPane;
|
---|
| 14 | import javax.swing.SwingUtilities;
|
---|
| 15 |
|
---|
| 16 | import org.openstreetmap.josm.Main;
|
---|
| 17 | import org.openstreetmap.josm.data.APIDataSet;
|
---|
| 18 | import org.openstreetmap.josm.data.osm.Changeset;
|
---|
| 19 | import org.openstreetmap.josm.data.osm.ChangesetCache;
|
---|
[12663] | 20 | import org.openstreetmap.josm.data.osm.DefaultNameFormatter;
|
---|
[4534] | 21 | import org.openstreetmap.josm.data.osm.IPrimitive;
|
---|
[5057] | 22 | import org.openstreetmap.josm.data.osm.Node;
|
---|
[2711] | 23 | import org.openstreetmap.josm.data.osm.OsmPrimitive;
|
---|
[5057] | 24 | import org.openstreetmap.josm.data.osm.Relation;
|
---|
| 25 | import org.openstreetmap.josm.data.osm.Way;
|
---|
[2711] | 26 | import org.openstreetmap.josm.gui.HelpAwareOptionPane;
|
---|
| 27 | import org.openstreetmap.josm.gui.HelpAwareOptionPane.ButtonSpec;
|
---|
[6248] | 28 | import org.openstreetmap.josm.gui.Notification;
|
---|
[2711] | 29 | import org.openstreetmap.josm.gui.layer.OsmDataLayer;
|
---|
| 30 | import org.openstreetmap.josm.gui.progress.ProgressMonitor;
|
---|
[5452] | 31 | import org.openstreetmap.josm.gui.util.GuiHelper;
|
---|
[13392] | 32 | import org.openstreetmap.josm.gui.widgets.HtmlPanel;
|
---|
[2711] | 33 | import org.openstreetmap.josm.io.ChangesetClosedException;
|
---|
[12687] | 34 | import org.openstreetmap.josm.io.MaxChangesetSizeExceededPolicy;
|
---|
[11686] | 35 | import org.openstreetmap.josm.io.MessageNotifier;
|
---|
[2711] | 36 | import org.openstreetmap.josm.io.OsmApi;
|
---|
| 37 | import org.openstreetmap.josm.io.OsmApiPrimitiveGoneException;
|
---|
| 38 | import org.openstreetmap.josm.io.OsmServerWriter;
|
---|
[4310] | 39 | import org.openstreetmap.josm.io.OsmTransferCanceledException;
|
---|
[2711] | 40 | import org.openstreetmap.josm.io.OsmTransferException;
|
---|
[12687] | 41 | import org.openstreetmap.josm.io.UploadStrategySpecification;
|
---|
[2711] | 42 | import org.openstreetmap.josm.tools.ImageProvider;
|
---|
[12620] | 43 | import 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] | 49 | public 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 | }
|
---|