001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.io; 003 004import static org.openstreetmap.josm.tools.I18n.marktr; 005import static org.openstreetmap.josm.tools.I18n.tr; 006import static org.openstreetmap.josm.tools.I18n.trn; 007 008import java.util.ArrayList; 009import java.util.Collection; 010import java.util.Iterator; 011import java.util.LinkedList; 012import java.util.List; 013import java.util.concurrent.TimeUnit; 014 015import org.openstreetmap.josm.data.UserIdentityManager; 016import org.openstreetmap.josm.data.osm.Changeset; 017import org.openstreetmap.josm.data.osm.OsmPrimitive; 018import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 019import org.openstreetmap.josm.gui.progress.NullProgressMonitor; 020import org.openstreetmap.josm.gui.progress.ProgressMonitor; 021import org.openstreetmap.josm.tools.CheckParameterUtil; 022 023/** 024 * Class that uploads all changes to the osm server. 025 * 026 * This is done like this: - All objects with id = 0 are uploaded as new, except 027 * those in deleted, which are ignored - All objects in deleted list are 028 * deleted. - All remaining objects with modified flag set are updated. 029 */ 030public class OsmServerWriter { 031 /** 032 * This list contains all successfully processed objects. The caller of 033 * upload* has to check this after the call and update its dataset. 034 * 035 * If a server connection error occurs, this may contain fewer entries 036 * than where passed in the list to upload*. 037 */ 038 private Collection<OsmPrimitive> processed; 039 040 private static volatile List<OsmServerWritePostprocessor> postprocessors; 041 042 /** 043 * Registers a post-processor. 044 * @param pp post-processor to register 045 */ 046 public static void registerPostprocessor(OsmServerWritePostprocessor pp) { 047 if (postprocessors == null) { 048 postprocessors = new ArrayList<>(); 049 } 050 postprocessors.add(pp); 051 } 052 053 /** 054 * Unregisters a post-processor. 055 * @param pp post-processor to unregister 056 */ 057 public static void unregisterPostprocessor(OsmServerWritePostprocessor pp) { 058 if (postprocessors != null) { 059 postprocessors.remove(pp); 060 } 061 } 062 063 private final OsmApi api = OsmApi.getOsmApi(); 064 private boolean canceled; 065 066 private long uploadStartTime; 067 068 protected String timeLeft(int progress, int listSize) { 069 long now = System.currentTimeMillis(); 070 long elapsed = now - uploadStartTime; 071 if (elapsed == 0) { 072 elapsed = 1; 073 } 074 double uploadsPerMs = (double) progress / elapsed; 075 double uploadsLeft = (double) listSize - progress; 076 long msLeft = (long) (uploadsLeft / uploadsPerMs); 077 long minutesLeft = msLeft / TimeUnit.MINUTES.toMillis(1); 078 long secondsLeft = (msLeft / TimeUnit.SECONDS.toMillis(1)) % TimeUnit.MINUTES.toSeconds(1); 079 StringBuilder timeLeftStr = new StringBuilder().append(minutesLeft).append(':'); 080 if (secondsLeft < 10) { 081 timeLeftStr.append('0'); 082 } 083 return timeLeftStr.append(secondsLeft).toString(); 084 } 085 086 /** 087 * Uploads the changes individually. Invokes one API call per uploaded primitive. 088 * 089 * @param primitives the collection of primitives to upload 090 * @param progressMonitor the progress monitor 091 * @throws OsmTransferException if an exception occurs 092 */ 093 protected void uploadChangesIndividually(Collection<? extends OsmPrimitive> primitives, ProgressMonitor progressMonitor) 094 throws OsmTransferException { 095 try { 096 progressMonitor.beginTask(tr("Starting to upload with one request per primitive ...")); 097 progressMonitor.setTicksCount(primitives.size()); 098 uploadStartTime = System.currentTimeMillis(); 099 for (OsmPrimitive osm : primitives) { 100 String msg; 101 switch(OsmPrimitiveType.from(osm)) { 102 case NODE: msg = marktr("{0}% ({1}/{2}), {3} left. Uploading node ''{4}'' (id: {5})"); break; 103 case WAY: msg = marktr("{0}% ({1}/{2}), {3} left. Uploading way ''{4}'' (id: {5})"); break; 104 case RELATION: msg = marktr("{0}% ({1}/{2}), {3} left. Uploading relation ''{4}'' (id: {5})"); break; 105 default: throw new AssertionError(); 106 } 107 int progress = progressMonitor.getTicks(); 108 progressMonitor.subTask( 109 tr(msg, 110 Math.round(100.0*progress/primitives.size()), 111 progress, 112 primitives.size(), 113 timeLeft(progress, primitives.size()), 114 osm.getName() == null ? osm.getId() : osm.getName(), osm.getId())); 115 makeApiRequest(osm, progressMonitor); 116 processed.add(osm); 117 progressMonitor.worked(1); 118 } 119 } finally { 120 progressMonitor.finishTask(); 121 } 122 } 123 124 /** 125 * Upload all changes in one diff upload 126 * 127 * @param primitives the collection of primitives to upload 128 * @param progressMonitor the progress monitor 129 * @throws OsmTransferException if an exception occurs 130 */ 131 protected void uploadChangesAsDiffUpload(Collection<? extends OsmPrimitive> primitives, ProgressMonitor progressMonitor) 132 throws OsmTransferException { 133 try { 134 progressMonitor.beginTask(tr("Starting to upload in one request ...")); 135 processed.addAll(api.uploadDiff(primitives, progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false))); 136 } finally { 137 progressMonitor.finishTask(); 138 } 139 } 140 141 /** 142 * Upload all changes in one diff upload 143 * 144 * @param primitives the collection of primitives to upload 145 * @param progressMonitor the progress monitor 146 * @param chunkSize the size of the individual upload chunks. > 0 required. 147 * @throws IllegalArgumentException if chunkSize <= 0 148 * @throws OsmTransferException if an exception occurs 149 */ 150 protected void uploadChangesInChunks(Collection<? extends OsmPrimitive> primitives, ProgressMonitor progressMonitor, int chunkSize) 151 throws OsmTransferException { 152 if (chunkSize <= 0) 153 throw new IllegalArgumentException(tr("Value >0 expected for parameter ''{0}'', got {1}", "chunkSize", chunkSize)); 154 try { 155 progressMonitor.beginTask(tr("Starting to upload in chunks...")); 156 List<OsmPrimitive> chunk = new ArrayList<>(chunkSize); 157 Iterator<? extends OsmPrimitive> it = primitives.iterator(); 158 int numChunks = (int) Math.ceil((double) primitives.size() / (double) chunkSize); 159 int i = 0; 160 while (it.hasNext()) { 161 i++; 162 if (canceled) return; 163 int j = 0; 164 chunk.clear(); 165 while (it.hasNext() && j < chunkSize) { 166 if (canceled) return; 167 j++; 168 chunk.add(it.next()); 169 } 170 progressMonitor.setCustomText( 171 trn("({0}/{1}) Uploading {2} object...", 172 "({0}/{1}) Uploading {2} objects...", 173 chunk.size(), i, numChunks, chunk.size())); 174 processed.addAll(api.uploadDiff(chunk, progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false))); 175 } 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 {@link NullProgressMonitor#INSTANCE} 188 * @throws IllegalArgumentException if changeset is null 189 * @throws IllegalArgumentException if strategy is null 190 * @throws OsmTransferException if something goes wrong 191 */ 192 public void uploadOsm(UploadStrategySpecification strategy, Collection<? extends OsmPrimitive> primitives, 193 Changeset changeset, ProgressMonitor monitor) throws OsmTransferException { 194 CheckParameterUtil.ensureParameterNotNull(changeset, "changeset"); 195 processed = new LinkedList<>(); 196 monitor = monitor == null ? NullProgressMonitor.INSTANCE : monitor; 197 monitor.beginTask(tr("Uploading data ...")); 198 try { 199 api.initialize(monitor); 200 // check whether we can use diff upload 201 if (changeset.getId() == 0) { 202 api.openChangeset(changeset, monitor.createSubTaskMonitor(0, false)); 203 // update the user information 204 changeset.setUser(UserIdentityManager.getInstance().asUser()); 205 } else { 206 api.updateChangeset(changeset, monitor.createSubTaskMonitor(0, false)); 207 } 208 api.setChangeset(changeset); 209 switch(strategy.getStrategy()) { 210 case SINGLE_REQUEST_STRATEGY: 211 uploadChangesAsDiffUpload(primitives, monitor.createSubTaskMonitor(0, false)); 212 break; 213 case INDIVIDUAL_OBJECTS_STRATEGY: 214 uploadChangesIndividually(primitives, monitor.createSubTaskMonitor(0, false)); 215 break; 216 case CHUNKED_DATASET_STRATEGY: 217 default: 218 uploadChangesInChunks(primitives, monitor.createSubTaskMonitor(0, false), strategy.getChunkSize()); 219 break; 220 } 221 } finally { 222 executePostprocessors(monitor); 223 monitor.finishTask(); 224 api.setChangeset(null); 225 } 226 } 227 228 void makeApiRequest(OsmPrimitive osm, ProgressMonitor progressMonitor) throws OsmTransferException { 229 if (osm.isDeleted()) { 230 api.deletePrimitive(osm, progressMonitor); 231 } else if (osm.isNew()) { 232 api.createPrimitive(osm, progressMonitor); 233 } else { 234 api.modifyPrimitive(osm, progressMonitor); 235 } 236 } 237 238 /** 239 * Cancel operation. 240 */ 241 public void cancel() { 242 this.canceled = true; 243 if (api != null) { 244 api.cancel(); 245 } 246 } 247 248 /** 249 * Replies the collection of successfully processed primitives 250 * 251 * @return the collection of successfully processed primitives 252 */ 253 public Collection<OsmPrimitive> getProcessedPrimitives() { 254 return processed; 255 } 256 257 /** 258 * Calls all registered upload postprocessors. 259 * @param pm progress monitor 260 */ 261 public void executePostprocessors(ProgressMonitor pm) { 262 if (postprocessors != null) { 263 for (OsmServerWritePostprocessor pp : postprocessors) { 264 pp.postprocessUploadedPrimitives(processed, pm); 265 } 266 } 267 } 268}