001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.actions;
003
004import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
005import static org.openstreetmap.josm.tools.I18n.tr;
006
007import java.awt.event.ActionEvent;
008import java.io.File;
009import java.io.IOException;
010import java.io.InputStream;
011import java.net.URI;
012import java.nio.file.Files;
013import java.nio.file.StandardCopyOption;
014import java.util.Arrays;
015import java.util.List;
016
017import javax.swing.JFileChooser;
018import javax.swing.JOptionPane;
019import javax.swing.SwingUtilities;
020
021import org.openstreetmap.josm.data.projection.ProjectionRegistry;
022import org.openstreetmap.josm.gui.HelpAwareOptionPane;
023import org.openstreetmap.josm.gui.MainApplication;
024import org.openstreetmap.josm.gui.Notification;
025import org.openstreetmap.josm.gui.PleaseWaitRunnable;
026import org.openstreetmap.josm.gui.layer.Layer;
027import org.openstreetmap.josm.gui.preferences.projection.ProjectionPreference;
028import org.openstreetmap.josm.gui.progress.ProgressMonitor;
029import org.openstreetmap.josm.gui.util.FileFilterAllFiles;
030import org.openstreetmap.josm.gui.widgets.AbstractFileChooser;
031import org.openstreetmap.josm.io.IllegalDataException;
032import org.openstreetmap.josm.io.session.SessionImporter;
033import org.openstreetmap.josm.io.session.SessionReader;
034import org.openstreetmap.josm.io.session.SessionReader.SessionProjectionChoiceData;
035import org.openstreetmap.josm.io.session.SessionReader.SessionViewportData;
036import org.openstreetmap.josm.tools.CheckParameterUtil;
037import org.openstreetmap.josm.tools.JosmRuntimeException;
038import org.openstreetmap.josm.tools.Logging;
039import org.openstreetmap.josm.tools.Utils;
040import org.openstreetmap.josm.tools.bugreport.ReportedException;
041
042/**
043 * Loads a JOSM session.
044 * @since 4668
045 */
046public class SessionLoadAction extends DiskAccessAction {
047
048    /**
049     * Constructs a new {@code SessionLoadAction}.
050     */
051    public SessionLoadAction() {
052        super(tr("Load Session"), "open", tr("Load a session from file."), null, true, "load-session", true);
053        setHelpId(ht("/Action/SessionLoad"));
054    }
055
056    @Override
057    public void actionPerformed(ActionEvent e) {
058        AbstractFileChooser fc = createAndOpenFileChooser(true, false, tr("Open session"),
059                Arrays.asList(SessionImporter.FILE_FILTER, FileFilterAllFiles.getInstance()),
060                SessionImporter.FILE_FILTER, JFileChooser.FILES_ONLY, "lastDirectory");
061        if (fc == null)
062            return;
063        File file = fc.getSelectedFile();
064        boolean zip = Utils.hasExtension(file, "joz");
065        MainApplication.worker.submit(new Loader(file, zip));
066    }
067
068    /**
069     * JOSM session loader
070     */
071    public static class Loader extends PleaseWaitRunnable {
072
073        private boolean canceled;
074        private File file;
075        private final URI uri;
076        private final InputStream is;
077        private final boolean zip;
078        private List<Layer> layers;
079        private Layer active;
080        private List<Runnable> postLoadTasks;
081        private SessionViewportData viewport;
082        private SessionProjectionChoiceData projectionChoice;
083
084        /**
085         * Constructs a new {@code Loader} for local session file.
086         * @param file The JOSM session file
087         * @param zip {@code true} if the file is a session archive file (*.joz)
088         */
089        public Loader(File file, boolean zip) {
090            super(tr("Loading session ''{0}''", file.getName()));
091            CheckParameterUtil.ensureParameterNotNull(file, "file");
092            this.file = file;
093            this.uri = null;
094            this.is = null;
095            this.zip = zip;
096        }
097
098        /**
099         * Constructs a new {@code Loader} for session file input stream (may be a remote file).
100         * @param is The input stream to session file
101         * @param uri The file URI
102         * @param zip {@code true} if the file is a session archive file (*.joz)
103         * @since 6245
104         */
105        public Loader(InputStream is, URI uri, boolean zip) {
106            super(tr("Loading session ''{0}''", uri));
107            CheckParameterUtil.ensureParameterNotNull(is, "is");
108            CheckParameterUtil.ensureParameterNotNull(uri, "uri");
109            this.file = null;
110            this.uri = uri;
111            this.is = is;
112            this.zip = zip;
113        }
114
115        @Override
116        public void cancel() {
117            canceled = true;
118        }
119
120        @Override
121        protected void finish() {
122            SwingUtilities.invokeLater(() -> {
123                if (canceled)
124                    return;
125                if (projectionChoice != null) {
126                    ProjectionPreference.setProjection(
127                            projectionChoice.getProjectionChoiceId(),
128                            projectionChoice.getSubPreferences(),
129                            false);
130                }
131                addLayers();
132                runPostLoadTasks();
133            });
134        }
135
136        private void addLayers() {
137            if (layers != null && !layers.isEmpty()) {
138                boolean noMap = MainApplication.getMap() == null;
139                for (Layer l : layers) {
140                    if (canceled)
141                        return;
142                    addLayer(l);
143                }
144                if (active != null) {
145                    MainApplication.getLayerManager().setActiveLayer(active);
146                }
147                if (noMap && viewport != null) {
148                    MainApplication.getMap().mapView.scheduleZoomTo(viewport.getEastNorthViewport(ProjectionRegistry.getProjection()));
149                }
150            }
151        }
152
153        /**
154         * Tries to add a new layer.
155         * @param l layer to add
156         * @return {@code true} if layer has been added, {@code false} if it wasn't needed or if an error occurred
157         */
158        static boolean addLayer(Layer l) {
159            // NoteImporter directly loads notes into current note layer
160            if (!MainApplication.getLayerManager().containsLayer(l)) {
161                try {
162                    MainApplication.getLayerManager().addLayer(l);
163                } catch (ReportedException e) {
164                    Logging.error(e);
165                    new Notification(tr("Unable to add layer ''{0}'': {1}", l.getName(), e.getMessage()))
166                        .setIcon(JOptionPane.ERROR_MESSAGE).setDuration(Notification.TIME_LONG).show();
167                    if (MainApplication.getLayerManager().containsLayer(l)) {
168                        MainApplication.getLayerManager().removeLayer(l);
169                    }
170                    return false;
171                }
172            }
173            return true;
174        }
175
176        private void runPostLoadTasks() {
177            if (postLoadTasks != null) {
178                for (Runnable task : postLoadTasks) {
179                    if (canceled)
180                        return;
181                    if (task == null) {
182                        continue;
183                    }
184                    task.run();
185                }
186            }
187        }
188
189        @Override
190        protected void realRun() {
191            try {
192                ProgressMonitor monitor = getProgressMonitor();
193                SessionReader reader = new SessionReader();
194                boolean tempFile = false;
195                try {
196                    if (file == null) {
197                        // Download and write entire joz file as a temp file on disk as we need random access later
198                        file = File.createTempFile("session_", ".joz", Utils.getJosmTempDir());
199                        tempFile = true;
200                        Files.copy(is, file.toPath(), StandardCopyOption.REPLACE_EXISTING);
201                    }
202                    reader.loadSession(file, zip, monitor);
203                    layers = reader.getLayers();
204                    active = reader.getActive();
205                    postLoadTasks = reader.getPostLoadTasks();
206                    viewport = reader.getViewport();
207                    projectionChoice = reader.getProjectionChoice();
208                } finally {
209                    if (tempFile) {
210                        Utils.deleteFile(file);
211                        file = null;
212                    }
213                }
214            } catch (IllegalDataException e) {
215                handleException(tr("Data Error"), e);
216            } catch (IOException e) {
217                handleException(tr("IO Error"), e);
218            } catch (JosmRuntimeException | IllegalArgumentException | IllegalStateException e) {
219                cancel();
220                throw e;
221            }
222        }
223
224        private void handleException(String dialogTitle, Exception e) {
225            Logging.error(e);
226            HelpAwareOptionPane.showMessageDialogInEDT(
227                    MainApplication.getMainFrame(),
228                    tr("<html>Could not load session file ''{0}''.<br>Error is:<br>{1}</html>",
229                            uri != null ? uri : file.getName(), Utils.escapeReservedCharactersHTML(e.getMessage())),
230                    dialogTitle,
231                    JOptionPane.ERROR_MESSAGE,
232                    null
233                    );
234            cancel();
235        }
236    }
237}