001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.actions.upload;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.io.IOException;
007import java.util.HashMap;
008import java.util.Map;
009import java.util.stream.Collectors;
010
011import javax.swing.JOptionPane;
012
013import org.openstreetmap.josm.data.notes.Note;
014import org.openstreetmap.josm.data.notes.NoteComment;
015import org.openstreetmap.josm.data.osm.NoteData;
016import org.openstreetmap.josm.gui.ExceptionDialogUtil;
017import org.openstreetmap.josm.gui.MainApplication;
018import org.openstreetmap.josm.gui.PleaseWaitRunnable;
019import org.openstreetmap.josm.gui.progress.ProgressMonitor;
020import org.openstreetmap.josm.io.OsmApi;
021import org.openstreetmap.josm.io.OsmTransferCanceledException;
022import org.openstreetmap.josm.io.OsmTransferException;
023import org.openstreetmap.josm.tools.Logging;
024import org.xml.sax.SAXException;
025
026/**
027 * Class for uploading note changes to the server
028 */
029public class UploadNotesTask {
030
031    private NoteData noteData;
032
033    /**
034     * Upload notes with modifications to the server
035     * @param noteData Note dataset with changes to upload
036     * @param progressMonitor progress monitor for user feedback
037     */
038    public void uploadNotes(NoteData noteData, ProgressMonitor progressMonitor) {
039        this.noteData = noteData;
040        MainApplication.worker.submit(new UploadTask(tr("Uploading modified notes"), progressMonitor));
041    }
042
043    private class UploadTask extends PleaseWaitRunnable {
044
045        private boolean isCanceled;
046        private final Map<Note, Note> updatedNotes = new HashMap<>();
047        private final Map<Note, Exception> failedNotes = new HashMap<>();
048
049        /**
050         * Constructs a new {@code UploadTask}.
051         * @param title message for the user
052         * @param monitor progress monitor
053         */
054        UploadTask(String title, ProgressMonitor monitor) {
055            super(title, monitor, false);
056        }
057
058        @Override
059        protected void cancel() {
060            Logging.debug("note upload canceled");
061            isCanceled = true;
062        }
063
064        @Override
065        protected void realRun() throws SAXException, IOException, OsmTransferException {
066            ProgressMonitor monitor = progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false);
067            OsmApi api = OsmApi.getOsmApi();
068            for (Note note : noteData.getNotes()) {
069                if (isCanceled) {
070                    Logging.info("Note upload interrupted by user");
071                    break;
072                }
073                for (NoteComment comment : note.getComments()) {
074                    if (comment.isNew()) {
075                        Logging.debug("found note change to upload");
076                        processNoteComment(monitor, api, note, comment);
077                    }
078                }
079            }
080        }
081
082        private void processNoteComment(ProgressMonitor monitor, OsmApi api, Note note, NoteComment comment) {
083            try {
084                if (updatedNotes.containsKey(note)) {
085                    // if note has been created earlier in this task, obtain its real id and not use the placeholder id
086                    note = updatedNotes.get(note);
087                }
088                Note newNote;
089                switch (comment.getNoteAction()) {
090                case OPENED:
091                    Logging.debug("opening new note");
092                    newNote = api.createNote(note.getLatLon(), comment.getText(), monitor);
093                    break;
094                case CLOSED:
095                    Logging.debug("closing note {0}", note.getId());
096                    newNote = api.closeNote(note, comment.getText(), monitor);
097                    break;
098                case COMMENTED:
099                    Logging.debug("adding comment to note {0}", note.getId());
100                    newNote = api.addCommentToNote(note, comment.getText(), monitor);
101                    break;
102                case REOPENED:
103                    Logging.debug("reopening note {0}", note.getId());
104                    newNote = api.reopenNote(note, comment.getText(), monitor);
105                    break;
106                default:
107                    newNote = null;
108                }
109                updatedNotes.put(note, newNote);
110            } catch (OsmTransferException e) {
111                Logging.error("Failed to upload note to server: {0}", note.getId());
112                Logging.error(e);
113                if (!(e instanceof OsmTransferCanceledException)) {
114                    failedNotes.put(note, e);
115                }
116            }
117        }
118
119        /** Updates the note layer with uploaded notes and notifies the user of any upload failures */
120        @Override
121        protected void finish() {
122            if (Logging.isDebugEnabled()) {
123                Logging.debug("finish called in notes upload task. Notes to update: {0}", updatedNotes.size());
124            }
125            noteData.updateNotes(updatedNotes);
126            if (!failedNotes.isEmpty()) {
127                String message = failedNotes.entrySet().stream()
128                        .map(entry -> tr("Note {0} failed: {1}", entry.getKey().getId(), entry.getValue().getMessage()))
129                        .collect(Collectors.joining("\n"));
130                Logging.error("Notes failed to upload: " + message);
131                JOptionPane.showMessageDialog(MainApplication.getMap(), message,
132                        tr("Notes failed to upload"), JOptionPane.ERROR_MESSAGE);
133                ExceptionDialogUtil.explainException(failedNotes.values().iterator().next());
134            }
135        }
136    }
137}