001 /*
002 Copyright (C) 2003 Adam Olsen
003
004 This program is free software; you can redistribute it and/or modify
005 it under the terms of the GNU General Public License as published by
006 the Free Software Foundation; either version 1, or (at your option)
007 any later version.
008
009 This program is distributed in the hope that it will be useful,
010 but WITHOUT ANY WARRANTY; without even the implied warranty of
011 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
012 GNU General Public License for more details.
013
014 You should have received a copy of the GNU General Public License
015 along with this program; if not, write to the Free Software
016 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
017 */
018
019 package com.valhalla.pluginmanager;
020
021 import java.awt.BorderLayout;
022 import java.awt.Dimension;
023 import java.awt.event.ActionEvent;
024 import java.awt.event.ActionListener;
025 import java.io.BufferedReader;
026 import java.io.File;
027 import java.io.InputStreamReader;
028 import java.io.PrintWriter;
029 import java.net.Socket;
030 import java.net.URL;
031 import java.util.ArrayList;
032 import java.util.Hashtable;
033 import java.util.Locale;
034 import java.util.Properties;
035 import java.util.ResourceBundle;
036
037 import javax.swing.*;
038 import javax.swing.SwingUtilities;
039 import javax.swing.event.ListSelectionEvent;
040 import javax.swing.event.ListSelectionListener;
041
042 import com.valhalla.gui.DialogTracker;
043 import com.valhalla.gui.Standard;
044
045 /**
046 * Allows the user to download, install, and upgrade plugins all from within the
047 * application that the plugins are used in
048 *
049 * @author Adam Olsen
050 * @version 1.0
051 */
052 public class PluginManager extends JFrame {
053 private ResourceBundle resources = ResourceBundle.getBundle(
054 "PluginManager", Locale.getDefault());
055
056 private String mirror;
057
058 private String script;
059
060 private String installDir;
061
062 private ArrayList pluginList = null;
063
064 private ArrayList installedPlugins = null;
065
066 private JLabel statusLabel = new JLabel("<html><b>"
067 + resources.getString("status") + "</b>: ");
068
069 private JTabbedPane pane = new JTabbedPane();
070
071 private JButton closeButton = new JButton(resources
072 .getString("closeButton"));
073
074 private JPanel mainPanel;
075
076 private JPanel bottomPanel = new JPanel(new BorderLayout(5, 5));
077
078 private PluginManagerPanel listPanel = new PluginManagerPanel(this, false);
079
080 private PluginManagerPanel managePanel = new PluginManagerPanel(this, true);
081
082 private JButton unloadButton = new JButton(resources.getString("unload"));
083
084 private JButton loadButton = new JButton(resources.getString("load"));
085
086 /**
087 * Constructs the Plugin Manager
088 *
089 * @param mirror
090 * the mirror to download the plugins from
091 * @param script
092 * the script to get after connecting
093 * @param installDir
094 * the location to install the plugins to
095 */
096 public PluginManager(String mirror, String script, String installDir) {
097 setTitle(resources.getString("pluginManager"));
098 this.mirror = mirror;
099 this.script = script;
100 this.installDir = installDir;
101
102 initComponents();
103 DialogTracker.addDialog(this, false, true);
104 downloadPluginList();
105 }
106
107 /**
108 * @return the download mirror
109 */
110 String getMirror() {
111 return mirror;
112 }
113
114 /**
115 * @return the download script
116 */
117 String getScript() {
118 return script;
119 }
120
121 /**
122 * @return the install dir
123 */
124 String getInstallDir() {
125 return installDir;
126 }
127
128 /**
129 * Sets up UI components
130 */
131 private void initComponents() {
132 closeButton.addActionListener(new ActionListener() {
133 public void actionPerformed(ActionEvent e) {
134 DialogTracker.removeDialog(PluginManager.this);
135 }
136 });
137
138 mainPanel = (JPanel) getContentPane();
139 mainPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
140 mainPanel.setLayout(new BorderLayout(5, 5));
141 loadButton.setEnabled(false);
142 unloadButton.setEnabled(false);
143
144 ListActionListener listener = new ListActionListener();
145
146 managePanel.getButton().addActionListener(listener);
147 managePanel.getTable().getSelectionModel().addListSelectionListener(
148 new HighlightListener());
149 unloadButton.addActionListener(listener);
150 loadButton.addActionListener(listener);
151
152 listPanel.getButton().addActionListener(listener);
153
154 // set up the manage panel
155 managePanel.setButtonText(resources.getString("removePlugins"));
156 JPanel bPanel = managePanel.getButtonPanel();
157 bPanel.add(Box.createRigidArea(new Dimension(5, 0)));
158 bPanel.add(unloadButton);
159 bPanel.add(loadButton);
160 bPanel.validate();
161
162 pane.add(managePanel, resources.getString("managePlugins"));
163 pane.add(listPanel, resources.getString("installUpgradePlugins"));
164
165 mainPanel.add(pane, BorderLayout.CENTER);
166 mainPanel.add(bottomPanel, BorderLayout.SOUTH);
167
168 bottomPanel.add(statusLabel, BorderLayout.CENTER);
169 bottomPanel.add(closeButton, BorderLayout.EAST);
170
171 pack();
172 setSize(new Dimension(520, 470));
173 setLocationRelativeTo(null);
174 }
175
176 class HighlightListener implements ListSelectionListener {
177 public void valueChanged(ListSelectionEvent e) {
178 int rows[] = managePanel.getTable().getSelectedRows();
179
180 boolean selected = (rows.length > 0);
181 managePanel.getButton().setEnabled(selected);
182 loadButton.setEnabled(selected);
183 unloadButton.setEnabled(selected);
184 }
185 }
186
187 /**
188 * Listens for a button to be clicked in one of the list panels
189 */
190 class ListActionListener implements ActionListener {
191 public void actionPerformed(ActionEvent e) {
192 if (e.getSource() == managePanel.getButton())
193 removeHandler();
194 else if (e.getSource() == listPanel.getButton())
195 installHandler(pluginList);
196 else if (e.getSource() == unloadButton) {
197 unloadPluginHandler(installedPlugins, true, managePanel);
198 managePanel.setPlugins(installedPlugins);
199 } else if (e.getSource() == loadButton) {
200 loadPluginHandler(installedPlugins, managePanel);
201 managePanel.setPlugins(installedPlugins);
202 }
203 }
204 }
205
206 private boolean checkSelectedRow(PluginManagerPanel panel, int current) {
207 if (panel == null)
208 return false;
209 JTable table = panel.getTable();
210
211 int rows[] = table.getSelectedRows();
212 for (int i = 0; i < rows.length; i++) {
213 if (current == rows[i])
214 return true;
215 }
216
217 return false;
218 }
219
220 /**
221 * loads the checked plugins
222 */
223 private void loadPluginHandler(ArrayList list, PluginManagerPanel panel) {
224 for (int i = 0; i < list.size(); i++) {
225 Properties props = (Properties) list.get(i);
226 if (props.getProperty("selected") != null
227 || checkSelectedRow(panel, i)) {
228 Hashtable loadedPlugins = PluginLoader.getInstance()
229 .getLoadedPlugins();
230 PluginJAR jar = PluginLoader.getInstance().getPlugin(
231 props.getProperty("name"));
232
233 if (jar != null && !jar.getLoaded()) {
234 jar.loadPlugin();
235 loadedPlugins.put(props.getProperty("name"), jar);
236 props.remove("selected");
237 }
238 }
239 }
240 }
241
242 /**
243 * Unloads the checked plugins
244 */
245 private void unloadPluginHandler(ArrayList list, boolean mark,
246 PluginManagerPanel panel) {
247 for (int i = 0; i < list.size(); i++) {
248 Properties props = (Properties) list.get(i);
249 if (props.getProperty("selected") != null
250 || checkSelectedRow(panel, i)) {
251 Hashtable loadedPlugins = PluginLoader.getInstance()
252 .getLoadedPlugins();
253
254 PluginJAR jar = PluginLoader.getInstance().getPlugin(
255 props.getProperty("name"));
256
257 if (jar != null) {
258 if (jar.getLoaded())
259 jar.unloadPlugin();
260 loadedPlugins.remove(props.getProperty("name"));
261
262 jar.close();
263
264 jar = null;
265 System.gc();
266 }
267
268 if (mark)
269 props.remove("selected");
270 }
271 }
272
273 }
274
275 /**
276 * Installs the plugins selected in the install panel
277 *
278 * @param list
279 * the list to download
280 */
281 private void installHandler(ArrayList list) {
282 setStatusText(resources.getString("downloading"));
283 Thread thread = new Thread(new PluginDownloaderThread(this, list));
284 thread.start();
285 }
286
287 /**
288 * Removes the plugins that are selected in the manage panel
289 */
290 private void removeHandler() {
291 int result = JOptionPane.showConfirmDialog(null, resources
292 .getString("pluginRemoveConfirmation"), resources
293 .getString("pluginManager"), JOptionPane.YES_NO_OPTION);
294
295 if (result != 0)
296 return;
297
298 int rows[] = managePanel.getTable().getSelectedRows();
299 ArrayList remove = new ArrayList();
300 for (int i = 0; i < rows.length; i++) {
301 Properties props = (Properties) installedPlugins.get(rows[i]);
302 props.setProperty("selected", "true");
303
304 remove.add(props);
305 }
306
307 unloadPluginHandler(remove, false, null);
308 System.gc();
309 remove = new ArrayList();
310
311 for (int i = 0; i < rows.length; i++) {
312 Properties props = (Properties) installedPlugins.get(rows[i]);
313 Hashtable loadedPlugins = PluginLoader.getInstance()
314 .getLoadedPlugins();
315
316 String name = props.getProperty("fileName");
317 File file = new File(name);
318
319 if (file.delete()) {
320 remove.add(props);
321 } else {
322 throwError("Could not unload plugin!", false);
323 return;
324 }
325 }
326
327 for (int i = 0; i < remove.size(); i++) {
328 installedPlugins.remove(remove.get(i));
329 }
330
331 managePanel.setPlugins(installedPlugins);
332 downloadPluginList();
333 }
334
335 /**
336 * Downloads a list of plugins
337 */
338 private void downloadPluginList() {
339 PluginLoader loader = PluginLoader.getInstance();
340 loader.findPlugins(installDir + File.separatorChar + "plugins");
341 installedPlugins = loader.getInstalledPlugins();
342
343 managePanel.setPlugins(installedPlugins);
344
345 managePanel.getTable().setEnabled(false);
346 listPanel.getTable().setEnabled(false);
347
348 setStatusText(resources.getString("gettingPluginList"));
349 Thread thread = new Thread(new DownloadListThread());
350 thread.start();
351 }
352
353 /**
354 * Called when the download thread is done downloading selected plugins
355 */
356 protected void doneDownloadingPlugins(ArrayList list) {
357 File cacheDir = new File(installDir, "downloadcache");
358 File pluginDir = new File(installDir, "plugins");
359 if (!pluginDir.isDirectory() && !pluginDir.mkdirs()) {
360 throwError("pluginDirectoryCreationError", true);
361 return;
362 }
363
364 unloadPluginHandler(list, false, null);
365 System.gc();
366
367 for (int i = 0; i < list.size(); i++) {
368 Properties props = (Properties) list.get(i);
369 if (props.getProperty("selected") != null) {
370 File file = new File(cacheDir, props.getProperty("fileName"));
371 File newFile = new File(pluginDir, props
372 .getProperty("fileName"));
373 if (newFile.exists() && !newFile.delete()) {
374 com.valhalla.Logger.debug("Could not delete old plugin!");
375 }
376
377 if (!file.renameTo(newFile)) {
378 com.valhalla.Logger.debug("Could not rename to new plugin");
379 }
380 }
381 }
382
383 PluginLoader.getNewInstance();
384 System.gc();
385
386 PluginLoader.getInstance().findPlugins(
387 installDir + File.separatorChar + "plugins");
388 loadPluginHandler(list, null);
389
390 downloadPluginList();
391 }
392
393 /**
394 * Called when the downloader thread is done downloading the list of
395 * available plugins
396 */
397 protected void doneDownloadingList() {
398 listPanel.setPlugins(pluginList);
399 managePanel.getTable().setEnabled(true);
400 listPanel.getTable().setEnabled(true);
401
402 setStatusText(resources.getString("doneDownloading"));
403 }
404
405 /**
406 * Sets the text in the status label
407 *
408 * @param text
409 * the text to set
410 */
411 private void setStatusText(String text) {
412 statusLabel.setText("<html><b>" + resources.getString("status")
413 + "</b>: " + text);
414 }
415
416 /**
417 * Returns true if a plugin is already installed
418 *
419 * @return <tt>true</tt> if a plugin is already installed
420 */
421 private boolean pluginInstalled(Properties props) {
422 boolean check = false;
423 if (props.getProperty("name") == null)
424 return check;
425 for (int i = 0; i < installedPlugins.size(); i++) {
426 Properties p = (Properties) installedPlugins.get(i);
427
428 if (p.getProperty("name") != null
429 && p.getProperty("name").equals(props.getProperty("name")))
430 check = true;
431 }
432
433 return check;
434 }
435
436 /**
437 * Returns true if a plugin is upgradable
438 *
439 * @return <tt>true</tt> if a plugin is already upgradable
440 */
441 private boolean pluginUpgradable(Properties props) {
442 boolean check = false;
443 if (props.getProperty("name") == null)
444 return check;
445 for (int i = 0; i < installedPlugins.size(); i++) {
446 Properties p = (Properties) installedPlugins.get(i);
447
448 if (p.getProperty("name") != null
449 && p.getProperty("name").equals(props.getProperty("name"))) {
450 try {
451 String installedVersion = p.getProperty("version");
452 String remoteVersion = props.getProperty("version");
453
454 int comp = remoteVersion.compareTo(installedVersion);
455
456 if (comp > 0)
457 check = true;
458
459 String installedAPI = p.getProperty("APIVersion");
460 String apiVersion = PluginLoader.getAPIVersion() + "";
461
462 comp = apiVersion.compareTo(installedAPI);
463 if( comp > 0 ) check = true;
464
465 } catch (Exception e) {
466 }
467 }
468 }
469
470 return check;
471 }
472
473 /**
474 * Collects the list of available plugins in their versions
475 */
476 class DownloadListThread implements Runnable {
477 public void run() {
478 BufferedReader in = null;
479 pluginList = new ArrayList();
480
481 try {
482 URL url = new URL("http://" + mirror + script +
483 "?command=pluginList&apiVersion=" + PluginLoader.getAPIVersion());
484 in = new BufferedReader(new InputStreamReader(url.openStream()));
485
486 String line = null;
487 String names[] = null;
488 int lineCount = 0;
489
490 while ((line = in.readLine()) != null) {
491 // the first line will indicate if we've connected
492 // successfully
493 if (lineCount == 0) {
494 if (!line.equals("Plugin list will follow")) {
495 throwError("invalidResponse", true);
496 break;
497 }
498 }
499
500 // second line gets a list of available arguments for each
501 // plugin
502 else if (lineCount == 1)
503 names = line.split("\t");
504
505 // all lines after are plugin information lines
506 else {
507 String de[] = line.split("\t");
508 //com.valhalla.Logger.debug( line );
509 if (de.length == names.length) {
510 Properties props = new Properties();
511 for (int i = 0; i < names.length; i++) {
512 props.setProperty(names[i], de[i]);
513 }
514
515 // make sure the plugin platform is compatible and
516 // the plugin api version is the same,
517 // otherwise
518 // ignore this plugin
519 if (PluginLoader.checkPlatform (props)
520 && props.getProperty("APIVersion").equals(
521 PluginLoader.getAPIVersion() + "")) {
522
523 com.valhalla.Logger.debug( props.getProperty("APIVersion"));
524 if (!pluginInstalled(props))
525 pluginList.add(props);
526 else if (pluginUpgradable(props))
527 pluginList.add(props);
528 }
529 } else {
530 com.valhalla.Logger.debug("Invalid plugin found.");
531 }
532 }
533
534 lineCount++;
535 }
536
537 } catch (Exception e) {
538 throwError(e.getMessage(), false);
539 e.printStackTrace();
540 }
541
542 doneDownloadingList();
543 }
544 }
545
546 /**
547 * Displays an error dialog
548 *
549 * @param message
550 * the message to display
551 */
552 void throwError(String message, boolean useResources) {
553 if (useResources)
554 message = resources.getString(message);
555 final String tempMessage = message;
556
557 SwingUtilities.invokeLater(new Runnable() {
558 public void run() {
559 Standard.warningMessage(PluginManager.this, resources
560 .getString("pluginManager"), tempMessage);
561 setStatusText(tempMessage);
562 }
563 });
564 }
565 }