001 // License: GPL. Copyright 2007 by Immanuel Scholz and others 002 package org.openstreetmap.josm.io; 003 004 import static org.openstreetmap.josm.tools.I18n.marktr; 005 import static org.openstreetmap.josm.tools.I18n.tr; 006 import static org.openstreetmap.josm.tools.I18n.trn; 007 008 import java.util.ArrayList; 009 import java.util.Collection; 010 import java.util.Iterator; 011 import java.util.LinkedList; 012 import java.util.List; 013 014 import org.openstreetmap.josm.data.osm.Changeset; 015 import org.openstreetmap.josm.data.osm.IPrimitive; 016 import org.openstreetmap.josm.data.osm.OsmPrimitiveType; 017 import org.openstreetmap.josm.gui.io.UploadStrategySpecification; 018 import org.openstreetmap.josm.gui.progress.NullProgressMonitor; 019 import org.openstreetmap.josm.gui.progress.ProgressMonitor; 020 import 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 */ 029 public 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<IPrimitive> processed; 038 039 private static ArrayList<OsmServerWritePostprocessor> postprocessors; 040 public static void registerPostprocessor(OsmServerWritePostprocessor pp) { 041 if (postprocessors == null) { 042 postprocessors = new ArrayList<OsmServerWritePostprocessor>(); 043 } 044 postprocessors.add(pp); 045 } 046 public static void unregisterPostprocessor(OsmServerWritePostprocessor pp) { 047 if (postprocessors != null) { 048 postprocessors.remove(pp); 049 } 050 } 051 052 private OsmApi api = OsmApi.getOsmApi(); 053 private boolean canceled = false; 054 055 private static final int MSECS_PER_SECOND = 1000; 056 private static final int SECONDS_PER_MINUTE = 60; 057 private static final int MSECS_PER_MINUTE = MSECS_PER_SECOND * SECONDS_PER_MINUTE; 058 059 long uploadStartTime; 060 061 public String timeLeft(int progress, int list_size) { 062 long now = System.currentTimeMillis(); 063 long elapsed = now - uploadStartTime; 064 if (elapsed == 0) { 065 elapsed = 1; 066 } 067 float uploads_per_ms = (float)progress / elapsed; 068 float uploads_left = list_size - progress; 069 int ms_left = (int)(uploads_left / uploads_per_ms); 070 int minutes_left = ms_left / MSECS_PER_MINUTE; 071 int seconds_left = (ms_left / MSECS_PER_SECOND) % SECONDS_PER_MINUTE ; 072 String time_left_str = Integer.toString(minutes_left) + ":"; 073 if (seconds_left < 10) { 074 time_left_str += "0"; 075 } 076 time_left_str += Integer.toString(seconds_left); 077 return time_left_str; 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 thrown if an exception occurs 086 */ 087 protected void uploadChangesIndividually(Collection<? extends IPrimitive> primitives, ProgressMonitor progressMonitor) throws OsmTransferException { 088 try { 089 progressMonitor.beginTask(tr("Starting to upload with one request per primitive ...")); 090 progressMonitor.setTicksCount(primitives.size()); 091 uploadStartTime = System.currentTimeMillis(); 092 for (IPrimitive osm : primitives) { 093 int progress = progressMonitor.getTicks(); 094 String time_left_str = timeLeft(progress, primitives.size()); 095 String msg = ""; 096 switch(OsmPrimitiveType.from(osm)) { 097 case NODE: msg = marktr("{0}% ({1}/{2}), {3} left. Uploading node ''{4}'' (id: {5})"); break; 098 case WAY: msg = marktr("{0}% ({1}/{2}), {3} left. Uploading way ''{4}'' (id: {5})"); break; 099 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 {@link 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 }