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