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