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. &gt; 0 required.
066     * @throws IllegalArgumentException if changesetId &lt;= 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 &lt;=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 &lt;=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}