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(() -> { 068 JPanel panel = new JPanel(new GridBagLayout()); 069 panel.add(new JLabel(trn("You have {0} unread message.", "You have {0} unread messages.", unread, unread)), 070 GBC.eol()); 071 panel.add(new UrlLabel(Main.getBaseUserUrl() + '/' + userInfo.getDisplayName() + "/inbox", 072 tr("Click here to see your inbox.")), GBC.eol()); 073 panel.setOpaque(false); 074 new Notification().setContent(panel) 075 .setIcon(JOptionPane.INFORMATION_MESSAGE) 076 .setDuration(Notification.TIME_LONG) 077 .show(); 078 }); 079 lastUnreadCount = unread; 080 } 081 } catch (OsmTransferException e) { 082 Main.warn(e); 083 } 084 } 085 } 086 087 /** 088 * Starts the message notifier task if not already started and if user is fully identified 089 */ 090 public static void start() { 091 int interval = PROP_INTERVAL.get(); 092 if (Main.isOffline(OnlineResource.OSM_API)) { 093 Main.info(tr("{0} not available (offline mode)", tr("Message notifier"))); 094 } else if (!isRunning() && interval > 0 && isUserEnoughIdentified()) { 095 task = EXECUTOR.scheduleAtFixedRate(WORKER, 0, TimeUnit.MINUTES.toSeconds(interval), TimeUnit.SECONDS); 096 Main.info("Message notifier active (checks every "+interval+" minute"+(interval > 1 ? "s" : "")+')'); 097 } 098 } 099 100 /** 101 * Stops the message notifier task if started 102 */ 103 public static void stop() { 104 if (isRunning()) { 105 task.cancel(false); 106 Main.info("Message notifier inactive"); 107 task = null; 108 } 109 } 110 111 /** 112 * Determines if the message notifier is currently running 113 * @return {@code true} if the notifier is running, {@code false} otherwise 114 */ 115 public static boolean isRunning() { 116 return task != null; 117 } 118 119 /** 120 * Determines if user set enough information in JOSM preferences to make the request to OSM API without 121 * prompting him for a password. 122 * @return {@code true} if user chose an OAuth token or supplied both its username and password, {@code false otherwise} 123 */ 124 public static boolean isUserEnoughIdentified() { 125 JosmUserIdentityManager identManager = JosmUserIdentityManager.getInstance(); 126 if (identManager.isFullyIdentified()) { 127 return true; 128 } else { 129 CredentialsManager credManager = CredentialsManager.getInstance(); 130 try { 131 if (JosmPreferencesCredentialAgent.class.equals(credManager.getCredentialsAgentClass())) { 132 if (OsmApi.isUsingOAuth()) { 133 return credManager.lookupOAuthAccessToken() != null; 134 } else { 135 String username = Main.pref.get("osm-server.username", null); 136 String password = Main.pref.get("osm-server.password", null); 137 return username != null && !username.isEmpty() && password != null && !password.isEmpty(); 138 } 139 } else { 140 CredentialsAgentResponse credentials = credManager.getCredentials( 141 RequestorType.SERVER, OsmApi.getOsmApi().getHost(), false); 142 if (credentials != null) { 143 String username = credentials.getUsername(); 144 char[] password = credentials.getPassword(); 145 return username != null && !username.isEmpty() && password != null && password.length > 0; 146 } 147 } 148 } catch (CredentialsAgentException e) { 149 Main.warn(e, "Unable to get credentials:"); 150 } 151 } 152 return false; 153 } 154}