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