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}