1 /** 2 * Jin - a chess client for internet chess servers. 3 * More information is available at http://www.jinchess.com/. 4 * Copyright (C) 2006 Alexander Maryanovsky. 5 * All rights reserved. 6 * 7 * This program is free software; you can redistribute it and/or 8 * modify it under the terms of the GNU General Public License 9 * as published by the Free Software Foundation; either version 2 10 * of the License, or (at your option) any later version. 11 * 12 * This program is distributed in the hope that it will be useful, 13 * but WITHOUT ANY WARRANTY; without even the implied warranty of 14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 * GNU General Public License for more details. 16 * 17 * You should have received a copy of the GNU General Public License 18 * along with this program; if not, write to the Free Software 19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 20 */ 21 22 package free.jin; 23 24 import java.applet.Applet; 25 import java.awt.*; 26 import java.awt.event.ActionEvent; 27 import java.awt.event.ActionListener; 28 import java.io.*; 29 import java.net.MalformedURLException; 30 import java.net.URL; 31 import java.net.URLConnection; 32 import java.util.*; 33 import java.util.zip.ZipEntry; 34 import java.util.zip.ZipInputStream; 35 36 import free.chess.BoardImageBoardPainter; 37 import free.chess.ImagePiecePainter; 38 import free.chess.SquareImagesBoardPainter; 39 import free.jin.action.ActionInfo; 40 import free.jin.plugin.Plugin; 41 import free.jin.plugin.PluginInfo; 42 import free.util.*; 43 import free.util.audio.AppletContextAudioPlayer; 44 45 46 /** 47 * A <code>JinContext</code> implementation for running Jin as an applet. 48 */ 49 50 public class JinApplet extends Applet implements JinContext{ 51 52 53 54 /** 55 * The <code>Locale</code> for this instance of Jin. 56 */ 57 58 private Locale locale; 59 60 61 62 /** 63 * The <code>Localization</code> for this class. 64 */ 65 66 private Localization l10n; 67 68 69 70 /** 71 * Autologin parameters for Jin. They override any applet parameters in 72 * <code>getParameter</code>. 73 */ 74 75 private final Properties autologinParams = new Properties(); 76 77 78 79 /** 80 * The server we're connecting to. 81 */ 82 83 private Server server; 84 85 86 87 /** 88 * The actions we'll be using. 89 */ 90 91 private ActionInfo [] actions; 92 93 94 95 /** 96 * The plugins we'll be using. 97 */ 98 99 private PluginInfo [] plugins; 100 101 102 103 /** 104 * The preferences, created after authenticating the user. 105 */ 106 107 private Preferences prefs; 108 109 110 111 /** 112 * The list of known accounts, created after authenticating the user. 113 */ 114 115 private User [] users; 116 117 118 119 /** 120 * The username with which we authenticated the user. 121 */ 122 123 private String username; 124 125 126 127 /** 128 * The password with which we authenticated the user. 129 */ 130 131 private String password; 132 133 134 135 /** 136 * Initializes the applet. 137 */ 138 init()139 public void init(){ 140 try{ 141 // Determine the locale 142 locale = determineLocale(); 143 144 // Configure the libraries the applet depends on 145 configureLibraries(); 146 147 // Load localization 148 l10n = Localization.load(JinApplet.class); 149 150 // Load the server we'll be connecting to 151 server = loadServer(); 152 153 // Load the actions we'll be using. 154 actions = loadActions(); 155 156 // Load the plugins we'll be running. 157 plugins = loadPlugins(); 158 159 160 // Set the background color 161 String bgColorString = getParameter("bgcolor"); 162 if (bgColorString != null) 163 setBackground(new Color(0xff000000 | Integer.parseInt(bgColorString, 16))); 164 165 setLayout(new FlowLayout()); 166 add(new UserAuthPanel()); 167 } catch (Throwable t){ 168 createErrorUI(t); 169 } 170 } 171 172 173 174 /** 175 * Restarts JinApplet. This is called when the user closes Jin and is supposed 176 * to bring the applet to its initial state - ready to accept a new username 177 * and password. 178 */ 179 restart()180 private void restart(){ 181 try{ 182 username = null; 183 password = null; 184 185 removeAll(); 186 autologinParams.clear(); 187 188 // The server needs to be recreated because the user might use a different 189 // account after restarting Jin, which means a different guest User. 190 server = loadServer(); 191 192 add(new UserAuthPanel()); 193 validate(); 194 } catch (Throwable t){ 195 createErrorUI(t); 196 } 197 } 198 199 200 201 /** 202 * Starts Jin with the specified application <code>Preferences</code> and list 203 * of known users. Autologin parameters are set to login with the specified 204 * username and password. 205 */ 206 start(Preferences prefs, User guestUser, User [] users, String username, String password)207 private void start(Preferences prefs, User guestUser, User [] users, String username, String password){ 208 this.username = username; 209 this.password = password; 210 this.prefs = prefs; 211 this.users = users; 212 213 server.setGuestUser(guestUser); 214 215 autologinParams.put("login.username", username); 216 autologinParams.put("login.password", password); 217 autologinParams.put("autologin", "true"); 218 219 Jin.createInstance(this); 220 Jin.getInstance().start(); 221 } 222 223 224 225 /** 226 * Starts Jin and logs on as a guest. 227 */ 228 startAsGuest()229 private void startAsGuest(){ 230 this.username = null; 231 this.password = null; 232 this.prefs = Preferences.createNew(); 233 this.users = new User[0]; 234 235 server.setGuestUser(null); 236 237 autologinParams.put("login.guest", "true"); 238 autologinParams.put("autologin", "true"); 239 240 Jin.createInstance(this); 241 Jin.getInstance().start(); 242 } 243 244 245 246 /** 247 * Determines the locale for this instance of Jin. 248 */ 249 determineLocale()250 private Locale determineLocale(){ 251 String language = getParameter("locale.language"); 252 String country = getParameter("locale.country"); 253 String variant = getParameter("locale.variant"); 254 255 language = language == null ? "" : language; 256 country = country == null ? "" : country; 257 variant = variant == null ? "" : variant; 258 259 return new Locale(language, country, variant); 260 } 261 262 263 264 /** 265 * Configures various libraries the applet uses. 266 */ 267 configureLibraries()268 private void configureLibraries(){ 269 Localization.setAppLocale(locale); 270 BrowserControl.setAppletContext(getAppletContext()); 271 AppletContextAudioPlayer.setAppletContext(getAppletContext()); 272 ImagePiecePainter.setAsyncImageLoad(true); 273 BoardImageBoardPainter.setAsyncImageLoad(true); 274 SquareImagesBoardPainter.setAsyncImageLoad(true); 275 } 276 277 278 279 /** 280 * Creates and returns the <code>Server</code> object for the server we'll be 281 * connecting to. 282 */ 283 loadServer()284 private Server loadServer() throws ClassNotFoundException, 285 InstantiationException, IllegalAccessException{ 286 287 String className = getParameter("server.classname"); 288 if (className == null) 289 throw new IllegalStateException("No server.classname parameter specified"); 290 291 Server server = (Server)Class.forName(className).newInstance(); 292 293 server.setHost(getDocumentBase().getHost()); 294 295 String portString = getParameter("port"); 296 if (portString != null) 297 server.setPort(Integer.parseInt(portString)); 298 299 return server; 300 } 301 302 303 304 /** 305 * Loads the actions we'll be using. 306 */ 307 loadActions()308 private ActionInfo [] loadActions() throws IOException, ClassNotFoundException{ 309 String actionsCount = getParameter("actions.count"); 310 if (actionsCount == null) 311 throw new IllegalStateException("No actions.count parameter specified"); 312 313 ActionInfo [] actions = new ActionInfo[Integer.parseInt(actionsCount)]; 314 315 for (int i = 0; i < actions.length; i++){ 316 String className = getParameter("actions." + i + ".classname"); 317 if (className == null) 318 throw new IllegalStateException("Missing classname for action No. " + i); 319 320 // See the long comment about the definition file in loadPlugins 321 322 Class actionClass = Class.forName(className); 323 324 InputStream actionPrefsIn = actionClass.getResourceAsStream("preferences"); 325 Preferences actionPrefs = (actionPrefsIn == null ? Preferences.createNew() : Preferences.load(actionPrefsIn)); 326 327 if (actionPrefsIn != null) 328 actionPrefsIn.close(); 329 330 actions[i] = new ActionInfo(actionClass, actionPrefs); 331 } 332 333 return actions; 334 } 335 336 337 338 /** 339 * Loads the plugins we'll be using. 340 */ 341 loadPlugins()342 private PluginInfo [] loadPlugins() throws IOException, ClassNotFoundException{ 343 String pluginsCount = getParameter("plugins.count"); 344 if (pluginsCount == null) 345 throw new IllegalStateException("No plugins.count parameter specified"); 346 347 PluginInfo [] plugins = new PluginInfo[Integer.parseInt(pluginsCount)]; 348 349 for (int i = 0; i < plugins.length; i++){ 350 String className = getParameter("plugins." + i + ".classname"); 351 if (className == null) 352 throw new IllegalStateException("Missing classname for plugin No. " + i); 353 354 // We should actually read the definition file here. 355 // Currently (30.07.2004) the definition file only contains the class name 356 // of the plugin, so we don't really need it, but it might contain 357 // additional information in the future, which we might need to read. 358 // It seems that we can't access the definition file when running as an 359 // applet because Class.getResourceAsStream("/definition") will return 360 // the first "definition" file it sees on the applet classpath (as 361 // specified by the ARCHIVE tag. 362 363 Class pluginClass = Class.forName(className); 364 365 InputStream pluginPrefsIn = pluginClass.getResourceAsStream("preferences"); 366 Preferences pluginPrefs = (pluginPrefsIn == null ? Preferences.createNew() : Preferences.load(pluginPrefsIn)); 367 368 if (pluginPrefsIn != null) 369 pluginPrefsIn.close(); 370 371 plugins[i] = new PluginInfo(pluginClass, pluginPrefs); 372 } 373 374 return plugins; 375 } 376 377 378 379 /** 380 * Returns the locale for this instance of Jin. 381 */ 382 getLocale()383 public Locale getLocale(){ 384 return locale; 385 } 386 387 388 389 /** 390 * Overrides the <code>getParameter</code> method to allow us to add our own 391 * parameters, via the props Properties object. 392 */ 393 getParameter(String paramName)394 public String getParameter(String paramName){ 395 String paramValue = autologinParams.getProperty(paramName); 396 397 return paramValue == null ? super.getParameter(paramName) : paramValue; 398 } 399 400 401 402 /** 403 * Returns the application-wide preferences. 404 */ 405 getPrefs()406 public Preferences getPrefs(){ 407 return prefs; 408 } 409 410 411 412 /** 413 * Returns all the resources of the specified type. 414 * See {@link JinContext#getResources(String, Plugin)} for more information. 415 */ 416 getResources(String resourceType, Plugin plugin)417 public Map getResources(String resourceType, Plugin plugin){ 418 Map resourceMap = new HashMap(); 419 420 try{ 421 URL codeBase = getCodeBase(); 422 URL definitionsFileURL = new URL(codeBase, "resources/" + resourceType + "/definitions.zip"); 423 if (!IOUtilities.isURLCached(definitionsFileURL)) 424 IOUtilities.cacheURL(definitionsFileURL); 425 ZipInputStream zip = new ZipInputStream(IOUtilities.inputStreamForURL(definitionsFileURL, true)); 426 ZipEntry entry; 427 while ((entry = zip.getNextEntry()) != null){ 428 String entryName = entry.getName(); 429 if (!entryName.endsWith("definition") || entry.isDirectory()) 430 continue; 431 String resourcePath = entryName.substring(0, entryName.length() - "/definition".length()); 432 byte [] data = IOUtilities.readToEnd(zip); 433 URL resourceURL = new URL(codeBase, "resources/" + resourceType + "/" + resourcePath + "/"); 434 IOUtilities.cacheData(new URL(resourceURL, "definition"), data); 435 Resource resource = loadResource(resourceURL, plugin); 436 if (resource != null) 437 resourceMap.put(resource.getId(), resource); 438 } 439 } catch (IOException e){ 440 e.printStackTrace(); 441 } 442 443 return resourceMap; 444 } 445 446 447 448 /** 449 * Returns the resource with the specified type and id, or <code>null</code> 450 * if the resource can't be loaded. 451 */ 452 getResource(String type, String id, Plugin plugin)453 public Resource getResource(String type, String id, Plugin plugin){ 454 try{ 455 URL resourceURL = new URL(getCodeBase(), "resources/" + type + "/" + id + "/"); 456 return loadResource(resourceURL, plugin); 457 } catch (IOException e){ 458 e.printStackTrace(); 459 return null; 460 } 461 } 462 463 464 465 466 /** 467 * Loads a single resource from the specified URL. Returns <code>null</code> 468 * if unsuccessful. 469 */ 470 loadResource(URL url, Plugin plugin)471 private Resource loadResource(URL url, Plugin plugin) throws IOException{ 472 URL defURL = new URL(url, "definition"); 473 if (!IOUtilities.isURLCached(defURL)) 474 IOUtilities.cacheURL(defURL); 475 476 Properties def = IOUtilities.loadProperties(defURL, true); 477 String classname = def.getProperty("classname"); 478 if (classname == null) 479 return null; 480 481 try{ 482 // We need to load it with the plugin's classloader because the 483 // resource may be of a type which is a part of the plugin. 484 Class resourceClass = plugin.getClass().getClassLoader().loadClass(classname); 485 Resource resource = (Resource)resourceClass.newInstance(); 486 if (resource.load(url, plugin)) 487 return resource; 488 else 489 return null; 490 } catch (ClassNotFoundException e){e.printStackTrace(); return null;} 491 catch (InstantiationException e){e.printStackTrace(); return null;} 492 catch (IllegalAccessException e){e.printStackTrace(); return null;} 493 } 494 495 496 497 /** 498 * The thread that uploads the user settings. <code>null</code> when none. 499 */ 500 501 private Thread settingsUploadThread = null; 502 503 504 505 /** 506 * The dialog displayed to the user while settings are uploaded. 507 */ 508 509 private Dialog settingsUploadDialog = null; 510 511 512 513 /** 514 * Uploads preferences and reinitializes the applet, so that it's ready to 515 * go again. 516 */ 517 shutdown()518 public void shutdown(){ 519 if (username != null){ // Not logged in as guest 520 521 settingsUploadThread = new Thread(){ 522 public void run(){ 523 try{ 524 String result = null; 525 while (!"OK".equals(result = uploadSettings())){ 526 // Hackish, I know 527 if (result.toLowerCase().indexOf("password") != -1){ 528 if (!showPasswordDialog(result)) 529 break; 530 } 531 else 532 showErrorDialog(result); 533 } 534 } catch (IOException e){ 535 ByteArrayOutputStream buf = new ByteArrayOutputStream(); 536 e.printStackTrace(new PrintStream(buf)); 537 showErrorDialog(buf.toString()); 538 } 539 finally{ 540 synchronized(JinApplet.this){ 541 if (settingsUploadThread == Thread.currentThread()){ 542 settingsUploadThread = null; 543 if (settingsUploadDialog != null){ 544 settingsUploadDialog.dispose(); 545 settingsUploadDialog = null; 546 } 547 } 548 } 549 } 550 } 551 552 private void showErrorDialog(String message){ 553 Dialog errorDialog = 554 new SettingsUploadErrorDialog(AWTUtilities.frameForComponent(JinApplet.this), message); 555 AWTUtilities.centerWindow(errorDialog, JinApplet.this); 556 errorDialog.setVisible(true); 557 } 558 559 /** 560 * Shows a password dialog and returns whether preferences upload should 561 * be retried. 562 */ 563 564 private boolean showPasswordDialog(String message){ 565 PasswordDialog passDialog = 566 new PasswordDialog(AWTUtilities.frameForComponent(JinApplet.this), message, username); 567 AWTUtilities.centerWindow(passDialog, JinApplet.this); 568 569 boolean result = passDialog.shouldRetry(); 570 571 if (result){ 572 username = passDialog.getUsername(); 573 password = passDialog.getPassword(); 574 } 575 return result; 576 } 577 }; 578 579 settingsUploadDialog = new SettingsUploadDialog(AWTUtilities.frameForComponent(this)){ 580 public void addNotify(){ 581 super.addNotify(); 582 if ((settingsUploadThread != null) && !settingsUploadThread.isAlive()) 583 settingsUploadThread.start(); 584 } 585 public void canceled(){ 586 synchronized(JinApplet.this){ 587 this.dispose(); 588 } 589 } 590 }; 591 592 AWTUtilities.centerWindow(settingsUploadDialog, this); 593 settingsUploadDialog.setVisible(true); 594 } 595 596 restart(); 597 } 598 599 600 601 602 /** 603 * Stores the user settings. 604 */ 605 uploadSettings()606 private String uploadSettings() throws IOException{ 607 URL savePrefsUrl = getPrefsUploadUrl(); 608 URLConnection conn = savePrefsUrl.openConnection(); 609 conn.setDoOutput(true); 610 conn.setRequestProperty("Content-type", "application/binary"); 611 612 DataOutputStream out = new DataOutputStream(new BufferedOutputStream(conn.getOutputStream())); 613 out.writeBytes(username + "\n"); 614 out.writeBytes(password + "\n"); 615 616 617 // Write application-wide prefs 618 ByteArrayOutputStream buf = new ByteArrayOutputStream(); 619 prefs.save(buf); 620 out.writeInt(buf.size()); 621 buf.writeTo(out); 622 623 // Write guest 624 writeUser(out, server.getGuest()); 625 626 // Write users 627 out.writeInt(users.length); 628 for (int i = 0; i < users.length; i++) 629 writeUser(out, users[i]); 630 631 out.writeBytes("PREFS_UPLOAD_END"); 632 633 out.close(); 634 635 conn.connect(); 636 637 buf.reset(); 638 IOUtilities.pump(conn.getInputStream(), buf); 639 String result = new String(buf.toByteArray()); 640 return result; 641 } 642 643 644 645 /** 646 * Writes (for storage purposes) the information about the specified user into 647 * the specified output stream. 648 */ 649 writeUser(DataOutputStream out, User user)650 private void writeUser(DataOutputStream out, User user) throws IOException{ 651 out.writeUTF(user.getUsername()); 652 653 ByteArrayOutputStream buf = new ByteArrayOutputStream(); 654 user.getPrefs().save(buf); 655 out.writeInt(buf.size()); 656 buf.writeTo(out); 657 658 // Todo: add storing user files 659 } 660 661 662 663 /** 664 * Returns an array containing the server we're connecting to. 665 */ 666 getServers()667 public Server [] getServers(){ 668 return new Server[]{server}; 669 } 670 671 672 673 /** 674 * Returns the list of known user's accounts on the server. 675 */ 676 getUsers()677 public User [] getUsers(){ 678 return users; 679 } 680 681 682 683 /** 684 * Sets the list of known user accounts, so that they can be uploaded as a 685 * part of the preferences. 686 */ 687 setUsers(User [] users)688 public void setUsers(User [] users){ 689 this.users = users; 690 } 691 692 693 694 /** 695 * Returns the descriptions of actions for the specified server. 696 */ 697 getActions(Server server)698 public ActionInfo [] getActions(Server server){ 699 if (server != this.server) 700 throw new IllegalArgumentException("Unknown server: " + server); 701 702 return actions; 703 } 704 705 706 707 /** 708 * Returns the descriptions of plugins for the specified server. 709 */ 710 getPlugins(Server server)711 public PluginInfo [] getPlugins(Server server){ 712 if (server != this.server) 713 throw new IllegalArgumentException("Unknown server: " + server); 714 715 return plugins; 716 } 717 718 719 720 /** 721 * Returns <code>true</code>. 722 */ 723 isSavePrefsCapable()724 public boolean isSavePrefsCapable(){ 725 return true; 726 } 727 728 729 730 /** 731 * Returns text warning the user about saving his password and asking him to 732 * confirm it. 733 */ 734 getPasswordSaveWarning()735 public String getPasswordSaveWarning(){ 736 try{ 737 boolean isSecure = getPrefsDownloadUrl().getProtocol().equals("https") && 738 getPrefsUploadUrl().getProtocol().equals("https"); 739 740 if (isSecure) 741 return null; 742 else 743 return l10n.getString("passwordSaveWarning"); 744 } catch (MalformedURLException e){ 745 e.printStackTrace(); 746 return "Error: Your password will not be stored due to a configuration problem."; 747 } 748 } 749 750 751 752 /** 753 * Returns <code>false</code>. 754 */ 755 isUserExtensible()756 public boolean isUserExtensible(){ 757 return false; 758 } 759 760 761 762 /** 763 * Creates UI which informs the user that the specified error has occurred. 764 */ 765 createErrorUI(Throwable t)766 private void createErrorUI(Throwable t){ 767 removeAll(); 768 769 setLayout(new BorderLayout()); 770 771 add(new Label(l10n.getString("errorLabel.text")), BorderLayout.NORTH); 772 773 ByteArrayOutputStream buf = new ByteArrayOutputStream(); 774 t.printStackTrace(new PrintStream(buf)); 775 TextArea stackTraceArea = new TextArea(buf.toString()); 776 777 add(stackTraceArea, BorderLayout.CENTER); 778 doLayout(); 779 } 780 781 782 783 /** 784 * Returns the URL from which we download user settings. 785 */ 786 getPrefsDownloadUrl()787 private URL getPrefsDownloadUrl() throws MalformedURLException{ 788 URL url = new URL(getDocumentBase(), getParameter("loadPrefsURL")); 789 return new URL(getParameter("prefsProtocol"), url.getHost(), url.getPort(), url.getFile()); 790 } 791 792 793 794 /** 795 * Returns the URL to which we upload user settings. 796 */ 797 getPrefsUploadUrl()798 private URL getPrefsUploadUrl() throws MalformedURLException{ 799 URL url = new URL(getDocumentBase(), getParameter("savePrefsURL")); 800 return new URL(getParameter("prefsProtocol"), url.getHost(), url.getPort(), url.getFile()); 801 } 802 803 804 805 /** 806 * A panel which asks the user to specify his username and password. When 807 * the user submits those, the settings for that user are retrieved and 808 * the <code>start(Preferences, User, User [], String, String)</code> method 809 * of <code>JinApplet</code> is invoked. If the user chooses to continue as 810 * guest, the <code>startAsGuest</code> method of <code>JinApplet</code> is 811 * invoked. 812 */ 813 814 private class UserAuthPanel extends Panel implements Runnable{ 815 816 817 818 /** 819 * The username text field. 820 */ 821 822 private final TextField usernameField; 823 824 825 826 /** 827 * The password field. 828 */ 829 830 private final TextField passwordField; 831 832 833 834 /** 835 * The status label. 836 */ 837 838 private final Label statusLabel; 839 840 841 842 /** 843 * The login button. 844 */ 845 846 private Button loginButton; 847 848 849 850 /** 851 * The "login as guest" button. 852 */ 853 854 private Button guestButton; 855 856 857 858 /** 859 * The thread authenticating the user and retrieving his settings. 860 */ 861 862 private Thread authThread = null; 863 864 865 866 /** 867 * Creates a new <code>UserAuthPanel</code>. 868 */ 869 UserAuthPanel()870 public UserAuthPanel(){ 871 usernameField = new TextField(20); 872 passwordField = new TextField(20); 873 statusLabel = new Label(); 874 875 passwordField.setEchoChar('*'); 876 877 createUI(); 878 } 879 880 881 882 /** 883 * Sets the status to the specified value (sets the status label). 884 */ 885 setStatus(String status, Color color)886 private void setStatus(String status, Color color){ 887 statusLabel.setForeground(color); 888 statusLabel.setText(status); 889 } 890 891 892 893 /** 894 * Builds the ui of this panel. 895 */ 896 createUI()897 private void createUI(){ 898 this.setLayout(new BorderLayout(15, 15)); 899 this.add(BorderLayout.NORTH, 900 new Label(l10n.getString("instructionsLabel.text"))); 901 902 Panel userInfoPanel = new Panel(new BorderLayout()); 903 Panel labelsPanel = new Panel(new GridLayout(2, 1, 10, 10)); 904 Panel textFieldsPanel = new Panel(new GridLayout(2, 1, 10, 10)); 905 906 labelsPanel.add(new Label(l10n.getString("usernameLabel.text"))); 907 textFieldsPanel.add(usernameField); 908 909 labelsPanel.add(new Label(l10n.getString("passwordLabel.text"))); 910 textFieldsPanel.add(passwordField); 911 912 userInfoPanel.add(BorderLayout.WEST, labelsPanel); 913 914 Panel textFieldsWrapperPanel = new Panel(new BorderLayout()); 915 textFieldsWrapperPanel.add(BorderLayout.WEST, textFieldsPanel); 916 userInfoPanel.add(BorderLayout.CENTER, textFieldsWrapperPanel); 917 918 this.add(BorderLayout.CENTER, userInfoPanel); 919 920 Panel statusAndButtonsPanel = new Panel(new GridLayout(2, 1)); 921 922 statusAndButtonsPanel.add(statusLabel); 923 924 loginButton = new Button(l10n.getString("loginButton.text")); 925 guestButton = new Button(l10n.getString("loginAsGuestButton.text")); 926 927 Panel buttonsPanel = new Panel(new FlowLayout(FlowLayout.CENTER)); 928 buttonsPanel.add(loginButton); 929 buttonsPanel.add(guestButton); 930 statusAndButtonsPanel.add(buttonsPanel); 931 932 this.add(BorderLayout.SOUTH, statusAndButtonsPanel); 933 934 ActionListener loginListener = new ActionListener(){ 935 public void actionPerformed(ActionEvent evt){ 936 String username = usernameField.getText(); 937 String password = passwordField.getText(); 938 939 if ((username == null) || "".equals(username)){ 940 setStatus(l10n.getString("usernameUnspecifiedError"), Color.red); 941 usernameField.requestFocus(); 942 } 943 else if ((password == null) || "".equals(password)){ 944 setStatus(l10n.getString("passwordUnspecifiedError"), Color.red); 945 passwordField.requestFocus(); 946 } 947 else{ 948 loginButton.setEnabled(false); 949 guestButton.setEnabled(false); 950 951 retrievePrefs(); 952 } 953 } 954 }; 955 loginButton.addActionListener(loginListener); 956 // We do this to imitate a "default" button 957 usernameField.addActionListener(loginListener); 958 passwordField.addActionListener(loginListener); 959 960 guestButton.addActionListener(new ActionListener(){ 961 public void actionPerformed(ActionEvent evt){ 962 setStatus(l10n.getString("startingJinStatus"), Color.black); 963 loginButton.setEnabled(false); 964 guestButton.setEnabled(false); 965 startAsGuest(); 966 setStatus(l10n.getString("stayOnPageStatus"), Color.black); 967 } 968 }); 969 } 970 971 972 973 /** 974 * Have we been painted already? 975 */ 976 977 private boolean isPainted = false; 978 979 980 981 /** 982 * Set focus to the username field on the first paint. 983 */ 984 paint(Graphics g)985 public void paint(Graphics g){ 986 super.paint(g); 987 988 if (!isPainted){ 989 isPainted = true; 990 usernameField.requestFocus(); 991 } 992 } 993 994 995 996 /** 997 * Authenticates the user and retrieves the preferences from the server. 998 */ 999 retrievePrefs()1000 private synchronized void retrievePrefs(){ 1001 if (authThread == null){ 1002 authThread = new Thread(this); 1003 authThread.start(); 1004 } 1005 } 1006 1007 1008 1009 /** 1010 * Connects to the server and retrieves the preferences. 1011 */ 1012 run()1013 public void run(){ 1014 try{ 1015 String username = usernameField.getText(); 1016 String password = passwordField.getText(); 1017 1018 setStatus(l10n.getString("connectingStatus"), Color.black); 1019 1020 URL loadPrefsUrl = getPrefsDownloadUrl(); 1021 URLConnection conn = loadPrefsUrl.openConnection(); 1022 conn.setDoOutput(true); 1023 conn.setRequestProperty("Content-type", "application/binary"); 1024 DataOutputStream out = new DataOutputStream(conn.getOutputStream()); 1025 out.writeBytes(username + "\n"); 1026 out.writeBytes(password + "\n"); 1027 1028 out.close(); 1029 1030 conn.connect(); 1031 1032 setStatus(l10n.getString("authenticatingStatus"), Color.black); 1033 1034 InputStream in = new BufferedInputStream(conn.getInputStream()); 1035 DataInputStream dataIn = new DataInputStream(in); 1036 1037 Preferences prefs; 1038 User guest; 1039 User [] users; 1040 1041 // Read return code 1042 String returnCode = dataIn.readLine(); 1043 1044 if ("OK".equals(returnCode)){ 1045 setStatus(l10n.getString("retrievingPreferencesStatus"), Color.black); 1046 1047 // Read application preferences 1048 int appPrefsLength = dataIn.readInt(); 1049 prefs = Preferences.load(new ByteArrayInputStream(IOUtilities.read(in, appPrefsLength))); 1050 1051 // Read guest user 1052 guest = loadUser(dataIn); 1053 1054 // Number of known users 1055 int usersCount = dataIn.readInt(); 1056 users = new User[usersCount]; 1057 1058 for (int i = 0; i < users.length; i++) 1059 users[i] = loadUser(dataIn); 1060 } else if ("NOPREFS".equals(returnCode)){ // A new user 1061 prefs = Preferences.createNew(); 1062 guest = null; 1063 users = new User[0]; 1064 } 1065 else{ // An error 1066 setStatus(returnCode, Color.red); 1067 System.out.println(returnCode); 1068 IOUtilities.pump(in, System.out); 1069 1070 loginButton.setEnabled(true); 1071 guestButton.setEnabled(true); 1072 return; 1073 } 1074 1075 setStatus(l10n.getString("startingJinStatus"), Color.black); 1076 1077 start(prefs, guest, users, username, password); 1078 1079 setStatus(l10n.getString("stayOnPageStatus"), Color.black); 1080 } catch (IOException e){ 1081 e.printStackTrace(); 1082 createErrorUI(e); 1083 } 1084 catch (RuntimeException e){ 1085 e.printStackTrace(); 1086 createErrorUI(e); 1087 } 1088 finally{ 1089 synchronized(this){ 1090 authThread = null; 1091 } 1092 } 1093 } 1094 1095 1096 1097 /** 1098 * Creates a User from the specified <code>InputStream</code>. 1099 */ 1100 loadUser(DataInputStream in)1101 private User loadUser(DataInputStream in) throws IOException{ 1102 String username = in.readUTF(); 1103 int prefsLength = in.readInt(); 1104 Preferences prefs = Preferences.load(new ByteArrayInputStream(IOUtilities.read(in, prefsLength))); 1105 Hashtable files = new Hashtable(); // TODO: Add loading/saving files 1106 1107 return new User(server, username, prefs, files); 1108 } 1109 1110 1111 } 1112 1113 1114 1115 /** 1116 * A dialog which is displayed while the user settings are uploaded. 1117 */ 1118 1119 private abstract class SettingsUploadDialog extends Dialog{ 1120 1121 1122 1123 /** 1124 * Creates a new <code>SettingsUploadDialog</code> with the specified 1125 * parent Frame. 1126 */ 1127 SettingsUploadDialog(Frame parent)1128 public SettingsUploadDialog(Frame parent){ 1129 super(parent, "", true); 1130 1131 setTitle(l10n.getString("prefsUploadDialog.title")); 1132 1133 this.setLayout(new GridLayout(2, 1)); 1134 1135 this.add(new Label(l10n.getString("prefsUploadDialog.message"))); 1136 1137 Button button = new Button(l10n.getString("prefsUploadDialog.cancelButton.text")); 1138 Panel buttonPanel = new Panel(new FlowLayout()); 1139 buttonPanel.add(button); 1140 this.add(buttonPanel); 1141 1142 button.addActionListener(new ActionListener(){ 1143 public void actionPerformed(ActionEvent evt){ 1144 canceled(); 1145 } 1146 }); 1147 } 1148 1149 1150 1151 /** 1152 * Gets called when the user pressed the "cancel" button. 1153 */ 1154 canceled()1155 public abstract void canceled(); 1156 1157 1158 } 1159 1160 1161 1162 /** 1163 * A dialog for displaying the error that occurred while uploading user 1164 * settings. 1165 */ 1166 1167 private class SettingsUploadErrorDialog extends Dialog{ 1168 1169 1170 1171 /** 1172 * Creates a new <code>SettingsUploadErrorDialog</code> with the specified 1173 * parent frame and error text. 1174 */ 1175 SettingsUploadErrorDialog(Frame parent, String errorMessage)1176 public SettingsUploadErrorDialog(Frame parent, String errorMessage){ 1177 super(parent, l10n.getString("prefsUploadErrorDialog.title"), true); 1178 1179 createUI(errorMessage); 1180 } 1181 1182 1183 1184 /** 1185 * Creates the UI of this dialog. 1186 */ 1187 createUI(String errorMessage)1188 private void createUI(String errorMessage){ 1189 this.setLayout(new BorderLayout(5, 5)); 1190 1191 this.add(BorderLayout.NORTH, new Label(l10n.getString("prefsUploadErrorDialog.message"))); 1192 1193 TextArea errorArea = new TextArea(errorMessage, 3, 40); 1194 1195 errorArea.setEditable(false); 1196 this.add(BorderLayout.CENTER, errorArea); 1197 1198 Button closeButton = new Button(l10n.getString("prefsUploadErrorDialog.closeButton.text")); 1199 Panel buttonPanel = new Panel(new FlowLayout()); 1200 buttonPanel.add(closeButton); 1201 this.add(BorderLayout.SOUTH, buttonPanel); 1202 1203 closeButton.addActionListener(new ActionListener(){ 1204 public void actionPerformed(ActionEvent evt){ 1205 dispose(); 1206 } 1207 }); 1208 } 1209 1210 1211 1212 } 1213 1214 1215 1216 /** 1217 * A dialog which asks the user to input his password and retry uploading the 1218 * preferences. 1219 */ 1220 1221 private class PasswordDialog extends Dialog{ 1222 1223 1224 /** 1225 * The username field. 1226 */ 1227 1228 private final TextField usernameField; 1229 1230 1231 1232 /** 1233 * The password field. 1234 */ 1235 1236 private final TextField passwordField; 1237 1238 1239 1240 /** 1241 * Whether prefs upload should be retried. 1242 */ 1243 1244 private boolean shouldRetry; 1245 1246 1247 1248 /** 1249 * Creates a new <code>PasswordDialog</code> with the specified 1250 * parent frame, error text and current username. 1251 */ 1252 PasswordDialog(Frame parent, String errorMessage, String username)1253 public PasswordDialog(Frame parent, String errorMessage, String username){ 1254 super(parent, l10n.getString("passwordPrefsUploadErrorDialog.title"), true); 1255 1256 usernameField = new TextField(username); 1257 passwordField = new TextField(); 1258 passwordField.setEchoChar('*'); 1259 1260 createUI(errorMessage); 1261 } 1262 1263 1264 1265 /** 1266 * Creates the UI of this dialog. 1267 */ 1268 createUI(String errorMessage)1269 private void createUI(String errorMessage){ 1270 this.setLayout(new BorderLayout(5, 5)); 1271 1272 this.add(BorderLayout.NORTH, new Label(l10n.getString("passwordPrefsUploadErrorDialog.message"))); 1273 1274 Panel centerPanel = new Panel(new BorderLayout(5, 5)); 1275 1276 TextArea errorArea = new TextArea(errorMessage, 3, 40); 1277 errorArea.setEditable(false); 1278 centerPanel.add(BorderLayout.CENTER, errorArea); 1279 1280 Panel userInfoPanel = new Panel(new BorderLayout()); 1281 Panel labelsPanel = new Panel(new GridLayout(2, 1, 10, 10)); 1282 Panel textFieldsPanel = new Panel(new GridLayout(2, 1, 10, 10)); 1283 1284 labelsPanel.add(new Label(l10n.getString("usernameLabel.text"))); 1285 textFieldsPanel.add(usernameField); 1286 1287 labelsPanel.add(new Label(l10n.getString("passwordLabel.text"))); 1288 textFieldsPanel.add(passwordField); 1289 1290 userInfoPanel.add(BorderLayout.WEST, labelsPanel); 1291 userInfoPanel.add(BorderLayout.CENTER, textFieldsPanel); 1292 centerPanel.add(BorderLayout.SOUTH, userInfoPanel); 1293 this.add(BorderLayout.CENTER, centerPanel); 1294 1295 1296 Button retryButton = new Button(l10n.getString("passwordPrefsUploadErrorDialog.retryButton.text")); 1297 Button closeButton = new Button(l10n.getString("passwordPrefsUploadErrorDialog.closeButton.text")); 1298 1299 Panel buttonPanel = new Panel(new FlowLayout()); 1300 buttonPanel.add(retryButton); 1301 buttonPanel.add(closeButton); 1302 this.add(BorderLayout.SOUTH, buttonPanel); 1303 1304 retryButton.addActionListener(new ActionListener(){ 1305 public void actionPerformed(ActionEvent evt){ 1306 shouldRetry = true; 1307 dispose(); 1308 } 1309 }); 1310 1311 closeButton.addActionListener(new ActionListener(){ 1312 public void actionPerformed(ActionEvent evt){ 1313 shouldRetry = false; 1314 dispose(); 1315 } 1316 }); 1317 } 1318 1319 1320 1321 /** 1322 * Displays the dialog and returns whether the user asked to retry 1323 * prefs upload. 1324 */ 1325 shouldRetry()1326 public boolean shouldRetry(){ 1327 this.setVisible(true); 1328 return shouldRetry; 1329 } 1330 1331 1332 1333 /** 1334 * Returns the username specified by the user. 1335 */ 1336 getUsername()1337 public String getUsername(){ 1338 return usernameField.getText(); 1339 } 1340 1341 1342 1343 /** 1344 * Returns the password specified by the user. 1345 */ 1346 getPassword()1347 public String getPassword(){ 1348 return passwordField.getText(); 1349 } 1350 1351 1352 } 1353 1354 }