From 46ef85f49375f4fc4a25f8df84cc98cef6ad23a1 Mon Sep 17 00:00:00 2001 From: Damien Roth Date: Fri, 21 Nov 2008 21:27:11 +0000 Subject: [PATCH] Major update for the dict protocol - Using an external library to manage the connection with the server - Enhanced account registration wizard --- build.xml | 1 + lib/installer-exclude/dict4j.jar | Bin 0 -> 83176 bytes resources/images/images.properties | 1 + .../protocol/dict/dict-16x16-offline.png | Bin 0 -> 687 bytes resources/languages/resources.properties | 39 +- resources/languages/resources_fr.properties | 31 + .../impl/protocol/dict/ContactDictImpl.java | 51 +- .../impl/protocol/dict/DictAccountID.java | 31 + .../impl/protocol/dict/DictActivator.java | 6 +- .../impl/protocol/dict/DictAdapter.java | 654 ------------------ .../impl/protocol/dict/DictException.java | 287 -------- .../impl/protocol/dict/DictRegistry.java | 64 -- .../impl/protocol/dict/DictResult.java | 97 --- .../impl/protocol/dict/DictResultset.java | 107 --- .../impl/protocol/dict/DictStatusEnum.java | 40 +- ...ationSetBasicInstantMessagingDictImpl.java | 139 ++-- .../protocol/dict/ProtocolIconDictImpl.java | 60 +- .../dict/ProtocolProviderFactoryDictImpl.java | 20 +- .../dict/ProtocolProviderServiceDictImpl.java | 170 +++-- .../protocol/dict/dict.provider.manifest.mf | 3 +- .../DictAccRegWizzActivator.java | 3 +- .../DictAccountRegistration.java | 42 +- .../DictAccountRegistrationWizard.java | 12 +- .../plugin/dictaccregwizz/DictAdapter.java | 183 ----- .../dictaccregwizz/FirstWizardPage.java | 233 +++---- .../plugin/dictaccregwizz/ProgressPanel.java | 137 ++++ .../plugin/dictaccregwizz/StrategiesList.java | 213 ++++++ .../plugin/dictaccregwizz/StrategyThread.java | 207 ------ .../plugin/dictaccregwizz/ThreadManager.java | 182 +++++ .../dictaccregwizz/dictaccregwizz.manifest.mf | 1 + 30 files changed, 948 insertions(+), 2066 deletions(-) create mode 100755 lib/installer-exclude/dict4j.jar create mode 100755 resources/images/protocol/dict/dict-16x16-offline.png delete mode 100644 src/net/java/sip/communicator/impl/protocol/dict/DictAdapter.java delete mode 100644 src/net/java/sip/communicator/impl/protocol/dict/DictException.java delete mode 100644 src/net/java/sip/communicator/impl/protocol/dict/DictRegistry.java delete mode 100644 src/net/java/sip/communicator/impl/protocol/dict/DictResult.java delete mode 100644 src/net/java/sip/communicator/impl/protocol/dict/DictResultset.java delete mode 100644 src/net/java/sip/communicator/plugin/dictaccregwizz/DictAdapter.java create mode 100755 src/net/java/sip/communicator/plugin/dictaccregwizz/ProgressPanel.java create mode 100755 src/net/java/sip/communicator/plugin/dictaccregwizz/StrategiesList.java delete mode 100644 src/net/java/sip/communicator/plugin/dictaccregwizz/StrategyThread.java create mode 100755 src/net/java/sip/communicator/plugin/dictaccregwizz/ThreadManager.java diff --git a/build.xml b/build.xml index 798927185..afd43f372 100644 --- a/build.xml +++ b/build.xml @@ -1740,6 +1740,7 @@ javax.swing.event, javax.swing.border"/> manifest="${src}/net/java/sip/communicator/impl/protocol/dict/dict.provider.manifest.mf"> + diff --git a/lib/installer-exclude/dict4j.jar b/lib/installer-exclude/dict4j.jar new file mode 100755 index 0000000000000000000000000000000000000000..4b65d35f71529e4a4db6b58be350f8ab9c4a32f0 GIT binary patch literal 83176 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 Xk`Dm~0}PA>_^JhF@|dg-fS>*kMzyDK literal 0 HcmV?d00001 diff --git a/resources/images/images.properties b/resources/images/images.properties index 6f4c4d592..b1c15842a 100644 --- a/resources/images/images.properties +++ b/resources/images/images.properties @@ -353,6 +353,7 @@ inviteIcon=resources/images/plugin/whiteboard/inviteDialogIcon.png # dict status enum dictProtocolIcon=resources/images/protocol/dict/dict-16x16.png +dictOfflineIcon=resources/images/protocol/dict/dict-16x16-offline.png dict64x64Icon=resources/images/protocol/dict/dict-64x64.png # gibberish status enum diff --git a/resources/images/protocol/dict/dict-16x16-offline.png b/resources/images/protocol/dict/dict-16x16-offline.png new file mode 100755 index 0000000000000000000000000000000000000000..f450b04816af512d1257ab2771aaa3acfd34a3bb GIT binary patch literal 687 zcmV;g0#N;lP)Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L00f-?00f-@d^BsJ00007bV*G`2iOP+ z5D+VcnRv(m00J*bL_t(I%cWC2PvbxkeY@V^kA#gagbfXfqHsm3NG?#y{D+j+(C|AD zJsmaD{Qw$T%G3xYQqUb#7DS5WB(kxwLTqQQK(RO-R7|s)-S?j6&CW0)!gqq>n;||X zqtU2Uuh)N8tJO}HWoD8jgUMvVo6Y7A&bjjX`ucmZSp4dCyMIa&5#jRk^33x*KS`1m z5m8YTG?`3jJRZ}<#RWxCM6=n9f*>Hz^Hz6vcmDbL`56(tTd*wa_~78cu2!p+jVKWz zNfN|yjP-gAB0`?$;G9?X_V#Qk<#E{qDP{dDkG5^&=H{jp1%RR`u)V!q2LSH6uGBP5 z|47<^i3qJ$3)ATo4-XHe01=^9tLd)mO0FnMM^)8-nz<=?L+JPW7z_rO&*vpZ*L9&N zN{4HjcBJe2m*2z~!`9XoT-U{LI4tiC!+@%)N8E9owrQH55Bo1mSy4T=?Os)APhrfS%ylb0?V=h0JpcdNYfOv*$kfN!8A?m?CijCoI@d{Y+PSo zqbLewS@u3%LVz)b`}=zU!2bR|k|cqY67%^SK@ea#9Ky1!hIo8@T>HKstyU{6mrD{t zaKkVZB1(PV&jE19aRldFPt&v*jYext)1d1*kD>^Sh|ufx`0nm*4S)n-0;mHp0H^>I z00n?I053!oa?Zm%&%>*$t2g%9HZsQ8$;k databasesList; - - /** - * Initialize a basic instance with predefined settings - */ - public DictAdapter() - { - this.host = "dict.org"; - this.port = 2628; - this.strategy = "prefix"; - this.connected = false; - this.socket = null; - this.out = null; - this.in = null; - } - - /** - * Initialize a basic instance and set th host - * @param host Host - */ - public DictAdapter(String host) - { - this.host = host; - this.port = 2628; - this.strategy = "prefix"; - this.connected = false; - this.socket = null; - this.out = null; - this.in = null; - } - - /** - * Initialize an instance and set the host and the port - * @param host Host - * @param port Port - */ - public DictAdapter(String host, int port) - { - this.host = host; - this.port = port; - this.strategy = "prefix"; - this.connected = false; - this.socket = null; - this.out = null; - this.in = null; - } - - /** - * Initialize an instance and set the host, port and strategy - * @param host Host - * @param port Port - * @param strategy Match strategy - */ - public DictAdapter(String host, int port, String strategy) - { - this.host = host; - this.port = port; - this.strategy = strategy; - this.connected = false; - this.socket = null; - this.out = null; - this.in = null; - } - - - /** - * Establish a connexion to the dict server - * @throws Exception - * @return DictResultset containing the error - null otherwise - */ - private void connect() throws Exception - { - String fromServer; - - if (this.isConnected()) - { - return; - } - - try - { - this.socket = new Socket(this.host, this.port); - this.out = new PrintWriter(new OutputStreamWriter(this.socket.getOutputStream(), - "UTF-8"), true); - this.in = new BufferedReader(new InputStreamReader(this.socket.getInputStream(), - "UTF-8")); - - fromServer = this.in.readLine(); // Server banner - - if (fromServer.startsWith("220")) - { // 220 = connect ok - this.connected = true; - this.client("SIP Communicator"); - return; - } - else - { - throw new DictException(fromServer.substring(0, 3)); - } - } - catch(UnknownHostException uhe) - { - throw new DictException(uhe); - } - catch(IOException ioe) - { - throw new DictException(ioe); - } - } - - /** - * Close the actual connexion - * @throws Exception - */ - public void close() throws Exception - { - String fromServer; - boolean quit = false; - - if (!this.isConnected()) - { - return; - } - - try - { - this.out.println("QUIT"); - - // Clean the socket buffer - while (quit == false && (fromServer = this.in.readLine()) != null) - { - if (fromServer.startsWith("221")) - { // Quit response - quit = true; - } - } - - this.out.close(); - this.in.close(); - this.socket.close(); - - this.connected = false; - } - catch (IOException ioe) - { - throw new DictException(ioe); - } - } - - /** - * Get the database list from the server - * @throws Exception - * @return a DictResultset containing the database list - otherwise the error code - */ - public DictResultset showDB() throws Exception - { - String fromServer; - boolean quit = false; - DictResultset result = new DictResultset(); - this.connect(); - - try - { - fromServer = this.query("SHOW DB"); - - if (fromServer.startsWith("110")) - { // OK - getting responses from the server - result.newResultset(); - while (quit == false && (fromServer = this.in.readLine()) != null) - { - if (fromServer.startsWith("250")) - { - quit = true; - } - else if (!fromServer.equals(".")) - { - result.addResult(fromServer); - } - } - } - else - { - throw new DictException(fromServer.substring(0,3)); - } - } - catch (IOException ioe) - { - throw new DictException(ioe); - } - - return result; - } - - /** - * Get the strategies allowed by the server for the MATCH command - * @throws Exception - * @return a DictResultset containing the database list - otherwise the error code - */ - public DictResultset showStrat() throws Exception - { - String fromServer; - boolean quit = false; - DictResultset result = new DictResultset(); - this.connect(); - - try - { - fromServer = this.query("SHOW STRAT"); - - if (fromServer.startsWith("111")) - { // OK - getting responses from the server - result.newResultset(); - while (quit == false && (fromServer = this.in.readLine()) != null) - { - if (fromServer.startsWith("250")) - { - quit = true; - } - else if (!fromServer.equals(".")) - { - result.addResult(fromServer); - } - } - } - else - { - throw new DictException(fromServer.substring(0,3)); - } - } - catch (IOException ioe) - { - throw new DictException(ioe); - } - - return result; - } - - - /** - * Get the definition of a word - * @param database the database in which the word will be searched - * @param word the search word - * @throws Exception - * @return a DictResultset containing the database list - otherwise the error code - */ - public DictResultset define(String database, String word) throws Exception - { - String fromServer; - boolean quit = false; - DictResultset result = new DictResultset(); - String[] test; - this.connect(); - - try - { - fromServer = this.query("DEFINE " + database + " " + word); - - if (fromServer.startsWith("150")) - { - while (quit == false && (fromServer = this.in.readLine()) != null) - { - if (fromServer.startsWith("151")) - { // First line - Contains the DB Name - test = fromServer.split(" ", 4); - result.newResultset(test[3].substring(1, test[3].length() - 1)); - continue; - } - else if (fromServer.startsWith("250")) - { // End of the request - quit = true; - } - else if (!fromServer.equals(".")) - { - result.addResult(fromServer); - } - } - } - else - { - throw new DictException(fromServer.substring(0,3)); - } - } - catch (IOException ioe) - { - throw new DictException(ioe); - } - - return result; - } - - /** - * Get words that match with a strategie form a word with the stored strategy - * @param database The database in which the words will be searched - * @param word The base word - * @return a DictResultset containing the words list - otherwise throw an exception - * @throws Exception - */ - public DictResultset match(String database, String word) throws Exception - { - return this.match(database, this.strategy, word); - } - - /** - * Get words that match with a strategie from a word - * @param database the database in which the words will be searched - * @param strat the strategies used - * @param word the base word - * @throws Exception - * @return a DictResultset containing the words list - otherwise the error code - */ - public DictResultset match(String database, String strat, String word) throws Exception - { - String fromServer; - boolean quit = false; - DictResultset result = new DictResultset(); - this.connect(); - - try - { - fromServer = this.query("MATCH " + database + " " + strat + " " + word); - - if (fromServer.startsWith("152")) - { - result.newResultset(); - while (quit == false && (fromServer = this.in.readLine()) != null) - { - if (fromServer.startsWith("250")) - { - quit = true; - } - else if (!fromServer.equals(".")) - { - result.addResult(fromServer); - } - } - } - else - { - throw new DictException(fromServer.substring(0,3)); - } - } - catch (IOException ioe) - { - throw new DictException(ioe); - } - - return result; - } - - /** - * Provide information to the server about the clientname, for logging and statistical purposes - * @param clientname Client name - * @throws Exception - */ - public void client(String clientname) throws Exception - { - String fromServer; - this.connect(); - - fromServer = this.query("CLIENT " + clientname); - - // 250 code is the only possible answer - if (!fromServer.startsWith("250")) - { - throw new DictException(fromServer.substring(0, 3)); - } - } - - /** - * Set the host - * @param newHost host address - */ - public void setHost(String newHost) throws Exception - { - if (isUrl(newHost)) - { - this.host = newHost; - } - else - { - throw new DictException(900, "Host URL is incorrect"); - } - } - - /** - * Set the host port - * @param newPort Port - */ - public void setPort(int newPort) - { - this.port = newPort; - } - - /** - * Set the strategy - * @param newStrat Strategy - */ - public void setStrategy(String newStrat) - { - this.strategy = newStrat; - } - - /** - * Set the client name which is communicated to the server - * @param cn Client name - */ - public void setClientName(String cn) - { - this.clientName = cn; - } - - /** - * Return the host - * @return return the host - */ - public String getHost() - { - return this.host; - } - - /** - * Return the port - * @return return the port - */ - public int getPort() - { - return this.port; - } - - /** - * Return the strategy - * @return return the strategy - */ - public String getStrategy() - { - return this.strategy; - } - - /** - * Return the client name - * @return return the client name - */ - public String getClientName() - { - return this.clientName; - } - - /** - * Gets the database's list from the server - * @return List of the databases - * @throws Exception - */ - public Vector getDatabases() throws Exception - { - if (this.databasesList == null) - { - DictResultset drs = this.showDB(); - DictResult list = drs.getResultset(0); - - this.databasesList = new Vector(); - - while(list.hasNext()) - { - this.databasesList.add(list.next()); - } - } - return this.databasesList; - } - - /** - * Gets the dictionary name from the databases list - * @param code Dictionary code - * @return the dictionary name - * @throws Exception - */ - public String getDictionaryName(String code) throws Exception - { - int dictionary_id_and_description_separator; - String dictionary_id_and_description; - String dictionary_id; - String dictionary_description; - - // First, we check if the code is a special code - // Checks the RFC-2229 for more details - if (code.equals("*")) - { - return "Any dictionary"; - } - else if (code.equals("!")) - { - return "First match"; - } - - // Gets the databases list - if (this.databasesList == null) - { - getDatabases(); - - } - - // Look down the databases list to get the name - for (int i=0; i= 900) - { - result = this.errorMessage; - } - else - { - result = this.errorGen(error); - } - } - - return result; - } - - /** - * Get informations about unknowns errors - * @param err Error number - * @return Error definition - */ - private String errorGen(int err) - { - String error_type = Integer.toString(err); - - String result = new String(); - - if(error_type.startsWith("1")) - {// test on digit one - result = "Positive Preliminary reply : "; - } - else if(error_type.startsWith("2")) - { - result = "Positive Completion reply : " ; - } - else if(error_type.startsWith("3")) - { - result = "Positive Intermediate reply : "; - } - else if(error_type.startsWith("4")) - { - result = "Transient Negative Completion reply : "; - } - else if(error_type.startsWith("5")) - { - result = "Permanent Negative Completion reply : "; - } - else - { - return "Unknown error"; - } - - //test on digit two - if(error_type.charAt(1) == '0') - { - result += "Syntax"; - } - else if ( error_type.charAt(1) == '1') - { - result += "Information"; - } - else if ( error_type.charAt(1) == '2') - { - result += "Connections"; - } - else if ( error_type.charAt(1) == '3') - { - result += "Authentication"; - } - else if ( error_type.charAt(1) == '4') - { - result += "Unspecified as yet"; - } - else if ( error_type.charAt(1) == '5') - { - result += "DICT System"; - } - else if ( error_type.charAt(1) == '8') - { - result += "Nonstandard (private implementation) extensions"; - } - - return result; - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/dict/DictRegistry.java b/src/net/java/sip/communicator/impl/protocol/dict/DictRegistry.java deleted file mode 100644 index 257744fe1..000000000 --- a/src/net/java/sip/communicator/impl/protocol/dict/DictRegistry.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. - * - * Distributable under LGPL license. - * See terms of license at gnu.org. - */ -package net.java.sip.communicator.impl.protocol.dict; - -import java.util.*; - -/** - * Static registry storing the connexions to dict servers - * @author ROTH Damien - * @author LITZELMANN Cedric - */ -public class DictRegistry -{ - private static HashMap adapters = new HashMap(); - - /** - * Checks if an adapter associated with the given key is stored in the regitry - * @param key Key to the adapter - * @return true, if an adapter exists - false otherwise - */ - public static boolean has(String key) - { - return adapters.containsKey(key); - } - - /** - * Stores a new adapter in the registry - * @param key Key - * @param value DictAdapter class - */ - public static void put(String key, DictAdapter value) - { - adapters.put(key, value); - } - - /** - * Returns the adapter associated with the given key - * @param key Key - * @return the adapter associated with the given key - null otherwise - */ - public static DictAdapter get(String key) - { - if (DictRegistry.has(key)) - { - return adapters.get(key); - } - return null; - } - - /** - * Removes the adapter associated with the given key from the registry - * @param key - */ - public static void remove(String key) - { - if (DictRegistry.has(key)) { - adapters.remove(key); - } - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/dict/DictResult.java b/src/net/java/sip/communicator/impl/protocol/dict/DictResult.java deleted file mode 100644 index 101515ddf..000000000 --- a/src/net/java/sip/communicator/impl/protocol/dict/DictResult.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. - * - * Distributable under LGPL license. - * See terms of license at gnu.org. - */ -package net.java.sip.communicator.impl.protocol.dict; - -import java.util.*; - -/** - * Representation of the results of a response to a query - * - * @author ROTH Damien - * @author LITZEMANN Cedric - */ -public class DictResult - implements Iterator -{ - private String databaseName; - private int index; - private ArrayList data; - - /** - * Basic construct initializing the result - */ - public DictResult() - { - this.index = 0; - this.data = new ArrayList(); - this.databaseName = ""; - } - - /** - * Initialize the result and save the database name - * @param dbn Database name - */ - public DictResult(String dbn) - { - this.index = 0; - this.data = new ArrayList(); - this.databaseName = dbn; - } - - /** - * Add a result - * @param s result - */ - public void add(String s) - { - this.data.add(s); - } - - /** - * From the Iterator implementation, return the next part of the result - * @return the next part of the result - */ - public String next() - { - return (String) this.data.get(this.index++); - } - - /** - * From the Iterator implementation, return true if the iteration has more elements - * @return true if the iteration has more elements - false otherwise - */ - public boolean hasNext() - { - return (this.index < this.data.size()); - } - - /** - * From the Iterator implementation but unsupported - */ - public void remove() - { - throw new UnsupportedOperationException(); - } - - /** - * Set the database name - * @param dbn Database name - */ - public void setDatabaseName(String dbn) - { - this.databaseName = dbn; - } - - /** - * Return the database name - * @return the database name - */ - public String getDatabaseName() - { - return this.databaseName; - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/dict/DictResultset.java b/src/net/java/sip/communicator/impl/protocol/dict/DictResultset.java deleted file mode 100644 index 14b629a21..000000000 --- a/src/net/java/sip/communicator/impl/protocol/dict/DictResultset.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. - * - * Distributable under LGPL license. - * See terms of license at gnu.org. - */ -package net.java.sip.communicator.impl.protocol.dict; - -import java.util.*; - -/** - * Class managing the results of a dict query - * - * @author ROTH Damien - * @author LITZELMANN Cedric - */ -public class DictResultset -{ - /** - * The index number of the last resultset for this DictResultset. This - * parametre is set to "-1" if there is no resultset. - */ - private int cursor; - - /** - * The list containing all the resultsets for this DictResultset. - */ - private ArrayList data; - - /** - * Initialize a resultset list - */ - public DictResultset() - { - this.cursor = -1; - this.data = new ArrayList(); - } - - /** - * Create a new resultset - */ - public void newResultset() - { - this.cursor++; - this.data.add(new DictResult()); - } - - /** - * Create a new resultset and save the database name - * @param dbn Database name - */ - public void newResultset(String dbn) - { - this.cursor++; - this.data.add(new DictResult(dbn)); - } - - /** - * Set the database name for the current resultset - * @param dbn Database name - */ - public void setDatabaseName(String dbn) - { - this.data.get(this.cursor).setDatabaseName(dbn); - } - - /** - * Add a result in the current resultset - * @param res a result line from a dict query - */ - public void addResult(String res) - { - this.data.get(this.cursor).add(res); - } - - /** - * Return true if there is a resultset - * @return return true if there is a resultset - false otherwise - */ - public boolean hasResult() - { - return this.data.size() > 0; - } - - /** - * Return the resultset at the given index - * @param index Index of the wished resultset - * @return a DictResult - null otherwise - */ - public DictResult getResultset(int index) - { - if (index < this.data.size()) - { - return (DictResult) this.data.get(index); - } - return null; - } - - /** - * Return the number of resultsets - * @return the number of resultsets - */ - public int getNbResults() - { - return this.data.size(); - } -} diff --git a/src/net/java/sip/communicator/impl/protocol/dict/DictStatusEnum.java b/src/net/java/sip/communicator/impl/protocol/dict/DictStatusEnum.java index 3f41afcc3..17ba603f6 100644 --- a/src/net/java/sip/communicator/impl/protocol/dict/DictStatusEnum.java +++ b/src/net/java/sip/communicator/impl/protocol/dict/DictStatusEnum.java @@ -30,9 +30,8 @@ public class DictStatusEnum */ public static final DictStatusEnum OFFLINE = new DictStatusEnum( - 0 - , "Offline" - , getImageInBytes("dictProtocolIcon")); + 0, "Offline", + DictActivator.getResources().getImageInBytes("dictOfflineIcon")); /** * The Online status. Indicate that the user is able and willing to @@ -40,9 +39,8 @@ public class DictStatusEnum */ public static final DictStatusEnum ONLINE = new DictStatusEnum( - 65 - , "Online" - , getImageInBytes("dictProtocolIcon")); + 65, "Online", + DictActivator.getResources().getImageInBytes("dictProtocolIcon")); /** * Initialize the list of supported status states. @@ -78,34 +76,4 @@ static Iterator supportedStatusSet() { return supportedStatusSet.iterator(); } - - /** - * Returns the byte representation of the image corresponding to the given - * identifier. - * - * @param imageID the identifier of the image - * @return the byte representation of the image corresponding to the given - * identifier. - */ - public static byte[] getImageInBytes(String imageID) - { - InputStream in = DictActivator.getResources(). - getImageInputStream(imageID); - - if (in == null) - return null; - byte[] image = null; - try - { - image = new byte[in.available()]; - - in.read(image); - } - catch (IOException e) - { - logger.error("Failed to load image:" + imageID, e); - } - - return image; - } } diff --git a/src/net/java/sip/communicator/impl/protocol/dict/OperationSetBasicInstantMessagingDictImpl.java b/src/net/java/sip/communicator/impl/protocol/dict/OperationSetBasicInstantMessagingDictImpl.java index fd958a903..32a092065 100644 --- a/src/net/java/sip/communicator/impl/protocol/dict/OperationSetBasicInstantMessagingDictImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/dict/OperationSetBasicInstantMessagingDictImpl.java @@ -7,11 +7,11 @@ package net.java.sip.communicator.impl.protocol.dict; import java.util.*; + +import net.java.dict4j.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.event.*; -import net.java.sip.communicator.util.*; - /** * Instant messaging functionalities for the Dict protocol. * @@ -22,9 +22,6 @@ public class OperationSetBasicInstantMessagingDictImpl extends AbstractOperationSetBasicInstantMessaging implements RegistrationStateChangeListener { - private static final Logger logger - = Logger.getLogger(OperationSetBasicInstantMessagingDictImpl.class); - /** * The currently valid persistent presence operation set. */ @@ -34,6 +31,8 @@ public class OperationSetBasicInstantMessagingDictImpl * The protocol provider that created us. */ private ProtocolProviderServiceDictImpl parentProvider = null; + + private DictAccountID accountID; /** * Creates an instance of this operation set keeping a reference to the @@ -49,10 +48,17 @@ public OperationSetBasicInstantMessagingDictImpl( { this.opSetPersPresence = opSetPersPresence; this.parentProvider = provider; + this.accountID = (DictAccountID) provider.getAccountID(); parentProvider.addRegistrationStateChangeListener(this); } + public Message createMessage(String content) + { + return new MessageDictImpl(content, HTML_MIME_TYPE, + DEFAULT_MIME_ENCODING, null); + } + public Message createMessage(String content, String contentType, String encoding, String subject) { @@ -170,9 +176,8 @@ private void submitDictQuery(ContactDictImpl dictContact, Message message) Message msg = this.createMessage(""); String database = dictContact.getContactID(); - DictAdapter dictAdapter = dictContact.getDictAdapter(); + DictConnection conn = this.parentProvider.getConnection(); boolean doMatch = false; - DictResultset fctResult; String word; @@ -187,46 +192,33 @@ private void submitDictQuery(ContactDictImpl dictContact, Message message) // Try to get the definition of the work try { - fctResult = dictAdapter.define(database, word); - msg = - this.createMessage(this.retrieveDefine(fctResult, word), - HTML_MIME_TYPE, DEFAULT_MIME_ENCODING, null); + List definitions = conn.define(database, word); + msg = this.createMessage(retrieveDefine(definitions, word)); } - catch(DictException dex) + catch(DictException dx) { - if (dex.getErrorCode() == 552) + if (dx.getErrorCode() == DictReturnCode.NO_MATCH) { // No word found, we are going to try the match command doMatch = true; } else { // Otherwise we display the error returned by the server - msg = this.createMessage(manageException(dex, database)); + msg = this.createMessage(manageException(dx, database)); } } - catch(Exception ex) - { - logger.error("Failed to retrieve Definition. Error was: " - + ex.getMessage() - , ex); - } if (doMatch) { // Trying the match command try { - fctResult = dictAdapter.match(database, word); - msg = this.createMessage(this.retrieveMatch(fctResult, word)); - } - catch(DictException dex) - { - msg = this.createMessage(manageException(dex, database)); + List matchWords = conn.match(database, word, + this.accountID.getStrategy()); + msg = this.createMessage(retrieveMatch(matchWords, word)); } - catch(Exception ex) + catch(DictException dx) { - logger.error("Failed to retrieve Match. Error was: " - + ex.getMessage() - , ex); + msg = this.createMessage(manageException(dx, database)); } } @@ -241,28 +233,26 @@ private void submitDictQuery(ContactDictImpl dictContact, Message message) * @param word the queried word * @return the formatted result */ - private String retrieveDefine(DictResultset data, String word) + private String retrieveDefine(List data, String word) { - String result; - DictResult resultData; + StringBuffer res = new StringBuffer(); + Definition def; - result = "
";
-        
-        for (int i=0; i 0)
             {
-                result += resultData.next() + "\n";
+                res.append("
"); } - result = result.replaceAll("\n+\\z", "\n"); + res.append(def.getDefinition().replaceAll("\n", "
")) + .append("
-- From ") + .append(def.getDictionary()) + .append("
"); } - result += "
"; + + String result = res.toString(); result = formatResult(result, "\\\\", "", ""); result = formatResult(result, "[\\[\\]]", "", ""); result = formatResult(result, "[\\{\\}]", "", ""); @@ -328,45 +318,25 @@ private String formatResult(String result, String regex, String startTag, String * @param word the queried word * @return the formatted result */ - private String retrieveMatch(DictResultset data, String word) + private String retrieveMatch(List data, String word) { - String result = ""; - String temp; - DictResult resultData; + StringBuffer result = new StringBuffer(); boolean isStart = true; - result = "No definitions found for \""+ word +"\", perhaps you mean:\n"; + result.append(DictActivator.getResources() + .getI18NString("dict.matchResult", new String[] {word})); - for (int i=0; iProtocolIcon @@ -25,20 +20,16 @@ public class ProtocolIconDictImpl implements ProtocolIcon { - private static Logger logger = Logger.getLogger(ProtocolIconDictImpl.class); - - private static ResourceManagementService resourcesService; - /** * A hash table containing the protocol icon in different sizes. */ private static Hashtable iconsTable = new Hashtable(); static { iconsTable.put(ProtocolIcon.ICON_SIZE_16x16, - getImageInBytes("dictProtocolIcon")); + DictActivator.getResources().getImageInBytes("dictProtocolIcon")); iconsTable.put(ProtocolIcon.ICON_SIZE_64x64, - getImageInBytes("dict64x64Icon")); + DictActivator.getResources().getImageInBytes("dict64x64Icon")); } /** @@ -81,51 +72,4 @@ public byte[] getConnectingIcon() { return iconsTable.get(ProtocolIcon.ICON_SIZE_16x16); } - - /** - * Returns the byte representation of the image corresponding to the given - * identifier. - * - * @param imageID the identifier of the image - * @return the byte representation of the image corresponding to the given - * identifier. - */ - private static byte[] getImageInBytes(String imageID) - { - InputStream in = DictActivator.getResources(). - getImageInputStream(imageID); - - if (in == null) - return null; - byte[] image = null; - try - { - image = new byte[in.available()]; - - in.read(image); - } - catch (IOException e) - { - logger.error("Failed to load image:" + imageID, e); - } - - return image; - } - - public static ResourceManagementService getResources() - { - if (resourcesService == null) - { - ServiceReference serviceReference = DictActivator.bundleContext - .getServiceReference(ResourceManagementService.class.getName()); - - if(serviceReference == null) - return null; - - resourcesService = (ResourceManagementService)DictActivator.bundleContext - .getService(serviceReference); - } - - return resourcesService; - } } diff --git a/src/net/java/sip/communicator/impl/protocol/dict/ProtocolProviderFactoryDictImpl.java b/src/net/java/sip/communicator/impl/protocol/dict/ProtocolProviderFactoryDictImpl.java index 45eabaf3e..59df9161b 100644 --- a/src/net/java/sip/communicator/impl/protocol/dict/ProtocolProviderFactoryDictImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/dict/ProtocolProviderFactoryDictImpl.java @@ -27,11 +27,6 @@ public class ProtocolProviderFactoryDictImpl private static final Logger logger = Logger.getLogger(ProtocolProviderFactoryDictImpl.class); - /** - * Name for the auto-create group - */ - private String groupName = "Dictionaries"; - /** * Creates an instance of the ProtocolProviderFactoryDictImpl. */ @@ -124,6 +119,9 @@ private void createGroup() try { + String groupName = DictActivator.getResources() + .getI18NString("dict.dictionaries"); + mcl.createMetaContactGroup(mcl.getRoot(), groupName); } catch (MetaContactListException ex) @@ -155,7 +153,15 @@ private void createDefaultContact(AccountID accountID) ProtocolProviderService protocolProvider = (ProtocolProviderService) DictActivator.getBundleContext() .getService(serRef); - + + // Gets group name + String groupName = DictActivator.getResources() + .getI18NString("dict.dictionaries"); + + // Gets contact name + String contactName = DictActivator.getResources() + .getI18NString("dict.anyDictionaryFrom", new String[] {accountID.getUserID()}); + // Gets the MetaContactGroup for the "dictionaries" group. MetaContactGroup group = mcl.getRoot().getMetaContactSubgroup(groupName); @@ -165,7 +171,7 @@ private void createDefaultContact(AccountID accountID) // Create the default contact. mcl.createMetaContact(protocolProvider, group, dict_uin); // Rename the default contact. - mcl.renameMetaContact(group.getMetaContact(protocolProvider, dict_uin), accountID.getUserID() + "_default_dictionary"); + mcl.renameMetaContact(group.getMetaContact(protocolProvider, dict_uin), contactName); } @Override diff --git a/src/net/java/sip/communicator/impl/protocol/dict/ProtocolProviderServiceDictImpl.java b/src/net/java/sip/communicator/impl/protocol/dict/ProtocolProviderServiceDictImpl.java index edf97f4be..b803c6c75 100644 --- a/src/net/java/sip/communicator/impl/protocol/dict/ProtocolProviderServiceDictImpl.java +++ b/src/net/java/sip/communicator/impl/protocol/dict/ProtocolProviderServiceDictImpl.java @@ -8,6 +8,7 @@ import java.util.*; +import net.java.dict4j.*; import net.java.sip.communicator.service.protocol.*; import net.java.sip.communicator.service.protocol.event.*; import net.java.sip.communicator.service.version.*; @@ -35,7 +36,7 @@ public class ProtocolProviderServiceDictImpl /** * The id of the account that this protocol provider represents. */ - private AccountID accountID = null; + private DictAccountID accountID = null; /** * We use this to lock access to initialization. @@ -65,6 +66,11 @@ public class ProtocolProviderServiceDictImpl private RegistrationState currentRegistrationState = RegistrationState.UNREGISTERED; + /** + * the DictConnection opened by this provider + */ + private DictConnection dictConnection; + /** * The default constructor for the Dict protocol provider. */ @@ -91,7 +97,11 @@ protected void initialize(String userID, { synchronized(initializationLock) { - this.accountID = accountID; + this.accountID = (DictAccountID) accountID; + + this.dictConnection = new DictConnection(this.accountID.getHost(), + this.accountID.getPort()); + this.dictConnection.setClientName(getSCVersion()); //initialize the presence operationset OperationSetPersistentPresenceDictImpl persistentPresence = @@ -131,65 +141,14 @@ protected void initialize(String userID, isInitialized = true; } } - - /** - * Retrieve the DictAdapter linked with the account. If there is no instance, one is created - * @return a DictAdapter instance - */ - public DictAdapter getDictAdapter() - { - String host = (String) this.accountID.getAccountProperties() - .get(ProtocolProviderFactory.SERVER_ADDRESS); - int port = Integer.parseInt((String) this.accountID.getAccountProperties() - .get(ProtocolProviderFactory.SERVER_PORT)); - String strategy = (String) this.accountID.getAccountProperties() - .get(ProtocolProviderFactory.STRATEGY); - - String key = this.accountID.getUserID(); - DictAdapter result = DictRegistry.get(key); - - if (!(result instanceof DictAdapter)) - { - result = new DictAdapter(host, port, strategy); - - - // Set the clientname from the current version - BundleContext bundleContext = DictActivator.getBundleContext(); - ServiceReference versionServRef = bundleContext - .getServiceReference(VersionService.class.getName()); - - VersionService versionService = (VersionService) bundleContext - .getService(versionServRef); - - result.setClientName(versionService.getCurrentVersion().toString()); - - // Store the DictAdapter - DictRegistry.put(key, result); - } - - return result; - } /** - * Close the DictAdapter linked with the account ID + * Returns the DictConnection opened by this provider + * @return the DictConnection opened by this provider */ - public void closeDictAdapter() + public DictConnection getConnection() { - String key = this.accountID.getUserID(); - DictAdapter result = DictRegistry.get(key); - - if ((result instanceof DictAdapter)) - { - try - { - result.close(); - DictRegistry.remove(key); - } - catch (Exception ex) - { - logger.error(ex); - } - } + return this.dictConnection; } /** @@ -279,14 +238,50 @@ public Map getSupportedOperationSets() public void register(SecurityAuthority authority) throws OperationFailedException { - RegistrationState oldState = currentRegistrationState; - currentRegistrationState = RegistrationState.REGISTERED; - - fireRegistrationStateChanged( - oldState - , currentRegistrationState - , RegistrationStateChangeEvent.REASON_USER_REQUEST - , null); + // Try to connect to the server + boolean connected = connect(); + + if (connected) + { + fireRegistrationStateChanged( + getRegistrationState(), + RegistrationState.REGISTERED, + RegistrationStateChangeEvent.REASON_USER_REQUEST, + null); + currentRegistrationState = RegistrationState.REGISTERED; + } + else + { + fireRegistrationStateChanged( + getRegistrationState(), + RegistrationState.CONNECTION_FAILED, + RegistrationStateChangeEvent.REASON_SERVER_NOT_FOUND, + null); + currentRegistrationState = RegistrationState.UNREGISTERED; + } + } + + /** + * Checks if the connection to the dict server is open + * @return TRUE if the connection is open - FALSE otherwise + */ + private boolean connect() + { + if (this.dictConnection.isConnected()) + { + return true; + } + + try + { + return this.dictConnection.isAvailable(); + } + catch (DictException dx) + { + logger.info(dx); + } + + return false; } /** @@ -302,7 +297,8 @@ public void shutdown() } logger.trace("Killing the Dict Protocol Provider for account " + this.accountID.getUserID()); - this.closeDictAdapter(); + + closeConnection(); if(isRegistered()) { @@ -335,13 +331,41 @@ public void shutdown() public void unregister() throws OperationFailedException { - RegistrationState oldState = currentRegistrationState; - currentRegistrationState = RegistrationState.UNREGISTERED; - + closeConnection(); + fireRegistrationStateChanged( - oldState - , currentRegistrationState - , RegistrationStateChangeEvent.REASON_USER_REQUEST - , null); + getRegistrationState(), + RegistrationState.UNREGISTERED, + RegistrationStateChangeEvent.REASON_USER_REQUEST, + null); + } + + /** + * Close the connection to the server + */ + private void closeConnection() + { + try + { + this.dictConnection.close(); + } + catch (DictException dx) + { + logger.info(dx); + } + } + + /** + * Returns the current version of SIP-Communicator + * @return the current version of SIP-Communicator + */ + private String getSCVersion() + { + BundleContext bc = DictActivator.getBundleContext(); + ServiceReference vsr = bc.getServiceReference(VersionService.class.getName()); + + VersionService vs = (VersionService) bc.getService(vsr); + return vs.getCurrentVersion().toString(); + } } diff --git a/src/net/java/sip/communicator/impl/protocol/dict/dict.provider.manifest.mf b/src/net/java/sip/communicator/impl/protocol/dict/dict.provider.manifest.mf index b2e050cda..a9d53a206 100644 --- a/src/net/java/sip/communicator/impl/protocol/dict/dict.provider.manifest.mf +++ b/src/net/java/sip/communicator/impl/protocol/dict/dict.provider.manifest.mf @@ -2,7 +2,7 @@ Bundle-Activator: net.java.sip.communicator.impl.protocol.dict.DictActivator Bundle-Name: Dict Protocol Provider Bundle-Description: A bundle providing support for the Dict protocol. Bundle-Vendor: sip-communicator.org -Bundle-Version: 0.0.1 +Bundle-Version: 1.0.0 Import-Package: org.osgi.framework, net.java.sip.communicator.service.contactlist, net.java.sip.communicator.service.configuration, @@ -12,3 +12,4 @@ Import-Package: org.osgi.framework, net.java.sip.communicator.service.protocol, net.java.sip.communicator.service.protocol.event, net.java.sip.communicator.service.version +Export-Package: net.java.dict4j diff --git a/src/net/java/sip/communicator/plugin/dictaccregwizz/DictAccRegWizzActivator.java b/src/net/java/sip/communicator/plugin/dictaccregwizz/DictAccRegWizzActivator.java index a41813d04..f9584ae56 100644 --- a/src/net/java/sip/communicator/plugin/dictaccregwizz/DictAccRegWizzActivator.java +++ b/src/net/java/sip/communicator/plugin/dictaccregwizz/DictAccRegWizzActivator.java @@ -21,7 +21,8 @@ * @author ROTH Damien * @author LITZELMANN Cedric */ -public class DictAccRegWizzActivator implements BundleActivator +public class DictAccRegWizzActivator + implements BundleActivator { public static BundleContext bundleContext; diff --git a/src/net/java/sip/communicator/plugin/dictaccregwizz/DictAccountRegistration.java b/src/net/java/sip/communicator/plugin/dictaccregwizz/DictAccountRegistration.java index 4be06eb42..f031c6afa 100644 --- a/src/net/java/sip/communicator/plugin/dictaccregwizz/DictAccountRegistration.java +++ b/src/net/java/sip/communicator/plugin/dictaccregwizz/DictAccountRegistration.java @@ -6,6 +6,8 @@ */ package net.java.sip.communicator.plugin.dictaccregwizz; +import net.java.dict4j.*; + /** * The DictAccountRegistration is used to store all user input data * through the DictAccountRegistrationWizard. @@ -16,7 +18,6 @@ public class DictAccountRegistration { private String userID; - private String password; /** * The hostname of the DICT server. @@ -29,15 +30,10 @@ public class DictAccountRegistration private int port; /** - * The code id of the strategie selected for the matching of words in dictionnaries. + * The strategy selected for the matching of words in dictionaries. */ - private String strategyCode; + private Strategy strategy; - /** - * The real name of the strategie selected for the matching of words in dictionnaries. - */ - private String strategy; - /** * Returns the User ID of the dict registration account. * @return the User ID of the dict registration account. @@ -47,16 +43,6 @@ public String getUserID() return userID; } - /** - * Sets the password of the dict registration account. - * - * @param password the password of the dict registration account. - */ - public void setPassword(String password) - { - this.password = password; - } - /** * Returns the port of the dict registration account. * @return the port of the dict registration account. @@ -93,7 +79,7 @@ public void setHost(String host) { * Returns the strategy that will be used for this dict account. * @return the strategy that will be used for this dict account. */ - public String getStrategy() { + public Strategy getStrategy() { return this.strategy; } @@ -101,23 +87,7 @@ public String getStrategy() { * Sets the strategy for this dict account. * @param strategy the strategy for this dict account. */ - public void setStrategy(String strategy) { + public void setStrategy(Strategy strategy) { this.strategy = strategy; } - - /** - * Returns the strategy code that will be used for this dict account. - * @return the strategy code that will be used for this dict account. - */ - public String getStrategyCode() { - return this.strategyCode; - } - - /** - * Sets the strategy code for this dict account. - * @param strategyCode the strategy code for this dict account. - */ - public void setStrategyCode(String strategyCode) { - this.strategyCode = strategyCode; - } } diff --git a/src/net/java/sip/communicator/plugin/dictaccregwizz/DictAccountRegistrationWizard.java b/src/net/java/sip/communicator/plugin/dictaccregwizz/DictAccountRegistrationWizard.java index 1d48b4101..54053bb5f 100644 --- a/src/net/java/sip/communicator/plugin/dictaccregwizz/DictAccountRegistrationWizard.java +++ b/src/net/java/sip/communicator/plugin/dictaccregwizz/DictAccountRegistrationWizard.java @@ -86,7 +86,7 @@ public byte[] getPageImage() */ public String getProtocolName() { - return Resources.getString("protocolNameDict"); + return Resources.getString("dict.protocolName"); } /** @@ -96,7 +96,7 @@ public String getProtocolName() */ public String getProtocolDescription() { - return Resources.getString("protocolDescriptionDict"); + return Resources.getString("dict.protocolDescription"); } /** @@ -122,7 +122,7 @@ public Iterator getSummary() summaryTable.put("Host", registration.getHost()); summaryTable.put("Port", String.valueOf(registration.getPort())); - summaryTable.put("Strategy", registration.getStrategy()); + summaryTable.put("Strategy", registration.getStrategy().getName()); return summaryTable.entrySet().iterator(); } @@ -140,7 +140,7 @@ public ProtocolProviderService finish() return this.installAccount(factory, registration.getHost(), registration.getPort(), - registration.getStrategyCode()); + registration.getStrategy().getCode()); } /** @@ -165,11 +165,9 @@ public ProtocolProviderService signin(String userName, String password) ProtocolProviderFactory factory = DictAccRegWizzActivator.getDictProtocolProviderFactory(); - /*return this.installAccount(factory, - userName);*/ return this.installAccount(factory, registration.getHost(), registration.getPort(), - registration.getStrategyCode()); + registration.getStrategy().getCode()); } /** diff --git a/src/net/java/sip/communicator/plugin/dictaccregwizz/DictAdapter.java b/src/net/java/sip/communicator/plugin/dictaccregwizz/DictAdapter.java deleted file mode 100644 index 9ffdb5038..000000000 --- a/src/net/java/sip/communicator/plugin/dictaccregwizz/DictAdapter.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. - * - * Distributable under LGPL license. See terms of license at gnu.org. - */ -package net.java.sip.communicator.plugin.dictaccregwizz; - -import java.io.*; -import java.net.*; -import java.util.*; - -import net.java.sip.communicator.util.Logger; - -/** - * Copy of DictAdapter class to solve the import problem - * @author ROTH Damien - * @author LITZELMANN Cedric - */ - -public class DictAdapter -{ - private static Logger logger = Logger.getLogger(DictAdapter.class); - - /** - * The socket used to connect to the DICT server. - */ - private Socket socket = null; - - /** - * A output stream piped to the socket in order to send command to the server. - */ - private PrintWriter out = null; - - /** - * A input stream piped to the socket in order to receive messages from the server. - */ - private BufferedReader in = null; - - /** - * A boolean telling if we are currently connected to the DICT server. - */ - private boolean connected = false; - - /** - * Get the strategies allowed by the server for the MATCH command - * @return a HashMap containing the database list - otherwise null - */ - public ArrayList getStrategies() - { - String fromServer; - boolean quit = false; - ArrayList result = null; - - // Connexion - if (!this.connected) - { - // Not connected - return null; - } - - try - { - this.out.println("SHOW STRAT"); - fromServer = this.in.readLine(); - - if (fromServer.startsWith("111")) - { // OK - getting responses from the server - result = new ArrayList(); - while (quit == false && (fromServer = this.in.readLine()) != null) - { - if (fromServer.startsWith("250")) - { - quit = true; - } - else if (!fromServer.equals(".")) - { - result.add(fromServer); - } - } - } - } - catch (IOException ioe) - { - logger.trace("Cannot get the strategies : " + ioe.getMessage()); - result = null; - } - return result; - } - - - /** - * Open a connection to the given host on the given port - * @param host The hostname of the server. - * @param port The port used by the server. - * @return true, if a connection is open - false otherwise - */ - public boolean connect(String host, int port) - { - this.connected = false; - String fromServer; - - try - { - this.socket = new Socket(host, port); - this.out = new PrintWriter(new OutputStreamWriter(this.socket.getOutputStream(), - "UTF-8"), true); - this.in = new BufferedReader(new InputStreamReader(this.socket.getInputStream(), - "UTF-8")); - - fromServer = this.in.readLine(); // Server banner - - if (fromServer.startsWith("220")) - { // 220 = connect ok - this.connected = true; - } - } - catch(Exception ex) - { // If an exception is throw == connexion impossible - logger.trace("Cannot establish a connexion to the server (" - + host + ":" + port + ")", ex); - } - - return this.connected; - } - - /** - * Close the connexion to the server - */ - public void close() - { - String fromServer; - - try - { - this.out.println("QUIT"); - - // Clean the socket buffer - while ((fromServer = this.in.readLine()) != null) - { - if (fromServer.startsWith("221")) - { // Quit response - break; - } - } - - this.out.close(); - this.in.close(); - this.socket.close(); - } - catch (IOException ioe) - { - logger.info("Cannot close the connextion to the server", ioe); - } - } - - /** - * Checks if the given url is correct and exists - * @param host The url that we have to test if it is correct and if it - * exists. - * @return true if the url exists - false otherwise - */ - public static boolean isUrl(String host) - { - boolean ok = false; - - if (host == null || host.length() == 0) - { - return false; - } - - // If an exception is throw, the host format isn't correct or isn't recheable - try - { - InetAddress.getByName(host); - ok = true; - } - catch (UnknownHostException uhex) - { - logger.trace("Test URL ("+host+") : " + uhex.getMessage(), uhex); - } - return ok; - } -} diff --git a/src/net/java/sip/communicator/plugin/dictaccregwizz/FirstWizardPage.java b/src/net/java/sip/communicator/plugin/dictaccregwizz/FirstWizardPage.java index 00d052851..a3a22e4e8 100644 --- a/src/net/java/sip/communicator/plugin/dictaccregwizz/FirstWizardPage.java +++ b/src/net/java/sip/communicator/plugin/dictaccregwizz/FirstWizardPage.java @@ -8,13 +8,14 @@ import java.awt.*; import java.awt.event.*; import java.util.*; +import java.util.List; import javax.swing.*; import javax.swing.event.*; +import net.java.dict4j.*; import net.java.sip.communicator.service.gui.*; import net.java.sip.communicator.service.protocol.*; -import net.java.sip.communicator.util.*; /** * The FirstWizardPage is the page, where user could enter the host, @@ -27,8 +28,6 @@ public class FirstWizardPage extends JPanel implements WizardPage, DocumentListener, ActionListener { - private static Logger logger = Logger.getLogger(FirstWizardPage.class); - public static final String FIRST_PAGE_IDENTIFIER = "FirstPageIdentifier"; private JPanel hostPortPanel = new JPanel(new BorderLayout(10, 10)); @@ -37,13 +36,13 @@ public class FirstWizardPage private JPanel valuesPanel = new JPanel(); - private JLabel hostLabel = new JLabel("Host"); + private JLabel hostLabel = new JLabel(Resources.getString("dict.host")); private JPanel emptyPanel = new JPanel(); private JLabel hostExampleLabel = new JLabel("Ex: dict.org"); - private JLabel portLabel = new JLabel("Port"); + private JLabel portLabel = new JLabel(Resources.getString("dict.port")); private JLabel existingAccountLabel = new JLabel(Resources.getString("existingAccount")); @@ -57,18 +56,15 @@ public class FirstWizardPage private JPanel strategyTitleBloc = new JPanel(new BorderLayout()); - private JLabel strategyTitle = new JLabel(Resources.getString("strategyList")); + private JLabel strategyTitle = new JLabel(Resources.getString("dict.strategyList")); - private JButton strategyLoader = new JButton(Resources.getString("strategyActu")); + private JButton strategyLoader = new JButton(Resources.getString("dict.strategyActu")); - private Vector strategyList; - private JScrollPane jScrollPane; - private JList strategyBox; - private JTextArea strategyDescription = new JTextArea(Resources.getString("strategyDesc")); - private JLabel strategyMessage; - private boolean strategyMessInstall = false; + private StrategiesList strategiesList; + private JTextArea strategyDescription = new JTextArea(Resources.getString("dict.strategyDesc")); + private ProgressPanel searchProgressPanel; - private JPanel mainPanel = new JPanel(); + private JPanel mainPanel = new JPanel(new BorderLayout()); private Object nextPageIdentifier = WizardPage.SUMMARY_PAGE_IDENTIFIER; @@ -76,9 +72,7 @@ public class FirstWizardPage private String initstrategy = ""; - private ArrayList strategiesAssoc = new ArrayList(); - - private StrategyThread populateThread = null; + private ThreadManager searchThread = null; private boolean firstAccount = false; @@ -105,7 +99,8 @@ public FirstWizardPage(DictAccountRegistrationWizard wizard) mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS)); - this.populateThread = new StrategyThread(this); + this.searchThread = new ThreadManager(this); + this.searchProgressPanel = new ProgressPanel(this.searchThread); this.firstAccount = !this.hasAccount(); @@ -117,9 +112,6 @@ public FirstWizardPage(DictAccountRegistrationWizard wizard) { this.init(); } - - this.populateThread = new StrategyThread(this); - this.populateThread.start(); this.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); } @@ -156,7 +148,8 @@ private void init() hostPortPanel.add(labelsPanel, BorderLayout.WEST); hostPortPanel.add(valuesPanel, BorderLayout.CENTER); - hostPortPanel.setBorder(BorderFactory.createTitledBorder("Server informations")); + hostPortPanel.setBorder(BorderFactory.createTitledBorder( + Resources.getString("dict.serverInformations"))); this.labelsPanel.setLayout(new BoxLayout(labelsPanel, BoxLayout.Y_AXIS)); this.valuesPanel.setLayout(new BoxLayout(valuesPanel, BoxLayout.Y_AXIS)); @@ -178,22 +171,12 @@ public void keyTyped(KeyEvent evt) public void keyReleased(KeyEvent evt) {;} }); - // Strategies - this.strategyList = new Vector(); - this.strategyBox = new JList(this.strategyList); - - for (int i=0; i<20; i++) - { - this.strategyList.add("Elem "+i); - } - - this.strategyBox.setVisibleRowCount(6); - - this.strategyBox.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); - - this.jScrollPane = new JScrollPane(); - this.jScrollPane.getViewport().add(this.strategyBox); - this.strategyPanel.add(this.jScrollPane); + // Strategies list + this.strategiesList = new StrategiesList(); + + JScrollPane scrollPane = new JScrollPane(); + scrollPane.getViewport().add(this.strategiesList); + this.strategyPanel.add(scrollPane); // Strategy title + button this.strategyTitleBloc.add(this.strategyTitle, BorderLayout.WEST); @@ -215,47 +198,49 @@ public void keyTyped(KeyEvent evt) sSouthPanel.add(this.strategyDescription); // Message - this.strategyMessage = new JLabel(" "); - sSouthPanel.add(this.strategyMessage, BorderLayout.SOUTH); + sSouthPanel.add(this.searchProgressPanel, BorderLayout.SOUTH); this.strategyPanel.add(sSouthPanel, BorderLayout.SOUTH); this.strategyPanel.add(this.strategyTitleBloc, BorderLayout.NORTH); - this.strategyPanel.setBorder(BorderFactory.createTitledBorder("Strategy selection")); + this.strategyPanel.setBorder(BorderFactory.createTitledBorder( + Resources.getString("dict.strategySelection"))); mainPanel.add(this.strategyPanel); this.add(mainPanel, BorderLayout.NORTH); } + /** + * Initialize the UI for the first account + */ private void initFirstAccount() { // Data init this.hostField = new JTextField("dict.org"); this.portField = new JTextField("2628"); - // Init strategy box - this.strategyList = new Vector(); - this.strategyBox = new JList(this.strategyList); - this.strategyMessage = new JLabel(" "); - + // Init strategies list + this.strategiesList = new StrategiesList(); + this.mainPanel = new JPanel(new BorderLayout()); + JPanel infoTitlePanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); - JTextArea firstDescription = new JTextArea(Resources.getString("firstAccount")); - JLabel title = new JLabel(Resources.getString("dictAccountInfoTitle")); - + JTextArea firstDescription = new JTextArea(Resources.getString("dict.firstAccount")); + JLabel title = new JLabel(Resources.getString("dict.accountInfoTitle")); + // Title title.setFont(title.getFont().deriveFont(Font.BOLD, 14.0f)); infoTitlePanel.add(title); - this.add(infoTitlePanel, BorderLayout.NORTH); - this.add(this.strategyMessage, BorderLayout.SOUTH); - + this.mainPanel.add(infoTitlePanel, BorderLayout.NORTH); + this.mainPanel.add(this.searchProgressPanel, BorderLayout.SOUTH); + // Description firstDescription.setLineWrap(true); firstDescription.setLineWrap(true); firstDescription.setRows(6); firstDescription.setWrapStyleWord(true); firstDescription.setAutoscrolls(false); - this.add(firstDescription); + this.mainPanel.add(firstDescription); } /** @@ -315,10 +300,8 @@ public void pageShowing() */ public void commitPage() { - //* String host = hostField.getText(); int port = Integer.parseInt(portField.getText()); - int stPos; boolean isModified = false; if (this.initAccountID instanceof AccountID) @@ -335,14 +318,18 @@ public void commitPage() } // We check if a strategy has been selected - if (this.strategyList.size() == 0) + if (this.strategiesList.getModel().getSize() == 0) { // No Strategy, we get them this.populateStrategies(); - while (this.populateThread.isRunning()) {;} + if (!this.searchThread.waitThread()) + { + // TODO error dialog : thread interrupted ? no thread ? + this.strategiesList.clear(); + } } - if (this.strategyList.size() == 0) + if (this.strategiesList.getModel().getSize() == 0) { // No strategy, maybe not connected // Information message is already on the wizard @@ -365,12 +352,8 @@ else if ((!wizard.isModification() && isExistingAccount(host, port)) registration.setHost(host); registration.setPort(port); - - stPos = this.strategyBox.getSelectedIndex(); - registration.setStrategyCode(this.strategiesAssoc.get(stPos)); - registration.setStrategy(this.strategyBox.getSelectedValue().toString()); + registration.setStrategy((Strategy) this.strategiesList.getSelectedValue()); } - //*/ isPageCommitted = true; } @@ -380,7 +363,7 @@ else if ((!wizard.isModification() && isExistingAccount(host, port)) */ private void setNextButtonEnabled() { - boolean hostOK = DictAdapter.isUrl(hostField.getText()); + boolean hostOK = DictConnection.isUrl(hostField.getText()); boolean portOK = (this.portField.getText().length() != 0) && Integer.parseInt(this.portField.getText()) > 10; @@ -399,7 +382,7 @@ else if (hostOK && portOK) wizard.getWizardContainer().setNextFinishButtonEnabled(false); // Clear the list and disable the button - this.strategyList.clear(); + this.strategiesList.clear(); this.strategyLoader.setEnabled(false); } } @@ -536,7 +519,6 @@ private boolean hasAccount() */ private boolean isExistingAccount(String host, int port) { - //* ProtocolProviderFactory factory = DictAccRegWizzActivator.getDictProtocolProviderFactory(); @@ -563,99 +545,74 @@ private boolean isExistingAccount(String host, int port) } } } - //*/ return false; } /** - * Start the thread which will populate the Strategy List + * Start the thread which will populate the Strategies List */ public void populateStrategies() { - // Clear ArrayLists - this.strategiesAssoc.clear(); - this.strategyList.clear(); - - - //this.populateThread = new StrategyThread(this); - this.populateThread.setHost(this.hostField.getText()) - .setPort(Integer.parseInt(this.portField.getText())) - .sendProcessRequest(); + // Clear ArrayList + this.strategiesList.clear(); + + boolean ok = this.searchThread.submitRequest(this.hostField.getText(), + Integer.parseInt(this.portField.getText())); + + if (!ok) + { + // TODO Display error + } } - + /** - * Called by the thread, display a message - * @param message a message + * Automatic selection of a strategy */ - public void threadMessage(String message) + public void autoSelectStrategy() { - this.strategyMessage.setText(message); + this.strategiesList.autoSelectStrategy(this.initstrategy); + } + + /** + * + * @param strategies + */ + public void setStrategies(List strategies) + { + this.strategiesList.setStrategies(strategies); } /** - * Called by the thread, remove the special message section + * Informs the user of the current status of the search + * Should only be called by the thread + * @param message Search status */ - public void threadRemoveMessage() + public void progressMessage(String message) { - this.strategyMessage.setText(" "); + this.searchProgressPanel.nextStep(message); } + /** - * Called by the thread, add a strategy in the list - * @param code The strategy code - * @param description The strategy description + * Informs the wizard that the search of the strategies is complete. + * Should only be called by the thread */ - public void threadAddStrategy(String code, String description) + public void strategiesSearchComplete() { - this.strategiesAssoc.add(code); - this.strategyList.add(description); - this.strategyBox.setListData(this.strategyList); + setStrategyButtonEnable(true); + this.searchProgressPanel.finish(); } /** - * Automatic selection of a strategy + * Informs the wizard that the search of the strategies is a failure + * Should only be called by the thread + * @param reason Reason message + * @param de Exception thrown */ - public void autoSelectStrategy() + public void strategiesSearchFailure(String reason, DictException de) { - int index = -1; - - if (this.initstrategy.length() > 0) - { // saved strategy - index = this.strategiesAssoc.indexOf(this.initstrategy); - this.initstrategy = ""; - } - if (index < 0) - { - // First case : levenstein distance - index = this.strategiesAssoc.indexOf("lev"); - } - if (index < 0) - { - // Second case : soundex - index = this.strategiesAssoc.indexOf("soundex"); - } - if (index < 0) - { - // Last case : prefix - index = this.strategiesAssoc.indexOf("prefix"); - } - - // If the index is still < 0, we select the first index - if (index < 0) - { - index = 0; - } - if (index < this.strategyBox.getVisibleRowCount()) - { - // If the index is visible row, we don't need to scroll - this.strategyBox.setSelectedIndex(index); - } - else - { - // Otherwise, we scroll to the selected value - this.strategyBox.setSelectedValue(this.strategyList.get(index), true); - } - + strategiesSearchComplete(); + // TODO SHOW ERROR MESSAGE } /** @@ -678,6 +635,16 @@ public Object getSimpleForm() return mainPanel; } + /** + * Indicates if this is the first dict account + * + * @return TRUE if this is the first dict account - FALSE otherwise + */ + public boolean isFirstAccount() + { + return this.firstAccount; + } + public boolean isCommitted() { return isPageCommitted; diff --git a/src/net/java/sip/communicator/plugin/dictaccregwizz/ProgressPanel.java b/src/net/java/sip/communicator/plugin/dictaccregwizz/ProgressPanel.java new file mode 100755 index 000000000..7b569624d --- /dev/null +++ b/src/net/java/sip/communicator/plugin/dictaccregwizz/ProgressPanel.java @@ -0,0 +1,137 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. See terms of license at gnu.org. + */ +package net.java.sip.communicator.plugin.dictaccregwizz; + +import java.awt.*; +import java.awt.event.*; + +import javax.swing.*; + +/** + * Panel showing the current status of the search of the strategies + * + * @author ROTH Damien + */ +public class ProgressPanel + extends JPanel + implements ActionListener +{ + private JPanel rightPanel; + + private JLabel messageLabel; + private JLabel progressLabel; + private JButton cancelButton; + + private int currentStep; + private int totalSteps; + + private boolean isBuild; + + private ThreadManager searchThread; + + /** + * Create an instance of ProgressPanel + * @param searchThread The thread manager + */ + public ProgressPanel(ThreadManager searchThread) + { + super(new BorderLayout()); + + // Element creation + this.messageLabel = new JLabel(" "); + this.progressLabel = new JLabel(" "); + this.cancelButton = new JButton(Resources.getString("cancel")); + this.cancelButton.addActionListener(this); + + // Right panel init + this.rightPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + this.rightPanel.add(this.progressLabel); + this.rightPanel.add(this.cancelButton); + + this.searchThread = searchThread; + + init(); + this.totalSteps = ThreadManager.NB_STEPS; + } + + /** + * Init the values + */ + private void init() + { + this.isBuild = false; + this.currentStep = 1; + + this.add(this.messageLabel, BorderLayout.CENTER); + } + + /** + * Build the UI + */ + private void build() + { + if (this.isBuild) + { + return; + } + + this.add(this.messageLabel, BorderLayout.CENTER); + this.add(this.rightPanel, BorderLayout.EAST); + + this.isBuild = true; + } + + /** + * Move to the next step without updating the message + */ + public void nextStep() + { + nextStep(this.messageLabel.getText()); + } + + /** + * Mode to the next step with a new message + * @param message Message + */ + public void nextStep(String message) + { + if (this.currentStep > this.totalSteps) + { + finish(); + } + + build(); + this.messageLabel.setText(message); + this.progressLabel.setText(currentStep + "/" + totalSteps); + + this.currentStep++; + } + + /** + * Informs the end of the progress. Remove all the components and + * reset the values + */ + public void finish() + { + // Remove all elements + this.removeAll(); + + // Re-init the panel + this.messageLabel.setText(" "); + this.progressLabel.setText(" "); + init(); + + this.repaint(); + this.validate(); + } + + @Override + public void actionPerformed(ActionEvent arg0) + { + this.searchThread.cancel(); + this.finish(); + } +} diff --git a/src/net/java/sip/communicator/plugin/dictaccregwizz/StrategiesList.java b/src/net/java/sip/communicator/plugin/dictaccregwizz/StrategiesList.java new file mode 100755 index 000000000..713ff8628 --- /dev/null +++ b/src/net/java/sip/communicator/plugin/dictaccregwizz/StrategiesList.java @@ -0,0 +1,213 @@ +/* + * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. + * + * Distributable under LGPL license. + * See terms of license at gnu.org. + */ +package net.java.sip.communicator.plugin.dictaccregwizz; + +import java.awt.Component; +import java.util.*; + +import javax.swing.*; + +import net.java.dict4j.*; + +/** + * Class managing the list of strategies + * + * @author ROTH Damien + */ +public class StrategiesList + extends JList +{ + private ListModel model; + private CellRenderer renderer; + + /** + * Create an instance of the StrategiesList + */ + public StrategiesList() + { + super(); + + this.model = new ListModel(); + this.renderer = new CellRenderer(); + + this.setCellRenderer(this.renderer); + this.setModel(model); + this.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + this.setVisibleRowCount(6); + } + + /** + * Stores a new set of strategies + * @param strategies List of strategies + */ + public void setStrategies(List strategies) + { + this.model.setStrategies(strategies); + } + + /** + * Remove all the strategies of the list + */ + public void clear() + { + this.model.clear(); + } + + /** + * Automatic selection of strategies + * @param initStrategy + */ + public void autoSelectStrategy(String initStrategy) + { + int index = -1; + + if (initStrategy.length() > 0) + { // saved strategy + index = this.model.indexOf(initStrategy); + } + if (index < 0) + { + // First case : levenstein distance + index = this.model.indexOf("lev"); + } + if (index < 0) + { + // Second case : soundex + index = this.model.indexOf("soundex"); + } + if (index < 0) + { + // Last case : prefix + index = this.model.indexOf("prefix"); + } + + // If the index is still < 0, we select the first index + if (index < 0) + { + index = 0; + } + if (index < this.getVisibleRowCount()) + { + // If the index is visible row, we don't need to scroll + this.setSelectedIndex(index); + } + else + { + // Otherwise, we scroll to the selected value + this.setSelectedValue(this.model.getElementAt(index), true); + } + } + + /** + * Class managing the list datas + * + * @author ROTH Damien + */ + class ListModel + extends AbstractListModel + { + List data; + + /** + * Create an instance of ListModel + */ + public ListModel() + { + this.data = new ArrayList(); + } + + /** + * Stores the strategies into this model + * @param data the strategies list + */ + public void setStrategies(List strategies) + { + this.data = strategies; + fireContentsChanged(this, 0, this.data.size()); + } + + /** + * Remove all the strategies of the list + */ + public void clear() + { + this.data.clear(); + } + + /** + * Implements ListModel.getElementAt + */ + @Override + public Object getElementAt(int row) + { + return this.data.get(row); + } + + /** + * Implements ListModel.getSize + */ + @Override + public int getSize() + { + return this.data.size(); + } + + /** + * Find the index of a strategie + * @param strategyCode the code of the strategy + * @return the index of the strategy + */ + public int indexOf(String strategyCode) + { + for (int i=0; iListCellRenderer.getListCellRendererComponent + */ + public Component getListCellRendererComponent(JList list, Object value, + int index, boolean isSelected, boolean cellHasFocus) + { + Strategy strategy = (Strategy) value; + this.setText(strategy.getName()); + + if (isSelected) + { + setBackground(list.getSelectionBackground()); + setForeground(list.getSelectionForeground()); + } + else + { + setBackground(list.getBackground()); + setForeground(list.getForeground()); + } + setEnabled(list.isEnabled()); + setFont(list.getFont()); + setOpaque(true); + + return this; + } + } +} \ No newline at end of file diff --git a/src/net/java/sip/communicator/plugin/dictaccregwizz/StrategyThread.java b/src/net/java/sip/communicator/plugin/dictaccregwizz/StrategyThread.java deleted file mode 100644 index 8dab524e6..000000000 --- a/src/net/java/sip/communicator/plugin/dictaccregwizz/StrategyThread.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * SIP Communicator, the OpenSource Java VoIP and Instant Messaging client. - * - * Distributable under LGPL license. See terms of license at gnu.org. - */ -package net.java.sip.communicator.plugin.dictaccregwizz; - -import java.util.*; - -import net.java.sip.communicator.util.*; - -/** - * The StrategyThread is the thread called by the wizzard to populate - * the strategies list. - * - * @author ROTH Damien - * @author LITZELMANN Cedric - */ -public class StrategyThread - extends Thread -{ - private static Logger logger = Logger.getLogger(StrategyThread.class); - - /** - * The hostname of the DICT server. - */ - private String host; - - /** - * The port used by the DICT server. - */ - private int port; - - /** - * True if the thread is running. - */ - private boolean isRunning = false; - - /** - * True if we need to search the strategies handled by the server in order - * to populate the list. - */ - private boolean needProcess = false; - - /** - * The first page wizard for the DICT protocole. - */ - private FirstWizardPage wizard; - - /** - * The java abstraction of the DICT server. - */ - private DictAdapter adapter = null; - - /** - * Create a new StrategyThread - * @param wizard the wizard for callback methods - */ - public StrategyThread(FirstWizardPage wizard) - { - this.wizard = wizard; - } - - /** - * Thread method running until it's destroy - */ - public void run() - { - while (true) - { - if (this.needProcess()) - { - this.setRunning(true); - this.process(); - this.processDone(); - this.setRunning(false); - } - try - { - this.sleep(500); - } - catch (InterruptedException ie) - { - // Action de log - logger.info("DICT THREAD : " + ie); - } - } - } - - /** - * Search the strategies on the server and populate the list - */ - private void process() - { - ArrayList strategies = null; - String temp[]; - - if (adapter == null) { - adapter = new DictAdapter(); - } - - this.wizard.setStrategyButtonEnable(false); - - // Initialize the connexion - this.wizard.threadMessage("Trying to connect to server"); - if (!adapter.connect(this.host, this.port)) - { - // Connexion attempt failed - this.wizard.threadMessage("Connexion attempt failed, this isn't a" - + "dict server or the server is offline"); - return; - } - - // Retrieving strategies - this.wizard.threadMessage("Retrieving strategies"); - strategies = adapter.getStrategies(); - if (strategies == null) - { - // No strategy found - this.wizard.threadMessage("No strategy found on the server"); - return; - } - - - // Insert the strategies in the list - for (int i=0; iThreadManager + * @param wiz Wizard + */ + public ThreadManager(FirstWizardPage wiz) + { + this.wizard = wiz; + } + + /** + * Submit a request to launch the thread + * @param host Server host + * @param port Server port + * @return TRUE if the thread is started - FALSE otherwise + */ + public boolean submitRequest(String host, int port) + { + if (this.thread != null) + { + return false; + } + + this.thread = new StrategyThread(this.wizard, host, port); + this.thread.start(); + + return true; + } + + /** + * Stop the thread + */ + public void cancel() + { + if (this.thread != null) + { + this.thread.interrupt(); + this.thread = null; + } + } + + /** + * Wait for the searching thread to stop + * @return + */ + public boolean waitThread() + { + if (this.thread == null) + { + return false; + } + + try + { + this.thread.join(); + } + catch (InterruptedException e) + { + logger.info(e); + return false; + } + return true; + } + + /** + * Thread used to search the strategies + * + * @author ROTH Damien + * @author LITZELMANN Cédric + */ + class StrategyThread + extends Thread + { + private FirstWizardPage wizard; + private String host; + private int port; + + /** + * Informations messages + */ + private String[] messages = new String[] { + Resources.getString("dict.threadConnect"), + Resources.getString("dict.threadConnectFailed"), + Resources.getString("dict.retrievingStrategies"), + Resources.getString("dict.noStrategiesFound"), + Resources.getString("dict.populateList"), + Resources.getString("dict.closingConnexion") + }; + + /** + * Create an instance of the thread + * @param wizard The wizard who started the thread + * @param host Server host + * @param port Server port + */ + public StrategyThread(FirstWizardPage wizard, String host, int port) + { + this.wizard = wizard; + this.host = host; + this.port = port; + } + + public void run() + { + List strategies = null; + + DictConnection dictConnection = new DictConnection(host, port); + + // Open the connection to the server + this.wizard.progressMessage(messages[0]); + try + { + dictConnection.connect(); + } + catch (DictException e) + { + this.wizard.strategiesSearchFailure(this.messages[1], e); + return; + } + + // Get the strategies + this.wizard.progressMessage(messages[2]); + try + { + strategies = dictConnection.getStrategies(); + } + catch (DictException e) + { + this.wizard.strategiesSearchFailure(this.messages[3], e); + return; + } + + // Store the strategies + this.wizard.progressMessage(messages[4]); + this.wizard.setStrategies(strategies); + this.wizard.autoSelectStrategy(); + + // Close the connection + this.wizard.progressMessage(messages[5]); + try + { + dictConnection.close(); + } + catch (DictException e) + { + // An error while closing the connection isn't very important + // We just log it + ThreadManager.logger.info("DICT search strategies thread : " + + "Error while closing connection", e); + } + + // End of the search + this.wizard.strategiesSearchComplete(); + } + } +} \ No newline at end of file diff --git a/src/net/java/sip/communicator/plugin/dictaccregwizz/dictaccregwizz.manifest.mf b/src/net/java/sip/communicator/plugin/dictaccregwizz/dictaccregwizz.manifest.mf index c1c4169b0..301d7f429 100644 --- a/src/net/java/sip/communicator/plugin/dictaccregwizz/dictaccregwizz.manifest.mf +++ b/src/net/java/sip/communicator/plugin/dictaccregwizz/dictaccregwizz.manifest.mf @@ -4,6 +4,7 @@ Bundle-Description: Dict account registration wizard. Bundle-Vendor: sip-communicator.org Bundle-Version: 0.0.1 Import-Package: org.osgi.framework, + net.java.dict4j, net.java.sip.communicator.util, net.java.sip.communicator.service.resources, net.java.sip.communicator.service.configuration,