| 1 | // License: GPL. Copyright 2007 by Immanuel Scholz and others |
|---|
| 2 | package org.openstreetmap.josm.io; |
|---|
| 3 | |
|---|
| 4 | import static org.openstreetmap.josm.tools.I18n.marktr; |
|---|
| 5 | import static org.openstreetmap.josm.tools.I18n.tr; |
|---|
| 6 | import static org.openstreetmap.josm.tools.I18n.trn; |
|---|
| 7 | |
|---|
| 8 | import java.util.ArrayList; |
|---|
| 9 | import java.util.Collection; |
|---|
| 10 | import java.util.Iterator; |
|---|
| 11 | import java.util.LinkedList; |
|---|
| 12 | import java.util.List; |
|---|
| 13 | |
|---|
| 14 | import org.openstreetmap.josm.data.osm.Changeset; |
|---|
| 15 | import org.openstreetmap.josm.data.osm.IPrimitive; |
|---|
| 16 | import org.openstreetmap.josm.data.osm.OsmPrimitiveType; |
|---|
| 17 | import org.openstreetmap.josm.gui.io.UploadStrategySpecification; |
|---|
| 18 | import org.openstreetmap.josm.gui.progress.NullProgressMonitor; |
|---|
| 19 | import org.openstreetmap.josm.gui.progress.ProgressMonitor; |
|---|
| 20 | import org.openstreetmap.josm.tools.CheckParameterUtil; |
|---|
| 21 | |
|---|
| 22 | /** |
|---|
| 23 | * Class that uploads all changes to the osm server. |
|---|
| 24 | * |
|---|
| 25 | * This is done like this: - All objects with id = 0 are uploaded as new, except |
|---|
| 26 | * those in deleted, which are ignored - All objects in deleted list are |
|---|
| 27 | * deleted. - All remaining objects with modified flag set are updated. |
|---|
| 28 | */ |
|---|
| 29 | public class OsmServerWriter { |
|---|
| 30 | /** |
|---|
| 31 | * This list contains all successfully processed objects. The caller of |
|---|
| 32 | * upload* has to check this after the call and update its dataset. |
|---|
| 33 | * |
|---|
| 34 | * If a server connection error occurs, this may contain fewer entries |
|---|
| 35 | * than where passed in the list to upload*. |
|---|
| 36 | */ |
|---|
| 37 | private Collection<IPrimitive> processed; |
|---|
| 38 | |
|---|
| 39 | private static ArrayList<OsmServerWritePostprocessor> postprocessors; |
|---|
| 40 | public static void registerPostprocessor(OsmServerWritePostprocessor pp) { |
|---|
| 41 | if (postprocessors == null) { |
|---|
| 42 | postprocessors = new ArrayList<OsmServerWritePostprocessor>(); |
|---|
| 43 | } |
|---|
| 44 | postprocessors.add(pp); |
|---|
| 45 | } |
|---|
| 46 | public static void unregisterPostprocessor(OsmServerWritePostprocessor pp) { |
|---|
| 47 | if (postprocessors != null) { |
|---|
| 48 | postprocessors.remove(pp); |
|---|
| 49 | } |
|---|
| 50 | } |
|---|
| 51 | |
|---|
| 52 | private OsmApi api = OsmApi.getOsmApi(); |
|---|
| 53 | private boolean canceled = false; |
|---|
| 54 | |
|---|
| 55 | private static final int MSECS_PER_SECOND = 1000; |
|---|
| 56 | private static final int SECONDS_PER_MINUTE = 60; |
|---|
| 57 | private static final int MSECS_PER_MINUTE = MSECS_PER_SECOND * SECONDS_PER_MINUTE; |
|---|
| 58 | |
|---|
| 59 | long uploadStartTime; |
|---|
| 60 | |
|---|
| 61 | public String timeLeft(int progress, int list_size) { |
|---|
| 62 | long now = System.currentTimeMillis(); |
|---|
| 63 | long elapsed = now - uploadStartTime; |
|---|
| 64 | if (elapsed == 0) { |
|---|
| 65 | elapsed = 1; |
|---|
| 66 | } |
|---|
| 67 | float uploads_per_ms = (float)progress / elapsed; |
|---|
| 68 | float uploads_left = list_size - progress; |
|---|
| 69 | int ms_left = (int)(uploads_left / uploads_per_ms); |
|---|
| 70 | int minutes_left = ms_left / MSECS_PER_MINUTE; |
|---|
| 71 | int seconds_left = (ms_left / MSECS_PER_SECOND) % SECONDS_PER_MINUTE ; |
|---|
| 72 | String time_left_str = Integer.toString(minutes_left) + ":"; |
|---|
| 73 | if (seconds_left < 10) { |
|---|
| 74 | time_left_str += "0"; |
|---|
| 75 | } |
|---|
| 76 | time_left_str += Integer.toString(seconds_left); |
|---|
| 77 | return time_left_str; |
|---|
| 78 | } |
|---|
| 79 | |
|---|
| 80 | /** |
|---|
| 81 | * Uploads the changes individually. Invokes one API call per uploaded primitmive. |
|---|
| 82 | * |
|---|
| 83 | * @param primitives the collection of primitives to upload |
|---|
| 84 | * @param progressMonitor the progress monitor |
|---|
| 85 | * @throws OsmTransferException thrown if an exception occurs |
|---|
| 86 | */ |
|---|
| 87 | protected void uploadChangesIndividually(Collection<? extends IPrimitive> primitives, ProgressMonitor progressMonitor) throws OsmTransferException { |
|---|
| 88 | try { |
|---|
| 89 | progressMonitor.beginTask(tr("Starting to upload with one request per primitive ...")); |
|---|
| 90 | progressMonitor.setTicksCount(primitives.size()); |
|---|
| 91 | uploadStartTime = System.currentTimeMillis(); |
|---|
| 92 | for (IPrimitive osm : primitives) { |
|---|
| 93 | int progress = progressMonitor.getTicks(); |
|---|
| 94 | String time_left_str = timeLeft(progress, primitives.size()); |
|---|
| 95 | String msg = ""; |
|---|
| 96 | switch(OsmPrimitiveType.from(osm)) { |
|---|
| 97 | case NODE: msg = marktr("{0}% ({1}/{2}), {3} left. Uploading node ''{4}'' (id: {5})"); break; |
|---|
| 98 | case WAY: msg = marktr("{0}% ({1}/{2}), {3} left. Uploading way ''{4}'' (id: {5})"); break; |
|---|
| 99 | case RELATION: msg = marktr("{0}% ({1}/{2}), {3} left. Uploading relation ''{4}'' (id: {5})"); break; |
|---|
| 100 | } |
|---|
| 101 | progressMonitor.subTask( |
|---|
| 102 | tr(msg, |
|---|
| 103 | Math.round(100.0*progress/primitives.size()), |
|---|
| 104 | progress, |
|---|
| 105 | primitives.size(), |
|---|
| 106 | time_left_str, |
|---|
| 107 | osm.getName() == null ? osm.getId() : osm.getName(), |
|---|
| 108 | osm.getId())); |
|---|
| 109 | makeApiRequest(osm,progressMonitor); |
|---|
| 110 | processed.add(osm); |
|---|
| 111 | progressMonitor.worked(1); |
|---|
| 112 | } |
|---|
| 113 | } catch(OsmTransferException e) { |
|---|
| 114 | throw e; |
|---|
| 115 | } catch(Exception e) { |
|---|
| 116 | throw new OsmTransferException(e); |
|---|
| 117 | } finally { |
|---|
| 118 | progressMonitor.finishTask(); |
|---|
| 119 | } |
|---|
| 120 | } |
|---|
| 121 | |
|---|
| 122 | /** |
|---|
| 123 | * Upload all changes in one diff upload |
|---|
| 124 | * |
|---|
| 125 | * @param primitives the collection of primitives to upload |
|---|
| 126 | * @param progressMonitor the progress monitor |
|---|
| 127 | * @throws OsmTransferException thrown if an exception occurs |
|---|
| 128 | */ |
|---|
| 129 | protected void uploadChangesAsDiffUpload(Collection<? extends IPrimitive> primitives, ProgressMonitor progressMonitor) throws OsmTransferException { |
|---|
| 130 | try { |
|---|
| 131 | progressMonitor.beginTask(tr("Starting to upload in one request ...")); |
|---|
| 132 | processed.addAll(api.uploadDiff(primitives, progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false))); |
|---|
| 133 | } catch(OsmTransferException e) { |
|---|
| 134 | throw e; |
|---|
| 135 | } finally { |
|---|
| 136 | progressMonitor.finishTask(); |
|---|
| 137 | } |
|---|
| 138 | } |
|---|
| 139 | |
|---|
| 140 | /** |
|---|
| 141 | * Upload all changes in one diff upload |
|---|
| 142 | * |
|---|
| 143 | * @param primitives the collection of primitives to upload |
|---|
| 144 | * @param progressMonitor the progress monitor |
|---|
| 145 | * @param chunkSize the size of the individual upload chunks. > 0 required. |
|---|
| 146 | * @throws IllegalArgumentException thrown if chunkSize <= 0 |
|---|
| 147 | * @throws OsmTransferException thrown if an exception occurs |
|---|
| 148 | */ |
|---|
| 149 | protected void uploadChangesInChunks(Collection<? extends IPrimitive> primitives, ProgressMonitor progressMonitor, int chunkSize) throws OsmTransferException, IllegalArgumentException { |
|---|
| 150 | if (chunkSize <=0) |
|---|
| 151 | throw new IllegalArgumentException(tr("Value >0 expected for parameter ''{0}'', got {1}", "chunkSize", chunkSize)); |
|---|
| 152 | try { |
|---|
| 153 | progressMonitor.beginTask(tr("Starting to upload in chunks...")); |
|---|
| 154 | List<IPrimitive> chunk = new ArrayList<IPrimitive>(chunkSize); |
|---|
| 155 | Iterator<? extends IPrimitive> it = primitives.iterator(); |
|---|
| 156 | int numChunks = (int)Math.ceil((double)primitives.size() / (double)chunkSize); |
|---|
| 157 | int i= 0; |
|---|
| 158 | while(it.hasNext()) { |
|---|
| 159 | i++; |
|---|
| 160 | if (canceled) return; |
|---|
| 161 | int j = 0; |
|---|
| 162 | chunk.clear(); |
|---|
| 163 | while(it.hasNext() && j < chunkSize) { |
|---|
| 164 | if (canceled) return; |
|---|
| 165 | j++; |
|---|
| 166 | chunk.add(it.next()); |
|---|
| 167 | } |
|---|
| 168 | progressMonitor.setCustomText( |
|---|
| 169 | trn("({0}/{1}) Uploading {2} object...", |
|---|
| 170 | "({0}/{1}) Uploading {2} objects...", |
|---|
| 171 | chunk.size(), i, numChunks, chunk.size())); |
|---|
| 172 | processed.addAll(api.uploadDiff(chunk, progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false))); |
|---|
| 173 | } |
|---|
| 174 | } catch(OsmTransferException e) { |
|---|
| 175 | throw e; |
|---|
| 176 | } finally { |
|---|
| 177 | progressMonitor.finishTask(); |
|---|
| 178 | } |
|---|
| 179 | } |
|---|
| 180 | |
|---|
| 181 | /** |
|---|
| 182 | * Send the dataset to the server. |
|---|
| 183 | * |
|---|
| 184 | * @param strategy the upload strategy. Must not be null. |
|---|
| 185 | * @param primitives list of objects to send |
|---|
| 186 | * @param changeset the changeset the data is uploaded to. Must not be null. |
|---|
| 187 | * @param monitor the progress monitor. If null, assumes {@see NullProgressMonitor#INSTANCE} |
|---|
| 188 | * @throws IllegalArgumentException thrown if changeset is null |
|---|
| 189 | * @throws IllegalArgumentException thrown if strategy is null |
|---|
| 190 | * @throws OsmTransferException thrown if something goes wrong |
|---|
| 191 | */ |
|---|
| 192 | public void uploadOsm(UploadStrategySpecification strategy, Collection<? extends IPrimitive> primitives, Changeset changeset, ProgressMonitor monitor) throws OsmTransferException { |
|---|
| 193 | CheckParameterUtil.ensureParameterNotNull(changeset, "changeset"); |
|---|
| 194 | processed = new LinkedList<IPrimitive>(); |
|---|
| 195 | monitor = monitor == null ? NullProgressMonitor.INSTANCE : monitor; |
|---|
| 196 | monitor.beginTask(tr("Uploading data ...")); |
|---|
| 197 | try { |
|---|
| 198 | api.initialize(monitor); |
|---|
| 199 | // check whether we can use diff upload |
|---|
| 200 | if (changeset.getId() == 0) { |
|---|
| 201 | api.openChangeset(changeset,monitor.createSubTaskMonitor(0, false)); |
|---|
| 202 | } else { |
|---|
| 203 | api.updateChangeset(changeset,monitor.createSubTaskMonitor(0, false)); |
|---|
| 204 | } |
|---|
| 205 | api.setChangeset(changeset); |
|---|
| 206 | switch(strategy.getStrategy()) { |
|---|
| 207 | case SINGLE_REQUEST_STRATEGY: |
|---|
| 208 | uploadChangesAsDiffUpload(primitives,monitor.createSubTaskMonitor(0,false)); |
|---|
| 209 | break; |
|---|
| 210 | case INDIVIDUAL_OBJECTS_STRATEGY: |
|---|
| 211 | uploadChangesIndividually(primitives,monitor.createSubTaskMonitor(0,false)); |
|---|
| 212 | break; |
|---|
| 213 | case CHUNKED_DATASET_STRATEGY: |
|---|
| 214 | uploadChangesInChunks(primitives,monitor.createSubTaskMonitor(0,false), strategy.getChunkSize()); |
|---|
| 215 | break; |
|---|
| 216 | } |
|---|
| 217 | } catch(OsmTransferException e) { |
|---|
| 218 | throw e; |
|---|
| 219 | } finally { |
|---|
| 220 | executePostprocessors(monitor); |
|---|
| 221 | monitor.finishTask(); |
|---|
| 222 | api.setChangeset(null); |
|---|
| 223 | } |
|---|
| 224 | } |
|---|
| 225 | |
|---|
| 226 | void makeApiRequest(IPrimitive osm, ProgressMonitor progressMonitor) throws OsmTransferException { |
|---|
| 227 | if (osm.isDeleted()) { |
|---|
| 228 | api.deletePrimitive(osm, progressMonitor); |
|---|
| 229 | } else if (osm.isNew()) { |
|---|
| 230 | api.createPrimitive(osm, progressMonitor); |
|---|
| 231 | } else { |
|---|
| 232 | api.modifyPrimitive(osm, progressMonitor); |
|---|
| 233 | } |
|---|
| 234 | } |
|---|
| 235 | |
|---|
| 236 | public void cancel() { |
|---|
| 237 | this.canceled = true; |
|---|
| 238 | if (api != null) { |
|---|
| 239 | api.cancel(); |
|---|
| 240 | } |
|---|
| 241 | } |
|---|
| 242 | |
|---|
| 243 | /** |
|---|
| 244 | * Replies the collection of successfully processed primitives |
|---|
| 245 | * |
|---|
| 246 | * @return the collection of successfully processed primitives |
|---|
| 247 | */ |
|---|
| 248 | public Collection<IPrimitive> getProcessedPrimitives() { |
|---|
| 249 | return processed; |
|---|
| 250 | } |
|---|
| 251 | |
|---|
| 252 | /** |
|---|
| 253 | * Calls all registered upload postprocessors. |
|---|
| 254 | */ |
|---|
| 255 | public void executePostprocessors(ProgressMonitor pm) { |
|---|
| 256 | if (postprocessors != null) { |
|---|
| 257 | for (OsmServerWritePostprocessor pp : postprocessors) { |
|---|
| 258 | pp.postprocessUploadedPrimitives(processed, pm); |
|---|
| 259 | } |
|---|
| 260 | } |
|---|
| 261 | } |
|---|
| 262 | } |
|---|