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}