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 thrown if changesetId &lt;= 0
067     */
068    public ChangesetContentDownloadTask(int changesetId) throws IllegalArgumentException{
069        super(tr("Downloading changeset content"), false /* don't ignore exceptions */);
070        if (changesetId <= 0)
071            throw new IllegalArgumentException(MessageFormat.format("Expected integer value > 0 for parameter ''{0}'', got ''{1}''", "changesetId", changesetId));
072        init(Collections.singleton(changesetId));
073    }
074
075    /**
076     * Creates a download task for a collection of changesets. null values and id &lt;=0 in
077     * the collection are sillently discarded.
078     *
079     * @param changesetIds the changeset ids. Empty collection assumed, if null.
080     */
081    public ChangesetContentDownloadTask(Collection<Integer> changesetIds) {
082        super(tr("Downloading changeset content"), false /* don't ignore exceptions */);
083        init(changesetIds);
084    }
085
086    /**
087     * Creates a download task for a single changeset
088     *
089     * @param parent the parent component for the {@link org.openstreetmap.josm.gui.PleaseWaitDialog}. Must not be {@code null}.
090     * @param changesetId the changeset id. {@code >0} required.
091     * @throws IllegalArgumentException thrown if {@code changesetId <= 0}
092     * @throws IllegalArgumentException thrown if parent is {@code null}
093     */
094    public ChangesetContentDownloadTask(Component parent, int changesetId) throws IllegalArgumentException{
095        super(parent, tr("Downloading changeset content"), false /* don't ignore exceptions */);
096        if (changesetId <= 0)
097            throw new IllegalArgumentException(MessageFormat.format("Expected integer value > 0 for parameter ''{0}'', got ''{1}''", "changesetId", changesetId));
098        init(Collections.singleton(changesetId));
099    }
100
101    /**
102     * Creates a download task for a collection of changesets. null values and id &lt;=0 in
103     * the collection are sillently discarded.
104     *
105     * @param parent the parent component for the {@link org.openstreetmap.josm.gui.PleaseWaitDialog}. Must not be {@code null}.
106     * @param changesetIds the changeset ids. Empty collection assumed, if {@code null}.
107     * @throws IllegalArgumentException thrown if parent is {@code null}
108     */
109    public ChangesetContentDownloadTask(Component parent, Collection<Integer> changesetIds) throws IllegalArgumentException {
110        super(parent, tr("Downloading changeset content"), false /* don't ignore exceptions */);
111        init(changesetIds);
112    }
113
114    /**
115     * Replies true if the local {@link ChangesetCache} already includes the changeset with
116     * id <code>changesetId</code>.
117     *
118     * @param changesetId the changeset id
119     * @return true if the local {@link ChangesetCache} already includes the changeset with
120     * id <code>changesetId</code>
121     */
122    protected boolean isAvailableLocally(int changesetId) {
123        return ChangesetCache.getInstance().get(changesetId) != null;
124    }
125
126    /**
127     * Downloads the changeset with id <code>changesetId</code> (only "header"
128     * information, no content)
129     *
130     * @param changesetId the changeset id
131     * @throws OsmTransferException thrown if something went wrong
132     */
133    protected void downloadChangeset(int changesetId) throws OsmTransferException {
134        synchronized(this) {
135            reader = new OsmServerChangesetReader();
136        }
137        Changeset cs = reader.readChangeset(changesetId, getProgressMonitor().createSubTaskMonitor(0, false));
138        synchronized(this) {
139            reader = null;
140        }
141        ChangesetCache.getInstance().update(cs);
142    }
143
144    @Override
145    protected void cancel() {
146        canceled = true;
147        synchronized (this) {
148            if (reader != null) {
149                reader.cancel();
150            }
151        }
152    }
153
154    @Override
155    protected void finish() {
156        if (canceled) return;
157        if (lastException != null) {
158            ExceptionDialogUtil.explainException(lastException);
159        }
160    }
161
162    @Override
163    protected void realRun() throws SAXException, IOException, OsmTransferException {
164        try {
165            getProgressMonitor().setTicksCount(toDownload.size());
166            int i=0;
167            for (int id: toDownload) {
168                i++;
169                if (!isAvailableLocally(id)) {
170                    getProgressMonitor().setCustomText(tr("({0}/{1}) Downloading changeset {2}...", i, toDownload.size(), id));
171                    downloadChangeset(id);
172                }
173                if (canceled) return;
174                synchronized(this) {
175                    reader = new OsmServerChangesetReader();
176                }
177                getProgressMonitor().setCustomText(tr("({0}/{1}) Downloading content for changeset {2}...", i, toDownload.size(), id));
178                ChangesetDataSet ds = reader.downloadChangeset(id, getProgressMonitor().createSubTaskMonitor(0, false));
179                synchronized(this) {
180                    reader = null;
181                }
182                Changeset cs = ChangesetCache.getInstance().get(id);
183                cs.setContent(ds);
184                ChangesetCache.getInstance().update(cs);
185                downloadedChangesets.add(cs);
186                getProgressMonitor().worked(1);
187            }
188        } catch(OsmTransferCanceledException e) {
189            // the download was canceled by the user. This exception is caught if the
190            // user canceled the authentication dialog.
191            //
192            canceled = true;
193            return;
194        } catch(OsmTransferException e) {
195            if (canceled)
196                return;
197            lastException = e;
198        }
199    }
200
201    /* ------------------------------------------------------------------------------- */
202    /* interface ChangesetDownloadTask                                                 */
203    /* ------------------------------------------------------------------------------- */
204    @Override
205    public Set<Changeset> getDownloadedChangesets() {
206        return downloadedChangesets;
207    }
208
209    @Override
210    public boolean isCanceled() {
211        return canceled;
212    }
213
214    @Override
215    public boolean isFailed() {
216        return lastException != null;
217    }
218}