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