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