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
052    private DownloadTask downloadTask;
053    private NoteLayer noteLayer;
054
055    /**
056     * Download a specific note by its id.
057     * @param id Note identifier
058     * @param progressMonitor progress monitor
059     * @return the future representing the asynchronous task
060     */
061    public Future<?> download(long id, ProgressMonitor progressMonitor) {
062        final String url = OsmApi.getOsmApi().getBaseUrl() + "notes/" + id;
063        downloadTask = new DownloadRawUrlTask(new OsmServerLocationReader(url), progressMonitor);
064        return MainApplication.worker.submit(downloadTask);
065    }
066
067    @Override
068    public Future<?> download(DownloadParams settings, Bounds downloadArea, ProgressMonitor progressMonitor) {
069        downloadTask = new DownloadBoundingBoxTask(new BoundingBoxDownloader(downloadArea), progressMonitor);
070        return MainApplication.worker.submit(downloadTask);
071    }
072
073    @Override
074    public Future<?> loadUrl(DownloadParams settings, String url, ProgressMonitor progressMonitor) {
075        if (url.matches(PATTERN_COMPRESS)) {
076            downloadTask = new DownloadCompressedRawUrlTask(new OsmServerLocationReader(url), progressMonitor, Compression.byExtension(url));
077        } else {
078            downloadTask = new DownloadRawUrlTask(new OsmServerLocationReader(url), progressMonitor);
079        }
080        return MainApplication.worker.submit(downloadTask);
081    }
082
083    @Override
084    public void cancel() {
085        if (downloadTask != null) {
086            downloadTask.cancel();
087        }
088    }
089
090    @Override
091    public String getConfirmationMessage(URL url) {
092        return null;
093    }
094
095    @Override
096    public String getTitle() {
097        return tr("Download OSM Notes");
098    }
099
100    @Override
101    public String[] getPatterns() {
102        return Arrays.stream(NoteUrlPattern.values()).map(NoteUrlPattern::pattern).toArray(String[]::new);
103    }
104
105    @Override
106    public boolean isSafeForRemotecontrolRequests() {
107        return true;
108    }
109
110    @Override
111    public ProjectionBounds getDownloadProjectionBounds() {
112        return noteLayer != null ? noteLayer.getViewProjectionBounds() : null;
113    }
114
115    abstract class DownloadTask extends PleaseWaitRunnable {
116        protected OsmServerReader reader;
117        protected List<Note> notesData;
118
119        DownloadTask(OsmServerReader reader, ProgressMonitor progressMonitor) {
120            super(tr("Downloading notes"), progressMonitor, false);
121            this.reader = reader;
122        }
123
124        @Override
125        protected void finish() {
126            rememberDownloadedData(new NoteData(notesData));
127            if (isCanceled() || isFailed() || notesData == null || notesData.isEmpty()) {
128                return;
129            }
130            if (Logging.isDebugEnabled()) {
131                Logging.debug("Notes downloaded: {0}", notesData.size());
132            }
133
134            noteLayer = new NoteLayer(notesData, tr("Notes"));
135            NoteLayer l = MainApplication.getLayerManager().getNoteLayer();
136            if (l != null) {
137                l.mergeFrom(noteLayer);
138                MapFrame map = MainApplication.getMap();
139                if (map != null && zoomAfterDownload) {
140                    map.mapView.scheduleZoomTo(new ViewportData(noteLayer.getViewProjectionBounds()));
141                }
142            } else {
143                MainApplication.getLayerManager().addLayer(noteLayer, zoomAfterDownload);
144            }
145        }
146
147        @Override
148        protected void cancel() {
149            setCanceled(true);
150            if (reader != null) {
151                reader.cancel();
152            }
153        }
154
155        @Override
156        public abstract void realRun() throws IOException, SAXException, OsmTransferException;
157    }
158
159    class DownloadBoundingBoxTask extends DownloadTask {
160
161        DownloadBoundingBoxTask(OsmServerReader reader, ProgressMonitor progressMonitor) {
162            super(reader, progressMonitor);
163        }
164
165        @Override
166        public void realRun() throws IOException, SAXException, OsmTransferException {
167            if (isCanceled()) {
168                return;
169            }
170            ProgressMonitor subMonitor = progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false);
171            try {
172                notesData = reader.parseNotes(DOWNLOAD_LIMIT.get(), DAYS_CLOSED.get(), subMonitor);
173            } catch (MoreNotesException e) {
174                Logging.debug(e);
175                notesData = e.notes;
176                JOptionPane.showMessageDialog(MainApplication.getMainFrame(), "<html>"
177                                + trn("{0} note has been downloaded.", "{0} notes have been downloaded.", e.limit, e.limit)
178                                + "<br>"
179                                + tr("Since the download limit was {0}, there might be more notes to download.", e.limit)
180                                + "<br>"
181                                + tr("Request a smaller area to make sure that all notes are being downloaded.")
182                                + "</html>",
183                        tr("More notes to download"), JOptionPane.INFORMATION_MESSAGE);
184            } catch (OsmTransferException e) {
185                if (isCanceled())
186                    return;
187                rememberException(e);
188            }
189        }
190    }
191
192    class DownloadRawUrlTask extends DownloadTask {
193
194        DownloadRawUrlTask(OsmServerReader reader, ProgressMonitor progressMonitor) {
195            super(reader, progressMonitor);
196        }
197
198        @Override
199        public void realRun() throws IOException, SAXException, OsmTransferException {
200            if (isCanceled()) {
201                return;
202            }
203            ProgressMonitor subMonitor = progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false);
204            try {
205                notesData = reader.parseRawNotes(subMonitor);
206            } catch (OsmTransferException e) {
207                if (isCanceled())
208                    return;
209                rememberException(e);
210            }
211        }
212    }
213
214    class DownloadCompressedRawUrlTask extends DownloadTask {
215
216        private final Compression compression;
217
218        DownloadCompressedRawUrlTask(OsmServerReader reader, ProgressMonitor progressMonitor, Compression compression) {
219            super(reader, progressMonitor);
220            this.compression = compression;
221        }
222
223        @Override
224        public void realRun() throws IOException, SAXException, OsmTransferException {
225            if (isCanceled()) {
226                return;
227            }
228            ProgressMonitor subMonitor = progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false);
229            try {
230                notesData = reader.parseRawNotes(subMonitor, compression);
231            } catch (OsmTransferException e) {
232                if (isCanceled())
233                    return;
234                rememberException(e);
235            }
236        }
237    }
238}