001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.dialogs.changeset; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.awt.Component; 007import java.io.IOException; 008import java.text.MessageFormat; 009import java.util.ArrayList; 010import java.util.Collection; 011import java.util.Collections; 012import java.util.HashSet; 013import java.util.List; 014import java.util.Set; 015 016import org.openstreetmap.josm.data.osm.Changeset; 017import org.openstreetmap.josm.data.osm.ChangesetCache; 018import org.openstreetmap.josm.data.osm.ChangesetDataSet; 019import org.openstreetmap.josm.gui.ExceptionDialogUtil; 020import org.openstreetmap.josm.gui.PleaseWaitRunnable; 021import org.openstreetmap.josm.io.OsmServerChangesetReader; 022import org.openstreetmap.josm.io.OsmTransferCanceledException; 023import org.openstreetmap.josm.io.OsmTransferException; 024import org.xml.sax.SAXException; 025 026/** 027 * This is an asynchronous task for downloading the changeset content of a collection of 028 * changesets. 029 * 030 */ 031public class ChangesetContentDownloadTask extends PleaseWaitRunnable implements ChangesetDownloadTask { 032 033 /** the list of changeset ids to download */ 034 private final List<Integer> toDownload = new ArrayList<>(); 035 /** true if the task was canceled */ 036 private boolean canceled; 037 /** keeps the last exception thrown in the task, if any */ 038 private Exception lastException; 039 /** the reader object used to read changesets from the API */ 040 private OsmServerChangesetReader reader; 041 /** the set of downloaded changesets */ 042 private Set<Changeset> downloadedChangesets; 043 044 /** 045 * Initialize the task with a collection of changeset ids to download 046 * 047 * @param ids the collection of ids. May be null. 048 */ 049 protected void init(Collection<Integer> ids) { 050 if (ids == null) { 051 ids = Collections.emptyList(); 052 } 053 for (Integer id: ids) { 054 if (id == null || id <= 0) { 055 continue; 056 } 057 toDownload.add(id); 058 } 059 downloadedChangesets = new HashSet<>(); 060 } 061 062 /** 063 * Creates a download task for a single changeset 064 * 065 * @param changesetId the changeset id. > 0 required. 066 * @throws IllegalArgumentException if changesetId <= 0 067 */ 068 public ChangesetContentDownloadTask(int changesetId) { 069 super(tr("Downloading changeset content"), false /* don't ignore exceptions */); 070 if (changesetId <= 0) 071 throw new IllegalArgumentException( 072 MessageFormat.format("Expected integer value > 0 for parameter ''{0}'', got ''{1}''", "changesetId", changesetId)); 073 init(Collections.singleton(changesetId)); 074 } 075 076 /** 077 * Creates a download task for a collection of changesets. null values and id <=0 in 078 * the collection are sillently discarded. 079 * 080 * @param changesetIds the changeset ids. Empty collection assumed, if null. 081 */ 082 public ChangesetContentDownloadTask(Collection<Integer> changesetIds) { 083 super(tr("Downloading changeset content"), false /* don't ignore exceptions */); 084 init(changesetIds); 085 } 086 087 /** 088 * Creates a download task for a single changeset 089 * 090 * @param parent the parent component for the {@link org.openstreetmap.josm.gui.PleaseWaitDialog}. Must not be {@code null}. 091 * @param changesetId the changeset id. {@code >0} required. 092 * @throws IllegalArgumentException if {@code changesetId <= 0} 093 * @throws IllegalArgumentException if parent is {@code null} 094 */ 095 public ChangesetContentDownloadTask(Component parent, int changesetId) { 096 super(parent, tr("Downloading changeset content"), false /* don't ignore exceptions */); 097 if (changesetId <= 0) 098 throw new IllegalArgumentException( 099 MessageFormat.format("Expected integer value > 0 for parameter ''{0}'', got ''{1}''", "changesetId", changesetId)); 100 init(Collections.singleton(changesetId)); 101 } 102 103 /** 104 * Creates a download task for a collection of changesets. null values and id <=0 in 105 * the collection are sillently discarded. 106 * 107 * @param parent the parent component for the {@link org.openstreetmap.josm.gui.PleaseWaitDialog}. Must not be {@code null}. 108 * @param changesetIds the changeset ids. Empty collection assumed, if {@code null}. 109 * @throws IllegalArgumentException if parent is {@code null} 110 */ 111 public ChangesetContentDownloadTask(Component parent, Collection<Integer> changesetIds) { 112 super(parent, tr("Downloading changeset content"), false /* don't ignore exceptions */); 113 init(changesetIds); 114 } 115 116 /** 117 * Replies true if the local {@link ChangesetCache} already includes the changeset with 118 * id <code>changesetId</code>. 119 * 120 * @param changesetId the changeset id 121 * @return true if the local {@link ChangesetCache} already includes the changeset with 122 * id <code>changesetId</code> 123 */ 124 protected boolean isAvailableLocally(int changesetId) { 125 return ChangesetCache.getInstance().get(changesetId) != null; 126 } 127 128 /** 129 * Downloads the changeset with id <code>changesetId</code> (only "header" 130 * information, no content) 131 * 132 * @param changesetId the changeset id 133 * @throws OsmTransferException if something went wrong 134 */ 135 protected void downloadChangeset(int changesetId) throws OsmTransferException { 136 synchronized (this) { 137 reader = new OsmServerChangesetReader(); 138 } 139 Changeset cs = reader.readChangeset(changesetId, false, getProgressMonitor().createSubTaskMonitor(0, false)); 140 synchronized (this) { 141 reader = null; 142 } 143 ChangesetCache.getInstance().update(cs); 144 } 145 146 @Override 147 protected void cancel() { 148 canceled = true; 149 synchronized (this) { 150 if (reader != null) { 151 reader.cancel(); 152 } 153 } 154 } 155 156 @Override 157 protected void finish() { 158 if (canceled) return; 159 if (lastException != null) { 160 ExceptionDialogUtil.explainException(lastException); 161 } 162 } 163 164 @Override 165 protected void realRun() throws SAXException, IOException, OsmTransferException { 166 try { 167 getProgressMonitor().setTicksCount(toDownload.size()); 168 int i = 0; 169 for (int id: toDownload) { 170 i++; 171 if (!isAvailableLocally(id)) { 172 getProgressMonitor().setCustomText(tr("({0}/{1}) Downloading changeset {2}...", i, toDownload.size(), id)); 173 downloadChangeset(id); 174 } 175 if (canceled) return; 176 synchronized (this) { 177 reader = new OsmServerChangesetReader(); 178 } 179 getProgressMonitor().setCustomText(tr("({0}/{1}) Downloading content for changeset {2}...", i, toDownload.size(), id)); 180 ChangesetDataSet ds = reader.downloadChangeset(id, getProgressMonitor().createSubTaskMonitor(0, false)); 181 synchronized (this) { 182 reader = null; 183 } 184 Changeset cs = ChangesetCache.getInstance().get(id); 185 cs.setContent(ds); 186 ChangesetCache.getInstance().update(cs); 187 downloadedChangesets.add(cs); 188 getProgressMonitor().worked(1); 189 } 190 } catch (OsmTransferCanceledException e) { 191 // the download was canceled by the user. This exception is caught if the 192 // user canceled the authentication dialog. 193 // 194 canceled = true; 195 return; 196 } catch (OsmTransferException e) { 197 if (canceled) 198 return; 199 lastException = e; 200 } 201 } 202 203 /* ------------------------------------------------------------------------------- */ 204 /* interface ChangesetDownloadTask */ 205 /* ------------------------------------------------------------------------------- */ 206 @Override 207 public Set<Changeset> getDownloadedChangesets() { 208 return downloadedChangesets; 209 } 210 211 @Override 212 public boolean isCanceled() { 213 return canceled; 214 } 215 216 @Override 217 public boolean isFailed() { 218 return lastException != null; 219 } 220}