001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.io; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.net.Authenticator.RequestorType; 007import java.util.concurrent.Executors; 008import java.util.concurrent.ScheduledExecutorService; 009import java.util.concurrent.ScheduledFuture; 010import java.util.concurrent.TimeUnit; 011 012import org.openstreetmap.josm.data.UserIdentityManager; 013import org.openstreetmap.josm.data.osm.UserInfo; 014import org.openstreetmap.josm.data.preferences.BooleanProperty; 015import org.openstreetmap.josm.data.preferences.IntegerProperty; 016import org.openstreetmap.josm.gui.progress.NullProgressMonitor; 017import org.openstreetmap.josm.io.auth.CredentialsAgentException; 018import org.openstreetmap.josm.io.auth.CredentialsAgentResponse; 019import org.openstreetmap.josm.io.auth.CredentialsManager; 020import org.openstreetmap.josm.io.auth.JosmPreferencesCredentialAgent; 021import org.openstreetmap.josm.spi.preferences.Config; 022import org.openstreetmap.josm.tools.Logging; 023import org.openstreetmap.josm.tools.Utils; 024 025/** 026 * Notifies user periodically of new received (unread) messages 027 * @since 6349 028 */ 029public final class MessageNotifier { 030 031 private MessageNotifier() { 032 // Hide default constructor for utils classes 033 } 034 035 /** 036 * Called when new new messages are detected. 037 * @since 12766 038 */ 039 @FunctionalInterface 040 public interface NotifierCallback { 041 /** 042 * Perform the actual notification of new messages. 043 * @param userInfo the new user information, that includes the number of unread messages 044 */ 045 void notifyNewMessages(UserInfo userInfo); 046 } 047 048 private static volatile NotifierCallback callback; 049 050 /** 051 * Sets the {@link NotifierCallback} responsible of notifying the user when new messages are received. 052 * @param notifierCallback the new {@code NotifierCallback} 053 */ 054 public static void setNotifierCallback(NotifierCallback notifierCallback) { 055 callback = notifierCallback; 056 } 057 058 /** Property defining if this task is enabled or not */ 059 public static final BooleanProperty PROP_NOTIFIER_ENABLED = new BooleanProperty("message.notifier.enabled", true); 060 /** Property defining the update interval in minutes */ 061 public static final IntegerProperty PROP_INTERVAL = new IntegerProperty("message.notifier.interval", 5); 062 063 private static final ScheduledExecutorService EXECUTOR = 064 Executors.newSingleThreadScheduledExecutor(Utils.newThreadFactory("message-notifier-%d", Thread.NORM_PRIORITY)); 065 066 private static final Runnable WORKER = new Worker(); 067 068 private static volatile ScheduledFuture<?> task; 069 070 private static class Worker implements Runnable { 071 072 private int lastUnreadCount; 073 private long lastTimeInMillis; 074 075 @Override 076 public void run() { 077 try { 078 long currentTime = System.currentTimeMillis(); 079 // See #14671 - Make sure we don't run the API call many times after system wakeup 080 if (currentTime >= lastTimeInMillis + TimeUnit.MINUTES.toMillis(PROP_INTERVAL.get())) { 081 lastTimeInMillis = currentTime; 082 final UserInfo userInfo = new OsmServerUserInfoReader().fetchUserInfo(NullProgressMonitor.INSTANCE, 083 tr("get number of unread messages")); 084 final int unread = userInfo.getUnreadMessages(); 085 if (unread > 0 && unread != lastUnreadCount) { 086 callback.notifyNewMessages(userInfo); 087 lastUnreadCount = unread; 088 } 089 } 090 } catch (OsmTransferException e) { 091 Logging.warn(e); 092 } 093 } 094 } 095 096 /** 097 * Starts the message notifier task if not already started and if user is fully identified 098 */ 099 public static void start() { 100 int interval = PROP_INTERVAL.get(); 101 if (NetworkManager.isOffline(OnlineResource.OSM_API)) { 102 Logging.info(tr("{0} not available (offline mode)", tr("Message notifier"))); 103 } else if (!isRunning() && interval > 0 && isUserEnoughIdentified()) { 104 task = EXECUTOR.scheduleAtFixedRate(WORKER, 0, interval, TimeUnit.MINUTES); 105 Logging.info("Message notifier active (checks every "+interval+" minute"+(interval > 1 ? "s" : "")+')'); 106 } 107 } 108 109 /** 110 * Stops the message notifier task if started 111 */ 112 public static void stop() { 113 if (isRunning()) { 114 task.cancel(false); 115 Logging.info("Message notifier inactive"); 116 task = null; 117 } 118 } 119 120 /** 121 * Determines if the message notifier is currently running 122 * @return {@code true} if the notifier is running, {@code false} otherwise 123 */ 124 public static boolean isRunning() { 125 return task != null; 126 } 127 128 /** 129 * Determines if user set enough information in JOSM preferences to make the request to OSM API without 130 * prompting him for a password. 131 * @return {@code true} if user chose an OAuth token or supplied both its username and password, {@code false otherwise} 132 */ 133 public static boolean isUserEnoughIdentified() { 134 UserIdentityManager identManager = UserIdentityManager.getInstance(); 135 if (identManager.isFullyIdentified()) { 136 return true; 137 } else { 138 CredentialsManager credManager = CredentialsManager.getInstance(); 139 try { 140 if (JosmPreferencesCredentialAgent.class.equals(credManager.getCredentialsAgentClass())) { 141 if (OsmApi.isUsingOAuth()) { 142 return credManager.lookupOAuthAccessToken() != null; 143 } else { 144 String username = Config.getPref().get("osm-server.username", null); 145 String password = Config.getPref().get("osm-server.password", null); 146 return username != null && !username.isEmpty() && password != null && !password.isEmpty(); 147 } 148 } else { 149 CredentialsAgentResponse credentials = credManager.getCredentials( 150 RequestorType.SERVER, OsmApi.getOsmApi().getHost(), false); 151 if (credentials != null) { 152 String username = credentials.getUsername(); 153 char[] password = credentials.getPassword(); 154 return username != null && !username.isEmpty() && password != null && password.length > 0; 155 } 156 } 157 } catch (CredentialsAgentException e) { 158 Logging.log(Logging.LEVEL_WARN, "Unable to get credentials:", e); 159 } 160 } 161 return false; 162 } 163}