001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.actions.downloadtasks;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005import static org.openstreetmap.josm.tools.I18n.trn;
006
007import java.io.IOException;
008import java.net.URL;
009import java.util.List;
010import java.util.concurrent.Future;
011
012import javax.swing.JOptionPane;
013
014import org.openstreetmap.josm.data.Bounds;
015import org.openstreetmap.josm.data.ProjectionBounds;
016import org.openstreetmap.josm.data.ViewportData;
017import org.openstreetmap.josm.data.notes.Note;
018import org.openstreetmap.josm.data.osm.NoteData;
019import org.openstreetmap.josm.data.preferences.IntegerProperty;
020import org.openstreetmap.josm.gui.MainApplication;
021import org.openstreetmap.josm.gui.MapFrame;
022import org.openstreetmap.josm.gui.PleaseWaitRunnable;
023import org.openstreetmap.josm.gui.layer.NoteLayer;
024import org.openstreetmap.josm.gui.progress.ProgressMonitor;
025import org.openstreetmap.josm.io.BoundingBoxDownloader;
026import org.openstreetmap.josm.io.BoundingBoxDownloader.MoreNotesException;
027import org.openstreetmap.josm.io.Compression;
028import org.openstreetmap.josm.io.OsmApi;
029import org.openstreetmap.josm.io.OsmServerLocationReader;
030import org.openstreetmap.josm.io.OsmServerReader;
031import org.openstreetmap.josm.io.OsmTransferException;
032import org.openstreetmap.josm.io.UrlPatterns.NoteUrlPattern;
033import org.openstreetmap.josm.tools.Logging;
034import org.xml.sax.SAXException;
035
036/**
037 * General task for downloading OSM notes.
038 * <p>
039 * It handles two URL patterns: API call and dump file export.
040 * @since 7531
041 */
042public class DownloadNotesTask extends AbstractDownloadTask<NoteData> {
043
044    /** Property defining the number of notes to be downloaded */
045    public static final IntegerProperty DOWNLOAD_LIMIT = new IntegerProperty("osm.notes.downloadLimit", 1000);
046    /** Property defining number of days a bug needs to be closed to no longer be downloaded */
047    public static final IntegerProperty DAYS_CLOSED = new IntegerProperty("osm.notes.daysClosed", 7);
048
049    private static final String PATTERN_COMPRESS = "https?://.*/(.*\\.osn.(gz|xz|bz2?|zip))";
050    private static final String NO_NOTES_FOUND = tr("No notes found in this area.");
051    static {
052        PostDownloadHandler.addNoDataErrorMessage(NO_NOTES_FOUND);
053    }
054
055    private DownloadTask downloadTask;
056    private NoteLayer noteLayer;
057
058    /** This allows subclasses to ignore this warning */
059    protected boolean warnAboutEmptyArea = true;
060
061    /**
062     * Download a specific note by its id.
063     * @param id Note identifier
064     * @param progressMonitor progress monitor
065     * @return the future representing the asynchronous task
066     */
067    public Future<?> download(long id, ProgressMonitor progressMonitor) {
068        final String url = OsmApi.getOsmApi().getBaseUrl() + "notes/" + id;
069        downloadTask = new DownloadRawUrlTask(new OsmServerLocationReader(url), progressMonitor);
070        return MainApplication.worker.submit(downloadTask);
071    }
072
073    @Override
074    public Future<?> download(DownloadParams settings, Bounds downloadArea, ProgressMonitor progressMonitor) {
075        downloadTask = new DownloadBoundingBoxTask(new BoundingBoxDownloader(downloadArea), progressMonitor);
076        return MainApplication.worker.submit(downloadTask);
077    }
078
079    @Override
080    public Future<?> loadUrl(DownloadParams settings, String url, ProgressMonitor progressMonitor) {
081        if (url.matches(PATTERN_COMPRESS)) {
082            downloadTask = new DownloadCompressedRawUrlTask(new OsmServerLocationReader(url), progressMonitor, Compression.byExtension(url));
083        } else {
084            downloadTask = new DownloadRawUrlTask(new OsmServerLocationReader(url), progressMonitor);
085        }
086        return MainApplication.worker.submit(downloadTask);
087    }
088
089    @Override
090    public void cancel() {
091        if (downloadTask != null) {
092            downloadTask.cancel();
093        }
094    }
095
096    @Override
097    public String getConfirmationMessage(URL url) {
098        return null;
099    }
100
101    @Override
102    public String getTitle() {
103        return tr("Download OSM Notes");
104    }
105
106    @Override
107    public String[] getPatterns() {
108        return patterns(NoteUrlPattern.class);
109    }
110
111    @Override
112    public boolean isSafeForRemotecontrolRequests() {
113        return true;
114    }
115
116    @Override
117    public ProjectionBounds getDownloadProjectionBounds() {
118        return noteLayer != null ? noteLayer.getViewProjectionBounds() : null;
119    }
120
121    abstract class DownloadTask extends PleaseWaitRunnable {
122        protected OsmServerReader reader;
123        protected List<Note> notesData;
124
125        DownloadTask(OsmServerReader reader, ProgressMonitor progressMonitor) {
126            super(tr("Downloading notes"), progressMonitor, false);
127            this.reader = reader;
128        }
129
130        @Override
131        protected void finish() {
132            rememberDownloadedData(new NoteData(notesData));
133            if (isCanceled() || isFailed() || notesData == null) {
134                return;
135            }
136            if (notesData.isEmpty()) {
137                if (warnAboutEmptyArea) {
138                    rememberErrorMessage(NO_NOTES_FOUND);
139                }
140                return;
141            }
142            if (Logging.isDebugEnabled()) {
143                Logging.debug("Notes downloaded: {0}", notesData.size());
144            }
145
146            noteLayer = new NoteLayer(notesData, tr("Notes"));
147            NoteLayer l = MainApplication.getLayerManager().getNoteLayer();
148            if (l != null) {
149                l.mergeFrom(noteLayer);
150                MapFrame map = MainApplication.getMap();
151                if (map != null && zoomAfterDownload) {
152                    map.mapView.scheduleZoomTo(new ViewportData(noteLayer.getViewProjectionBounds()));
153                }
154            } else {
155                MainApplication.getLayerManager().addLayer(noteLayer, zoomAfterDownload);
156            }
157        }
158
159        @Override
160        protected void cancel() {
161            setCanceled(true);
162            if (reader != null) {
163                reader.cancel();
164            }
165        }
166
167        @Override
168        public abstract void realRun() throws IOException, SAXException, OsmTransferException;
169    }
170
171    class DownloadBoundingBoxTask extends DownloadTask {
172
173        DownloadBoundingBoxTask(OsmServerReader reader, ProgressMonitor progressMonitor) {
174            super(reader, progressMonitor);
175        }
176
177        @Override
178        public void realRun() throws IOException, SAXException, OsmTransferException {
179            if (isCanceled()) {
180                return;
181            }
182            ProgressMonitor subMonitor = progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false);
183            try {
184                notesData = reader.parseNotes(DOWNLOAD_LIMIT.get(), DAYS_CLOSED.get(), subMonitor);
185            } catch (MoreNotesException e) {
186                Logging.debug(e);
187                notesData = e.notes;
188                JOptionPane.showMessageDialog(MainApplication.getMainFrame(), "<html>"
189                                + trn("{0} note has been downloaded.", "{0} notes have been downloaded.", e.limit, e.limit)
190                                + "<br>"
191                                + tr("Since the download limit was {0}, there might be more notes to download.", e.limit)
192                                + "<br>"
193                                + tr("Request a smaller area to make sure that all notes are being downloaded.")
194                                + "</html>",
195                        tr("More notes to download"), JOptionPane.INFORMATION_MESSAGE);
196            } catch (OsmTransferException e) {
197                if (isCanceled())
198                    return;
199                rememberException(e);
200            }
201        }
202    }
203
204    class DownloadRawUrlTask extends DownloadTask {
205
206        DownloadRawUrlTask(OsmServerReader reader, ProgressMonitor progressMonitor) {
207            super(reader, progressMonitor);
208        }
209
210        @Override
211        public void realRun() throws IOException, SAXException, OsmTransferException {
212            if (isCanceled()) {
213                return;
214            }
215            ProgressMonitor subMonitor = progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false);
216            try {
217                notesData = reader.parseRawNotes(subMonitor);
218            } catch (OsmTransferException e) {
219                if (isCanceled())
220                    return;
221                rememberException(e);
222            }
223        }
224    }
225
226    class DownloadCompressedRawUrlTask extends DownloadTask {
227
228        private final Compression compression;
229
230        DownloadCompressedRawUrlTask(OsmServerReader reader, ProgressMonitor progressMonitor, Compression compression) {
231            super(reader, progressMonitor);
232            this.compression = compression;
233        }
234
235        @Override
236        public void realRun() throws IOException, SAXException, OsmTransferException {
237            if (isCanceled()) {
238                return;
239            }
240            ProgressMonitor subMonitor = progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false);
241            try {
242                notesData = reader.parseRawNotes(subMonitor, compression);
243            } catch (OsmTransferException e) {
244                if (isCanceled())
245                    return;
246                rememberException(e);
247            }
248        }
249    }
250}