001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.io;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005import static org.openstreetmap.josm.tools.I18n.trn;
006
007import java.awt.GridBagLayout;
008import java.net.Authenticator.RequestorType;
009import java.util.concurrent.Executors;
010import java.util.concurrent.ScheduledExecutorService;
011import java.util.concurrent.ScheduledFuture;
012import java.util.concurrent.TimeUnit;
013
014import javax.swing.JLabel;
015import javax.swing.JOptionPane;
016import javax.swing.JPanel;
017
018import org.openstreetmap.josm.Main;
019import org.openstreetmap.josm.data.osm.UserInfo;
020import org.openstreetmap.josm.data.preferences.BooleanProperty;
021import org.openstreetmap.josm.data.preferences.IntegerProperty;
022import org.openstreetmap.josm.gui.JosmUserIdentityManager;
023import org.openstreetmap.josm.gui.Notification;
024import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
025import org.openstreetmap.josm.gui.util.GuiHelper;
026import org.openstreetmap.josm.gui.widgets.UrlLabel;
027import org.openstreetmap.josm.io.auth.CredentialsAgentException;
028import org.openstreetmap.josm.io.auth.CredentialsAgentResponse;
029import org.openstreetmap.josm.io.auth.CredentialsManager;
030import org.openstreetmap.josm.io.auth.JosmPreferencesCredentialAgent;
031import org.openstreetmap.josm.tools.GBC;
032import org.openstreetmap.josm.tools.Utils;
033
034/**
035 * Notifies user periodically of new received (unread) messages
036 * @since 6349
037 */
038public final class MessageNotifier {
039
040    private MessageNotifier() {
041        // Hide default constructor for utils classes
042    }
043
044    /** Property defining if this task is enabled or not */
045    public static final BooleanProperty PROP_NOTIFIER_ENABLED = new BooleanProperty("message.notifier.enabled", true);
046    /** Property defining the update interval in minutes */
047    public static final IntegerProperty PROP_INTERVAL = new IntegerProperty("message.notifier.interval", 5);
048
049    private static final ScheduledExecutorService EXECUTOR =
050            Executors.newSingleThreadScheduledExecutor(Utils.newThreadFactory("message-notifier-%d", Thread.NORM_PRIORITY));
051
052    private static final Runnable WORKER = new Worker();
053
054    private static volatile ScheduledFuture<?> task;
055
056    private static class Worker implements Runnable {
057
058        private int lastUnreadCount;
059
060        @Override
061        public void run() {
062            try {
063                final UserInfo userInfo = new OsmServerUserInfoReader().fetchUserInfo(NullProgressMonitor.INSTANCE,
064                        tr("get number of unread messages"));
065                final int unread = userInfo.getUnreadMessages();
066                if (unread > 0 && unread != lastUnreadCount) {
067                    GuiHelper.runInEDT(new Runnable() {
068                        @Override
069                        public void run() {
070                            JPanel panel = new JPanel(new GridBagLayout());
071                            panel.add(new JLabel(trn("You have {0} unread message.", "You have {0} unread messages.", unread, unread)),
072                                    GBC.eol());
073                            panel.add(new UrlLabel(Main.getBaseUserUrl() + '/' + userInfo.getDisplayName() + "/inbox",
074                                    tr("Click here to see your inbox.")), GBC.eol());
075                            panel.setOpaque(false);
076                            new Notification().setContent(panel)
077                                .setIcon(JOptionPane.INFORMATION_MESSAGE)
078                                .setDuration(Notification.TIME_LONG)
079                                .show();
080                        }
081                    });
082                    lastUnreadCount = unread;
083                }
084            } catch (OsmTransferException e) {
085                Main.warn(e);
086            }
087        }
088    }
089
090    /**
091     * Starts the message notifier task if not already started and if user is fully identified
092     */
093    public static void start() {
094        int interval = PROP_INTERVAL.get();
095        if (Main.isOffline(OnlineResource.OSM_API)) {
096            Main.info(tr("{0} not available (offline mode)", tr("Message notifier")));
097        } else if (!isRunning() && interval > 0 && isUserEnoughIdentified()) {
098            task = EXECUTOR.scheduleAtFixedRate(WORKER, 0, interval * 60L, TimeUnit.SECONDS);
099            Main.info("Message notifier active (checks every "+interval+" minute"+(interval > 1 ? "s" : "")+')');
100        }
101    }
102
103    /**
104     * Stops the message notifier task if started
105     */
106    public static void stop() {
107        if (isRunning()) {
108            task.cancel(false);
109            Main.info("Message notifier inactive");
110            task = null;
111        }
112    }
113
114    /**
115     * Determines if the message notifier is currently running
116     * @return {@code true} if the notifier is running, {@code false} otherwise
117     */
118    public static boolean isRunning() {
119        return task != null;
120    }
121
122    /**
123     * Determines if user set enough information in JOSM preferences to make the request to OSM API without
124     * prompting him for a password.
125     * @return {@code true} if user chose an OAuth token or supplied both its username and password, {@code false otherwise}
126     */
127    public static boolean isUserEnoughIdentified() {
128        JosmUserIdentityManager identManager = JosmUserIdentityManager.getInstance();
129        if (identManager.isFullyIdentified()) {
130            return true;
131        } else {
132            CredentialsManager credManager = CredentialsManager.getInstance();
133            try {
134                if (JosmPreferencesCredentialAgent.class.equals(credManager.getCredentialsAgentClass())) {
135                    if (OsmApi.isUsingOAuth()) {
136                        return credManager.lookupOAuthAccessToken() != null;
137                    } else {
138                        String username = Main.pref.get("osm-server.username", null);
139                        String password = Main.pref.get("osm-server.password", null);
140                        return username != null && !username.isEmpty() && password != null && !password.isEmpty();
141                    }
142                } else {
143                    CredentialsAgentResponse credentials = credManager.getCredentials(
144                            RequestorType.SERVER, OsmApi.getOsmApi().getHost(), false);
145                    if (credentials != null) {
146                        String username = credentials.getUsername();
147                        char[] password = credentials.getPassword();
148                        return username != null && !username.isEmpty() && password != null && password.length > 0;
149                    }
150                }
151            } catch (CredentialsAgentException e) {
152                Main.warn("Unable to get credentials: "+e.getMessage());
153            }
154        }
155        return false;
156    }
157}