/* * MultiHubsAdapter.java * * Copyright (C) 2008 AppleGrew * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package org.elite.jdcbot.framework; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.net.DatagramSocket; import java.net.SocketException; import java.util.ArrayList; import java.util.Collections; import java.util.ConcurrentModificationException; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.locks.ReentrantLock; import javax.imageio.IIOException; import org.elite.jdcbot.shareframework.SearchSet; import org.elite.jdcbot.shareframework.ShareManager; import org.elite.jdcbot.util.GlobalFunctions; import org.slf4j.Logger; /** * Created on 06-Jun-08<br> * This allows you to connect to multiple * hubs. This will handle the intricacies of creation of * different jDCBot instances for handling a hub and * synchronizing them. * <p> * <b>Note:</b> Whenever a method in this class has * a name similar to a method in jDCBot then always use the * method of this class, else proper synchronizations may * not possible. There are some similar named methods for which * this rule can be safely ignored, but they explicitly metion * this, hence look in their doc comment. * <p> * This class is thread safe. * * @author AppleGrew * @since 1.0 * @version 0.1.2 */ public class MultiHubsAdapter implements UDPInputThreadTarget, BotInterface { private static final Logger logger = GlobalObjects.getLogger(MultiHubsAdapter.class); private String _botname, _password; protected String _description, _conn_type, _email, _sharesize; protected boolean _passive; protected int _udp_port; protected String _botIP; protected int _listenPort; protected int _maxUploadSlots; protected int _maxDownloadSlots; protected BufferedServerSocket socketServer = null; protected DatagramSocket udpSocket = null; protected String miscDir; protected String incompleteDir; protected ShareManager shareManager; protected DownloadCentral downloadCentral = null; private UDPInputThread _udp_inputThread = null; protected List<jDCBot> bots; protected Map<String, Hub> hubMap = null; /** * Used to synchronized some process like when initConnectToMe is called. */ protected ReentrantLock lock; /** * Creates a new instance of MultiHubsAdapter. There should always be only instance of * this class. * @param config * @throws IOException * @throws BotException */ public MultiHubsAdapter(BotConfig config) throws IOException, BotException { this( config.getBotname(), config.getBotIP(), config.getListenPort(), config.getUDP_listenPort(), config.getPassword(), config.getDescription(), config.getConn_type(), config.getEmail(), config.getSharesize(), config.getUploadSlots(), config.getDownloadSlots(), config.isPassive() ); } /** * Creates a new instance of MultiHubsAdapter. There should always be only instance of * this class. For explanation of the parameters see * {@link jDCBot#jDCBot(String, String, int, int, String, String, String, String, String, int, int, boolean) jDCBot Constrcutor}. * @param botname * @param botIP * @param listenPort * @param UDP_listenPort * @param password * @param description * @param conn_type * @param email * @param sharesize * @param uploadSlots * @param downloadSlots * @param passive * @throws IOException * @throws BotException */ public MultiHubsAdapter(String botname, String botIP, int listenPort, int UDP_listenPort, String password, String description, String conn_type, String email, String sharesize, int uploadSlots, int downloadSlots, boolean passive) throws IOException, BotException { if(!GlobalFunctions.isUserNameValid(botname)) { throw new BotException(BotException.Error.INVALID_USERNAME); } _botname = botname; _password = password; _description = description; _conn_type = conn_type; _email = email; _sharesize = sharesize; _maxUploadSlots = uploadSlots; _maxDownloadSlots = downloadSlots; _botIP = botIP; _listenPort = listenPort; _passive = passive; _udp_port = UDP_listenPort; lock = new ReentrantLock(); bots = Collections.synchronizedList(new ArrayList<jDCBot>(6)); hubMap = Collections.synchronizedMap(new HashMap<String, Hub>(6)); shareManager = null; if (_sharesize == null || _sharesize.isEmpty()) _sharesize = "0"; socketServer = new BufferedServerSocket(_listenPort); socketServer.setSoTimeout(60000); // Wait for 60s before timing out. initiateUDPListening(); } /** * <b>Note:</b> The given directories' must exist and should be empty, as * already existing files in them will overwritten without warning. * @param path2DirForMiscData In this directory own file list, hash data, etc. will be kept. * @param path2IncompleteDir Where incomplete downloads will be kept. * @throws FileNotFoundException If the directory paths are not found or 'fileListHash' * doesn't exist. * @throws IIOException If the given path are not directories. * @throws InstantiationException The read object from 'fileListHash' is not instance of FLDir. * @throws ClassNotFoundException Class of FLDir serialized object cannot be found. * @throws IOException Error occurred while reading from 'fileListHash'. */ public void setDirs(String path2DirForMiscData, String path2IncompleteDir) throws IIOException, FileNotFoundException, IOException { File miscDir = new File(path2DirForMiscData); File incompleteDir = new File(path2IncompleteDir); if (!miscDir.exists() || !incompleteDir.exists()) throw new FileNotFoundException("'" + path2DirForMiscData + " or '" + path2IncompleteDir + "' does not exist."); if (!miscDir.isDirectory()) throw new IIOException("Given path '" + path2DirForMiscData + "' is not a directory."); if (!incompleteDir.isDirectory()) throw new IIOException("Given path '" + path2IncompleteDir + "' is not a directory."); this.miscDir = miscDir.getCanonicalPath(); this.incompleteDir = incompleteDir.getCanonicalPath(); for (jDCBot bot : bots) { bot.miscDir = this.miscDir; bot.incompleteDir = this.incompleteDir; } } void addBot(jDCBot bot) { if (bot == null) { throw new NullPointerException("Cannot add null bot."); } bots.add(bot); } void removeBot(jDCBot bot) { if (bot == null) { throw new NullPointerException("Cannot remove null bot."); } bots.remove(bot); } public String getMiscDir() { return miscDir; } public String getIncompleteDir() { return incompleteDir; } public List<jDCBot> getAllBots() { synchronized (bots) { return new ArrayList<jDCBot>(bots); } } public jDCBot getBot(String hubSignature) { synchronized (bots) { for (jDCBot bot : bots) if (bot.getHubSignature().equals(hubSignature)) return bot; } return null; } /** * Sets the mapping of hub signature to * Hub. This allows you to specify different * settings for certain values (like user name, * passowrd, active/passive mode, etc.) for * different hubs. * @param hubSettings List of Hub settings. */ public void setHubMaps(List<Hub> hubSettings) { for (Hub hub : hubSettings) hubMap.put(hub.getHubSignature(), hub); } public ShareManager getShareManager() { return shareManager; } /** * <b>Note:</b> <u>Always</u> call {@link #setDirs(String, String)} * before calling this method else you will get all sorts of nasty * exceptions like NullPointerException, etc. and ShareManager * will seem to be not working at all. * <p> * It is recommended that you set the shareManager at the * initiation of application and donot call this method again, * ever during the lifetime of the application. * * @param sm */ public void setShareManager(ShareManager sm) { if (shareManager != null) shareManager.close(); shareManager = sm; shareManager.setDirs(miscDir); shareManager.init(); _sharesize = String.valueOf(shareManager.getOwnShareSize(false)); synchronized (bots) { for (jDCBot bot : bots) { bot.shareManager = shareManager; bot._sharesize = _sharesize; } } } public DownloadCentral getDownloadCentral() { return downloadCentral; } /** * <b>Note:</b> <u>Always</u> call {@link #setDirs(String, String)} * before calling this method else you will get all sorts of nasty * exceptions like NullPointerException, etc. and DownloadCentral * will seem to be not working at all. * <p> * It is recommended that you set the downloadManager at the * initiation of application and donot call this method again, * ever during the lifetime of the application. * * @param dc */ public void setDownloadCentral(DownloadCentral dc) { if (downloadCentral != null) downloadCentral.close(); downloadCentral = dc; downloadCentral.setDirs(incompleteDir); downloadCentral.init(); downloadCentral.startNewQueueProcessThread(); synchronized (bots) { for (jDCBot bot : bots) bot.downloadCentral = downloadCentral; } } public void updateShareSize() { String sharesize = String.valueOf(shareManager.getOwnShareSize(false)); if (sharesize.equals(_sharesize)) return; _sharesize = sharesize; synchronized (bots) { for (jDCBot bot : bots) try { bot.sendMyINFO(); } catch (IOException e) { logger.error("Exception in setDownloadCentral()", e); } } } public synchronized void terminate() { for (jDCBot bot : bots) bot.terminate(); if (_udp_inputThread != null) _udp_inputThread.stop(); if (shareManager != null) shareManager.close(); if (downloadCentral != null) downloadCentral.close(); try { socketServer.close(); } catch (IOException e) { logger.warn("Exception in terminate.", e); } } public void handleUDPCommand(String rawCommand, String ip, int port) { synchronized (bots) { for (jDCBot bot : bots) bot.handleUDPCommand(rawCommand, ip, port); } } public void onUDPExceptionClose(IOException e) { _udp_inputThread = null; try { initiateUDPListening(); } catch (SocketException e1) { logger.warn("Failed to reopen UDP port. Searching may not work."); logger.error("Exception in onUDPExceptionClose()", e); } } synchronized private void initiateUDPListening() throws SocketException { if (_udp_inputThread != null && !_udp_inputThread.isClosed()) return; udpSocket = new DatagramSocket(_udp_port); _udp_inputThread = new UDPInputThread(this, udpSocket); _udp_inputThread.start(); } /** * Searches all the hubs for the given term. * <p> * If you want to search in only some specific * hubs the you can safely call the Search() * methods of appropriate jDCBot. * @param ss * @throws IOException */ public void Search(SearchSet ss) throws IOException { synchronized (bots) { for (jDCBot bot : bots) bot.Search(ss); } } /** * You can safely use jDCBot's * UserExist() if you need. */ public boolean UserExist(String user) { synchronized (bots) { for (jDCBot bot : bots) if (bot.UserExist(user)) return true; } return false; } /** * You can safely use jDCBot's * getBotClientProtoSupports() if you need. */ public String getBotClientProtoSupports() { return jDCBot._clientproto_supports; } /** * You can safely use jDCBot's * getBotHubProtoSupports() if you need. */ public String getBotHubProtoSupports() { return jDCBot._hubproto_supports; } public int getMaxDownloadSlots() { return _maxDownloadSlots; } public void setMaxDownloadSlots(int slots) { _maxDownloadSlots = slots; synchronized (bots) { for (jDCBot bot : bots) bot.setMaxDownloadSlots(slots); } } public int getMaxUploadSlots() { return _maxUploadSlots; } public void setMaxUploadSlots(int slots) { _maxUploadSlots = slots; synchronized (bots) { for (jDCBot bot : bots) bot.setMaxUploadSlots(slots); } } /** * @return User with the matching client ID. If none found then it is null. */ public User getUserByCID(String cid) { synchronized (bots) { for (jDCBot bot : bots) { User u = bot.getUserByCID(cid); if (u != null) return u; } } return null; } /** * This is a powerful method of locating users from different hubs * who are actually the same inspite of having different usernames. * Note that having same IP doesn't mean that two users are the * same. Also note that user may run multiple clients in which * case the CID may very well be different. Also to know the * CID you need to download client's file list first. * * @param cid * @return null is never returned. */ public List<User> getUsersByCID(String cid) { List<User> users = new ArrayList<User>(); synchronized (bots) { for (jDCBot bot : bots) { User u = bot.getUserByCID(cid); if (u != null) users.add(u); } } return users; } /** * You can safely use jDCBot's * botname() if you need. * @return */ public String botname() { return _botname; } protected String botname(jDCBot bot) { Hub h; synchronized (hubMap) { if (hubMap != null && (h = hubMap.get(bot.getHubSignature())) != null) { h.username = h.username.trim(); return h.username == null || h.username.isEmpty() ? _botname : h.username; } } return _botname; } protected String getPassword(jDCBot bot) { Hub h; synchronized (hubMap) { if (hubMap != null && (h = hubMap.get(bot.getHubSignature())) != null) { return h.password; } } return _password; } protected String getDescription(jDCBot bot) { Hub h; synchronized (hubMap) { if (hubMap != null && (h = hubMap.get(bot.getHubSignature())) != null) { return h.description; } } return _description; } protected String getConnType(jDCBot bot) { Hub h; synchronized (hubMap) { if (hubMap != null && (h = hubMap.get(bot.getHubSignature())) != null) { return h.conn_type; } } return _conn_type; } protected String getEmail(jDCBot bot) { Hub h; synchronized (hubMap) { if (hubMap != null && (h = hubMap.get(bot.getHubSignature())) != null) { return h.email; } } return _email; } protected boolean isPassive(jDCBot bot) { Hub h; synchronized (hubMap) { if (hubMap != null && (h = hubMap.get(bot.getHubSignature())) != null) { return h.isPassive; } } return _passive; } /** * This will return all users * from all the hubs. * <p> * You can safely use jDCBot's * GetAllUsers() if you need. * @return Array of Users. */ public User[] GetAllUsers() { List<User> users = new ArrayList<User>(); synchronized (bots) { for (jDCBot bot : bots) for (User u : bot.GetAllUsers()) users.add(u); } return users.toArray(new User[0]); } /** * Connects to a hub. * @param hostname * @param port * @param newbot A new instance of jDCBot's sub-class. <b>Note</b>, that * this sub-class must have called jDCBot's {@link jDCBot#jDCBot(MultiHubsAdapter)} * constructor with this class instance being passed as argument. * @throws IOException * @throws BotException Various exceptions are thrown when error occurs during connecting. * If we are already connected to this hub then BotException.Error.ALREADY_CONNECTED is * thrown. */ public void connect(String hostname, int port, jDCBot newbot) throws IOException, BotException { if (newbot == null) { throw new NullPointerException("Cannot connect with null bot"); } synchronized (bots) { for (jDCBot bot : bots) if (Hub.prepareHubSignature(hostname, port).equals(bot.getHubSignature())) throw new BotException(BotException.Error.ALREADY_CONNECTED); bots.add(newbot); } newbot.connect(hostname, port); } public int getFreeDownloadSlots() { int dhCount = 0; synchronized (bots) { for (jDCBot bot : bots) dhCount += bot.downloadManager.getAllDHCount(); } int free = _maxDownloadSlots - dhCount; return free < 0 ? 0 : free; } public int getFreeUploadSlots() { int uhCount = 0; synchronized (bots) { for (jDCBot bot : bots) uhCount += bot.uploadManager.getAllUHCount(); } int free = _maxUploadSlots - uhCount; return free < 0 ? 0 : free; } /** * @param user * @return Users with matching * user name as <i>user</i>. Multiple hubs * may contain the same user * name hence an ArrayList is * returned. It will never be null. */ public List<User> getUsers(String user) { List<User> usrs = new ArrayList<User>(); synchronized (bots) { for (jDCBot bot : bots) { User u = bot.getUser(user); if (u != null) usrs.add(u); } } return usrs; } /** * You can safely use jDCBot's * getUser() if you need. When * multiple users with same name * is found (in different hubs) * then arbitraly any one is * returned. * @param user * @return null if * no user with this user name is * found in any of the hubs. */ public User getUser(String user) { synchronized (bots) { for (jDCBot bot : bots) { User u = bot.getUser(user); if (u != null) return u; } } return null; } /** * You can safely use jDCBot's * isBotClientProtoSupports() if you need. */ public boolean isBotClientProtoSupports(String feature) { return jDCBot._clientproto_supports.toLowerCase().indexOf(feature.toLowerCase()) != -1; } /** * You can safely use jDCBot's * isBotHubProtoSupports() if you need. */ public boolean isBotHubProtoSupports(String feature) { return jDCBot._hubproto_supports.toLowerCase().indexOf(feature.toLowerCase()) != -1; } public int getTotalHubsConnectedToCount() { return bots.size(); } public int getTotalHubsConnectedToAsOps() { try { int count = 0; for(jDCBot bot: bots) { if(bot.isOp()) count++; } return count; } catch(ConcurrentModificationException cme) { logger.warn("ConcurrentModificationException so sending TotalHubsConnectedToCount" + "instead of TotalHubsConnectedToAsOps"); return getTotalHubsConnectedToCount(); } } public int getTotalHubsConnectedToAsRegistered() { try { int count = 0; for(jDCBot bot: bots) { if(bot.isRegistered() && !bot.isOp()) count++; } return count; } catch(ConcurrentModificationException cme) { logger.warn("ConcurrentModificationException so sending TotalHubsConnectedToRegistered" + "instead of TotalHubsConnectedToAsOps"); return getTotalHubsConnectedToCount(); } } public int getTotalHubsConnectedToAsNormalUser() { int normalCount = getTotalHubsConnectedToCount() - getTotalHubsConnectedToAsOps() - getTotalHubsConnectedToAsRegistered(); return normalCount < 0? getTotalHubsConnectedToCount(): normalCount; } }