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