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.getOSMWebsite() + "/user/"+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 (!isRunning() && interval > 0 && isUserEnoughIdentified()) {
091            task = EXECUTOR.scheduleAtFixedRate(WORKER, 0, interval * 60, TimeUnit.SECONDS);
092            Main.info("Message notifier active (checks every "+interval+" minute"+(interval>1?"s":"")+")");
093        }
094    }
095
096    /**
097     * Stops the message notifier task if started
098     */
099    public static void stop() {
100        if (isRunning()) {
101            task.cancel(false);
102            Main.info("Message notifier inactive");
103            task = null;
104        }
105    }
106    
107    /**
108     * Determines if the message notifier is currently running
109     * @return {@code true} if the notifier is running, {@code false} otherwise
110     */
111    public static boolean isRunning() {
112        return task != null;
113    }
114    
115    /**
116     * Determines if user set enough information in JOSM preferences to make the request to OSM API without
117     * prompting him for a password.
118     * @return {@code true} if user chose an OAuth token or supplied both its username and password, {@code false otherwise}
119     */
120    public static boolean isUserEnoughIdentified() {
121        JosmUserIdentityManager identManager = JosmUserIdentityManager.getInstance();
122        if (identManager.isFullyIdentified()) {
123            return true;
124        } else {
125            CredentialsManager credManager = CredentialsManager.getInstance();
126            try {
127                if (JosmPreferencesCredentialAgent.class.equals(credManager.getCredentialsAgentClass())) {
128                    if (OsmApi.isUsingOAuth()) {
129                        return credManager.lookupOAuthAccessToken() != null;
130                    } else {
131                        String username = Main.pref.get("osm-server.username", null);
132                        String password = Main.pref.get("osm-server.password", null);
133                        return username != null && !username.isEmpty() && password != null && !password.isEmpty();
134                    }
135                } else {
136                    CredentialsAgentResponse credentials = credManager.getCredentials(
137                            RequestorType.SERVER, OsmApi.getOsmApi().getHost(), false);
138                    if (credentials != null) {
139                        String username = credentials.getUsername();
140                        char[] password = credentials.getPassword();
141                        return username != null && !username.isEmpty() && password != null && password.length > 0;
142                    }
143                }
144            } catch (CredentialsAgentException e) {
145                Main.warn("Unable to get credentials: "+e.getMessage());
146            }
147        }
148        return false;
149    }
150}