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