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