source: gli/trunk/src/org/greenstone/gatherer/Gatherer.java@ 19678

Last change on this file since 19678 was 19678, checked in by ak19, 15 years ago

The proxy and Authenticator setup was recently moved into the gsdlRemote case alone and didn't get executed when the GS server was local. As an unfortunate side-effect, none of the downloading worked anymore (discovered when going through the OAI download tutorial).

  • Property svn:keywords set to Author Date Id Revision
File size: 48.2 KB
Line 
1/**
2 *#########################################################################
3 *
4 * A component of the Gatherer application, part of the Greenstone digital
5 * library suite from the New Zealand Digital Library Project at the
6 * University of Waikato, New Zealand.
7 *
8 * Author: John Thompson, Greenstone Digital Library, University of Waikato
9 *
10 * Copyright (C) 1999 New Zealand Digital Library Project
11 *
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 2 of the License, or
15 * (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program; if not, write to the Free Software
24 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
25 *########################################################################
26 */
27package org.greenstone.gatherer;
28
29import java.awt.*;
30import java.awt.event.*;
31import java.io.*;
32import java.lang.*;
33import java.net.*;
34import java.util.*;
35import javax.swing.*;
36import javax.swing.plaf.*;
37import javax.swing.text.*;
38
39
40import org.greenstone.gatherer.Configuration;
41import org.greenstone.gatherer.GAuthenticator;
42import org.greenstone.gatherer.FedoraInfo;
43import org.greenstone.gatherer.collection.CollectionManager;
44import org.greenstone.gatherer.feedback.ActionRecorderDialog;
45import org.greenstone.gatherer.feedback.Base64;
46import org.greenstone.gatherer.file.FileManager;
47import org.greenstone.gatherer.file.FileAssociationManager;
48import org.greenstone.gatherer.file.RecycleBin;
49import org.greenstone.gatherer.greenstone.Classifiers;
50import org.greenstone.gatherer.greenstone.LocalGreenstone;
51import org.greenstone.gatherer.greenstone.LocalLibraryServer;
52import org.greenstone.gatherer.greenstone.Plugins;
53import org.greenstone.gatherer.greenstone3.ServletConfiguration;
54import org.greenstone.gatherer.gui.GUIManager;
55import org.greenstone.gatherer.gui.URLField;
56import org.greenstone.gatherer.gui.WarningDialog;
57import org.greenstone.gatherer.gui.FedoraLogin;
58import org.greenstone.gatherer.remote.RemoteGreenstoneServer;
59import org.greenstone.gatherer.util.JarTools;
60import org.greenstone.gatherer.util.StaticStrings;
61import org.greenstone.gatherer.util.Utility;
62
63
64/** Containing the top-level "core" for the Gatherer, this class is the
65 * common core for the GLI application and applet. It first parses the
66 * command line arguments, preparing to update the configuration as
67 * required. Next it loads several important support classes such as the
68 * Configuration and Dictionary. Finally it creates the other important
69 * managers and sends them on their way.
70 * @author John Thompson, Greenstone Digital Library, University of Waikato
71 * @version 2.3
72 */
73public class Gatherer
74{
75 /** The name of the GLI. */
76 static final public String PROGRAM_NAME = "Greenstone Librarian Interface";
77 /** The current version of the GLI.
78 * Note: the gs3-release-maker relies on this variable being declared
79 * in a line which maches this java regex:
80 * ^(.*)String\s*PROGRAM_VERSION\s*=\s*"trunk";
81 * If change the declaration and it no longer matches the regex, please
82 * change the regex in the gs3-release-maker code and in this message
83 */
84
85 static final public String PROGRAM_VERSION = "trunk";
86
87 static private Dimension size = new Dimension(800, 540);
88 static public RemoteGreenstoneServer remoteGreenstoneServer = null;
89
90 /** Has the exit flag been set? */
91 static final public int EXIT_THEN_RESTART= 2;
92 static public boolean exit = false;
93 static public int exit_status = 0;
94
95 static private String gli_directory_path = null;
96 static private String gli_user_directory_path = null;
97
98 static public String client_operating_system = null;
99
100 /** All of the external applications that must exit before we close the Gatherer. */
101 static private Vector apps = new Vector();
102 static private String non_standard_collect_directory_path = null;
103 static public String open_collection_file_path = null;
104 /** A public reference to the FileAssociationManager. */
105 static public FileAssociationManager assoc_man;
106 /** A public reference to the CollectionManager. */
107 static public CollectionManager c_man;
108 /** A public reference to the RecycleBin. */
109 static public RecycleBin recycle_bin;
110 /** a reference to the Servlet Configuration is GS3 */
111 static public ServletConfiguration servlet_config;
112 /** A public reference to the FileManager. */
113 static public FileManager f_man;
114 /** A public reference to the GUIManager. */
115 static public GUIManager g_man = null;
116 static private boolean g_man_built = false;
117
118 /** We are using the GLI for GS3 */
119 static public boolean GS3 = false;
120
121 static public boolean isApplet = false;
122 static public boolean isGsdlRemote = false;
123
124 /* TODO: If we're using local GLI, collections are built locally. If we're using client-GLI
125 * and it contains a gs2build folder in it, then localBuild will also be true (if this is not
126 * turned off in Preferences). If we're remote and this is turned off in Prefs, build remotely. */
127 /*static public boolean buildingLocally = true;*/
128 /** If we're using local GLI, we can always download. If we're using client-GLI, we can only
129 * download if we have a gs2build folder inside it. And if we don't turn off downloadEnabling
130 * in the preferences.
131 */
132 static public boolean isDownloadEnabled = true;
133
134 // feedback stuff
135 /** is the feedback feature enabled? */
136 static public boolean feedback_enabled = true;
137 /** the action recorder dialog */
138 static public ActionRecorderDialog feedback_dialog = null;
139
140 // Refresh reasons
141 static public final int COLLECTION_OPENED = 0;
142 static public final int COLLECTION_CLOSED = 1;
143 static public final int COLLECTION_REBUILT = 2;
144 static public final int PREFERENCES_CHANGED = 3;
145
146 //////kk added////////
147 static public String cgiBase="";
148 /////////////////
149
150 /** Magic to allow Enter to fire the default button. */
151 static {
152 KeyStroke enter = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
153 Keymap map = JTextComponent.getKeymap(JTextComponent.DEFAULT_KEYMAP);
154 map.removeKeyStrokeBinding(enter);
155 }
156
157 static private URL default_gliserver_url=null;
158
159 public Gatherer(String[] args)
160 {
161 // Display the version to make error reports a lot more useful
162 System.err.println("Version: " + PROGRAM_VERSION + "\n");
163
164 JarTools.initialise(this);
165
166 GetOpt go = new GetOpt(args);
167
168 // Remember the GSDLOS value
169 client_operating_system = go.client_operating_system;
170
171 // If feedback is enabled, set up the recorder dialog
172 if (go.feedback_enabled) {
173 // Use the default locale for now - this will be changed by the Gatherer run method
174 feedback_enabled = true;
175 feedback_dialog = new ActionRecorderDialog(Locale.getDefault());
176 }
177
178 // Are we using a remote Greenstone?
179 if (go.use_remote_greenstone) {
180 isGsdlRemote = true;
181
182 // We don't have a local Greenstone!
183 go.gsdl3_path=null;
184 go.gsdl3_src_path=null;
185
186 // Don't set go.gsdl_path to null, since gdsl_path may still be set
187 // if we have a client-gli containing gs2build folder.
188 // However, keep track of whether we can download.
189 if(go.gsdl_path == null) {
190 isDownloadEnabled = false;
191 }
192
193 // We have to use our own collect directory since we can't use the Greenstone one
194 setCollectDirectoryPath(getGLIUserDirectoryPath() + "collect" + File.separator);
195 }
196 // We have a local Greenstone. OR we have a gs2build folder inside
197 // the client-GLI folder (with which the Download panel becomes enabled)
198 if(isDownloadEnabled) {
199 LocalGreenstone.setDirectoryPath(go.gsdl_path);
200 }
201
202 // Users may specify a non-standard collect directory (eg. when running one GLI in a network environment)
203 if (go.collect_directory_path != null) {
204 setCollectDirectoryPath(go.collect_directory_path);
205 }
206
207 // More special code for running with a remote Greenstone
208 if (isGsdlRemote) {
209 if (go.fedora_info.isActive()) {
210 Configuration.TEMPLATE_CONFIG_XML = "xml/" + Configuration.FEDORA_CONFIG_PREFIX + "configRemote.xml";
211 }
212 else {
213 Configuration.TEMPLATE_CONFIG_XML = "xml/configRemote.xml";
214 }
215
216 Configuration.CONFIG_XML = "configRemote.xml";
217
218 File collect_directory = new File(Gatherer.getCollectDirectoryPath());
219 if (!collect_directory.exists() && !collect_directory.mkdir()) {
220 System.err.println("Warning: Unable to make directory: " + collect_directory);
221 }
222 }
223 else {
224 if (go.fedora_info.isActive()) {
225 Configuration.TEMPLATE_CONFIG_XML = "xml/" + Configuration.FEDORA_CONFIG_PREFIX + Configuration.CONFIG_XML;
226 }
227 // else, the CONFIG_XML uses the default config file, which is for the local GS server
228 }
229
230 init(go.gsdl_path, go.gsdl3_path, go.gsdl3_src_path,
231 go.fedora_info,
232 go.local_library_path, go.library_url_string,
233 go.gliserver_url_string, go.debug, go.perl_path, go.no_load, go.filename, go.site_name,
234 go.servlet_path);
235 }
236
237
238 public void init(String gsdl_path, String gsdl3_path, String gsdl3_src_path,
239 FedoraInfo fedora_info,
240 String local_library_path,
241 String library_url_string, String gliserver_url_string, boolean debug_enabled,
242 String perl_path, boolean no_load, String open_collection,
243 String site_name, String servlet_path)
244 {
245 if (gsdl3_path != null && !gsdl3_path.equals("")) {
246 this.GS3 = true;
247 } else {
248 gsdl3_path = null;
249 gsdl3_src_path = null;
250 }
251
252 // Create the debug stream if required
253 if (debug_enabled) {
254 DebugStream.enableDebugging();
255
256 Calendar now = Calendar.getInstance();
257 String debug_file_path = "debug" + now.get(Calendar.DATE) + "-" + now.get(Calendar.MONTH) + "-" + now.get(Calendar.YEAR) + ".txt";
258
259 // Debug file is created in the user's GLI directory
260 debug_file_path = getGLIUserDirectoryPath() + debug_file_path;
261 DebugStream.println("Debug file path: " + debug_file_path);
262 DebugStream.setDebugFile(debug_file_path);
263 DebugStream.print(System.getProperties());
264 }
265
266 // Delete plugins.dat and classifiers.dat files from previous versions of the GLI (no longer used)
267 File plugins_dat_file = new File(Gatherer.getGLIUserDirectoryPath() + "plugins.dat");
268 if (plugins_dat_file.exists()) {
269 System.err.println("Deleting plugins.dat file...");
270 Utility.delete(plugins_dat_file);
271 }
272 File classifiers_dat_file = new File(Gatherer.getGLIUserDirectoryPath() + "classifiers.dat");
273 if (classifiers_dat_file.exists()) {
274 System.err.println("Deleting classifiers.dat file...");
275 Utility.delete(classifiers_dat_file);
276 }
277
278 try {
279 // Load GLI config file
280 new Configuration(getGLIUserDirectoryPath(), gsdl_path, gsdl3_path, gsdl3_src_path, site_name,
281 fedora_info);
282
283 // Check we know where Perl is
284 Configuration.perl_path = perl_path;
285 if (isGsdlRemote && !isDownloadEnabled && Utility.isWindows() && Configuration.perl_path != null) {
286 if (Configuration.perl_path.toLowerCase().endsWith("perl.exe")) {
287 Configuration.perl_path = Configuration.perl_path.substring(0, Configuration.perl_path.length() - "perl.exe".length());
288 }
289 if (Configuration.perl_path.endsWith(File.separator)) {
290 Configuration.perl_path = Configuration.perl_path.substring(0, Configuration.perl_path.length() - File.separator.length());
291 }
292 }
293
294 // the feedback dialog has been loaded with a default locale,
295 // now set the user specified one
296 if (feedback_enabled && feedback_dialog != null) {
297 feedback_dialog.setLocale(Configuration.getLocale("general.locale", true));
298 }
299
300 // Read Dictionary
301 new Dictionary(Configuration.getLocale("general.locale", true), Configuration.getFont("general.font", true));
302
303 // check that we are using Sun Java
304 String java_vendor = System.getProperty("java.vendor");
305 if (!java_vendor.equals("Sun Microsystems Inc.")) {
306 System.err.println(Dictionary.get("General.NotSunJava", java_vendor));
307 }
308
309 // Unless we're using remote building, we need to know where the local Greenstone is
310 if (!isGsdlRemote && gsdl_path == null) {
311 missingGSDL();
312 }
313
314 if (fedora_info.isActive()) {
315 popupFedoraInfo();
316 }
317
318 // Finally, we're ready to find out the version of the remote Greenstone server
319 if(isGsdlRemote) {
320 // instantiate the RemoteGreenstoneServer object first
321 remoteGreenstoneServer = new RemoteGreenstoneServer();
322
323 // Set up proxy
324 setProxy();
325 // Now we set up an Authenticator
326 Authenticator.setDefault(new GAuthenticator());
327
328 int greenstoneVersion = 2;
329 requestGLIServerURL();
330 gliserver_url_string = Configuration.gliserver_url.toString();
331
332 greenstoneVersion = remoteGreenstoneServer.getGreenstoneVersion();
333 // Display the version to make error reports a lot more useful
334 System.err.println("Remote Greenstone server version: " + greenstoneVersion);
335 if(greenstoneVersion >= 3) {
336 this.GS3 = true;
337 Configuration.prepareForGS3();
338 }
339
340 if(fedora_info.isActive()) {
341 // when GS server is remote, FEDORA_HOME resides on the remote server side,
342 // but we know the library URL from user-provided information
343 library_url_string = fedora_info.getLibraryURL();
344 } else {
345 library_url_string = remoteGreenstoneServer.getLibraryURL(Configuration.gliserver_url.toString());
346 }
347 // write it into the config file
348 Configuration.setString("general.library_url", true, library_url_string);
349 }
350 else { // local greenstone: Start up the local library server, if that's what we want
351 if (local_library_path != null && !GS3) {
352 LocalLibraryServer.start(gsdl_path, local_library_path);
353 }
354 }
355
356 // The "-library_url" option overwrites anything in the config files
357 if (library_url_string != null && library_url_string.length() > 0) {
358 try {
359 System.err.println("Setting library_url to " + library_url_string + "...");
360 Configuration.library_url = new URL(library_url_string);
361 }
362 catch (MalformedURLException error) {
363 DebugStream.printStackTrace(error);
364 }
365 }
366
367
368 // Check that we now know the Greenstone library URL, since we need this for previewing collections
369 // It is not necessary if an independent GSI was launched and the user hasn't pressed Enter Library yet
370 DebugStream.println("Configuration.library_url = " + Configuration.library_url);
371 if (Configuration.library_url == null && (LocalLibraryServer.isRunning() && !LocalLibraryServer.isURLPending())) {
372 missingEXEC();
373 }
374
375 // The "-gliserver_url" option overwrites anything in the config files
376 if (gliserver_url_string != null && gliserver_url_string.length() > 0) {
377 try {
378 System.err.println("Setting gliserver_url to " + gliserver_url_string + "...");
379 Configuration.gliserver_url = new URL(gliserver_url_string);
380 }
381 catch (MalformedURLException error) {
382 DebugStream.printStackTrace(error);
383 }
384 }
385
386 // If we're using a remote Greenstone we need to know where the gliserver script is
387 DebugStream.println("Configuration.gliserver_url = " + Configuration.gliserver_url);
388
389 if (GS3) {
390 // Load Greenstone 3 servlet configuration
391 if (isGsdlRemote){
392 servlet_config = new ServletConfiguration(Configuration.gli_user_directory_path);
393 }else{
394 servlet_config= new ServletConfiguration(gsdl3_path);
395 }
396 }
397
398 if (GS3 && Configuration.servlet_path == null) {
399 Configuration.servlet_path = servlet_config.getServletPath(Configuration.site_name);
400 }
401
402 // ensure that a directory called 'cache' exists in the GLI user directory
403 File user_cache_dir = new File(Gatherer.getGLIUserCacheDirectoryPath());
404 System.err.println("User cache dir: " + Gatherer.getGLIUserCacheDirectoryPath());
405 if (!user_cache_dir.exists() && !user_cache_dir.mkdir()) {
406 System.err.println("Warning: Unable to make directory: " + user_cache_dir);
407 }
408
409 // Check for ImageMagick
410 if (Gatherer.isGsdlRemote) {
411 DebugStream.println("Not checking for ImageMagick.");
412 }
413 else if (!(new ImageMagickTest()).found()) {
414 // Time for a warning message
415 missingImageMagick();
416 }
417
418 if (Gatherer.isGsdlRemote) {
419 DebugStream.println("Not checking for perl path/exe");
420 }
421 else {
422 // Perl path is a little different as it is perfectly ok to
423 // start the GLI without providing a perl path
424 boolean found_perl = false;
425 if (Configuration.perl_path != null) {
426 // See if the file pointed to actually exists
427 File perl_file = new File(Configuration.perl_path);
428 found_perl = perl_file.exists();
429 perl_file = null;
430 }
431 if (Configuration.perl_path == null || !found_perl) {
432 // Run test to see if we can run perl as is.
433 PerlTest perl_test = new PerlTest();
434 if (perl_test.found()) {
435 // If so replace the perl path with the system
436 // default (or null for unix).
437 Configuration.perl_path = perl_test.toString();
438 found_perl = true;
439 }
440 }
441 if (!found_perl) {
442 // Time for an error message.
443 missingPERL();
444 }
445 }
446
447 // Set the default font for all Swing components.
448 FontUIResource default_font = Configuration.getFont("general.font", true);
449 Enumeration keys = UIManager.getDefaults().keys();
450 while (keys.hasMoreElements()) {
451 Object key = keys.nextElement();
452 Object value = UIManager.get(key);
453 if (value instanceof FontUIResource) {
454 UIManager.put(key, default_font);
455 }
456 }
457
458 // At this point (which is where this code originally used to be), we can set up the proxy for the
459 // non-remote case. The remote Greenstone server would already have setup its proxy when required.
460 if(!isGsdlRemote) {
461 setProxy();
462 // Now we set up an Authenticator
463 Authenticator.setDefault(new GAuthenticator());
464 }
465 assoc_man = new FileAssociationManager();
466 // Create File Manager
467 f_man = new FileManager();
468 // Create Collection Manager
469 c_man = new CollectionManager();
470 // Create Recycle Bin
471 recycle_bin = new RecycleBin();
472
473 if (GS3) {
474 if (site_name==null) {
475 site_name = Configuration.site_name;
476 servlet_path = null; // need to reset this
477 }
478 if (servlet_path == null) {
479 servlet_path = Configuration.getServletPath();
480 }
481 }
482
483 open_collection_file_path = open_collection;
484 if (open_collection_file_path == null) {
485 open_collection_file_path = Configuration.getString(
486 "general.open_collection"+Configuration.gliPropertyNameSuffix(), true);
487 }
488 if (no_load || open_collection_file_path.equals("")) {
489 open_collection_file_path = null;
490 }
491 }
492 catch (Exception exception) {
493 DebugStream.printStackTrace(exception);
494 }
495
496
497 // Create GUI Manager (last) or else suffer the death of a thousand NPE's
498 g_man = new GUIManager(size);
499
500 // Get a list of the core Greenstone classifiers and plugins
501 Classifiers.loadClassifiersList(null);
502 Plugins.loadPluginsList(null);
503
504 // If using a remote Greenstone we need to download the collection configurations now
505 if (Gatherer.isGsdlRemote) {
506 if (remoteGreenstoneServer.downloadCollectionConfigurations().equals("")) {
507 // !! Something went wrong downloading the collection configurations
508 System.err.println("Error: Could not download collection configurations.");
509 if(!Gatherer.isApplet) { // don't close the browser if it is an applet!
510 System.exit(0);
511 }
512 }
513 }
514 }
515
516
517 /** Returns the correct version of the (local or remote) Greenstone server if init() has already been called. */
518 public static int serverVersionNumber() {
519 return GS3 ? 3 : 2;
520 }
521
522 /** Returns "Server: version number" if init() has already been called. */
523 public static String getServerVersionAsString() {
524 return "Server: v" + serverVersionNumber();
525 }
526
527 public void openGUI()
528 {
529 // Size and place the frame on the screen
530 Rectangle bounds = Configuration.getBounds("general.bounds", true);
531 if (bounds == null) {
532 // Choose a sensible default value
533 bounds = new Rectangle(0, 0, 640, 480);
534 }
535
536 // Ensure width and height are reasonable
537 size = bounds.getSize();
538 if (size.width < 640) {
539 size.width = 640;
540 }
541 else if (size.width > Configuration.screen_size.width && Configuration.screen_size.width > 0) {
542 size.width = Configuration.screen_size.width;
543 }
544 if (size.height < 480) {
545 size.height = 480;
546 }
547 else if (size.height > Configuration.screen_size.height && Configuration.screen_size.height > 0) {
548 size.height = Configuration.screen_size.height;
549 }
550
551 if (!g_man_built) {
552
553 g_man.display();
554
555 // Place the window in the desired location on the screen, if this is do-able (not under most linux window managers apparently. In fact you're lucky if they listen to any of your screen size requests).
556 g_man.setLocation(bounds.x, bounds.y);
557 g_man.setVisible(true);
558
559 // After the window has been made visible, check that it is in the correct place
560 // sometimes java places a window not in the correct place,
561 // but with an offset. If so, we work out what the offset is
562 // and change the desired location to take that into account
563 Point location = g_man.getLocation();
564 int x_offset = bounds.x - location.x;
565 int y_offset = bounds.y - location.y;
566 // If not, offset the window to move it into the correct location
567 if (x_offset > 0 || y_offset > 0) {
568 ///ystem.err.println("changing the location to "+(bounds.x + x_offset)+" "+ (bounds.y + y_offset));
569 g_man.setLocation(bounds.x + x_offset, bounds.y + y_offset);
570 }
571
572 // The 'after-display' triggers several events which don't occur until after the visual components are actually available on screen. Examples of these would be the various html renderings, as they can't happen offscreen.
573 g_man.afterDisplay();
574 g_man_built = true;
575 }
576 else {
577 g_man.setVisible(true);
578 }
579
580 // Get a list of the core Greenstone classifiers and plugins
581 /*Classifiers.loadClassifiersList(null);
582 Plugins.loadPluginsList(null);
583
584 // If using a remote Greenstone we need to download the collection configurations now
585 if (Gatherer.isGsdlRemote) {
586 if (remoteGreenstoneServer.downloadCollectionConfigurations().equals("")) {
587 // !! Something went wrong downloading the collection configurations
588 System.err.println("Error: Could not download collection configurations.");
589 System.exit(0);
590 }
591 }*/
592
593 // If there was a collection left open last time, reopen it
594 if (open_collection_file_path == null) {
595
596 //the menu bar items, file and edit, are disabled from the moment of their creation. if there is no left-over collection from the last session, enable them; otherwise it is untill the collection finishes loading, will they be enabled in collectionManager.java
597 setMenuBarEnabled(true);
598 } else {
599
600 // If there was a collection left open last time, reopen it
601 c_man.openCollectionFromLastTime();
602 }
603 }
604
605 public static void setMenuBarEnabled(boolean enabled) {
606 g_man.menu_bar.file.setEnabled(enabled);
607 g_man.menu_bar.edit.setEnabled(enabled);
608 }
609
610 /** Exits the Gatherer after ensuring that things needing saving are saved.
611 * @see java.io.FileOutputStream
612 * @see java.io.PrintStream
613 * @see java.lang.Exception
614 * @see javax.swing.JOptionPane
615 * @see org.greenstone.gatherer.Configuration
616 * @see org.greenstone.gatherer.collection.CollectionManager
617 * @see org.greenstone.gatherer.gui.GUIManager
618 */
619 static public void exit(int new_exit_status)
620 {
621 DebugStream.println("In Gatherer.exit()...");
622 exit = true;
623 if (new_exit_status != 0) {
624 // default exit_status is already 0
625 // only remember a new exit status if it is non-trivial
626 exit_status = new_exit_status;
627 }
628
629 // Save the file associations
630 if (assoc_man != null) {
631 assoc_man.save();
632 assoc_man = null;
633 }
634
635 // Get the gui to deallocate
636 if(g_man != null) {
637 g_man.destroy();
638 g_man_built = false;
639 }
640
641 // Flush debug
642 DebugStream.closeDebugStream();
643
644 // If we started a server, we should try to stop it.
645 if (LocalLibraryServer.isRunning() == true) {
646 LocalLibraryServer.stop();
647 }
648
649 // If we're using a remote Greenstone server we need to make sure that all jobs have completed first
650 if (isGsdlRemote) {
651 remoteGreenstoneServer.exit();
652 }
653
654 // Make sure we haven't started up any processes that are still running
655 if (apps.size() == 0) {
656 // If we're running as an applet we don't actually quit (just hide the main GLI window)
657 if (!Gatherer.isApplet) {
658 // This is the end...
659 System.exit(exit_status);
660 }
661 }
662 else {
663 JOptionPane.showMessageDialog(g_man, Dictionary.get("General.Outstanding_Processes"), Dictionary.get("General.Outstanding_Processes_Title"), JOptionPane.ERROR_MESSAGE);
664 g_man.setVisible(false);
665 }
666 }
667
668 static public void exit()
669 {
670 exit(0);
671 }
672
673 /** Returns the path of the Greenstone "collect" directory. */
674 static public String getCollectDirectoryPath()
675 {
676 if (non_standard_collect_directory_path != null) {
677 return non_standard_collect_directory_path;
678 }
679
680 if (!GS3) {
681 return Configuration.gsdl_path + "collect" + File.separator;
682 }
683 else {
684 return getSitesDirectoryPath() + Configuration.site_name + File.separator + "collect" + File.separator;
685 }
686 }
687
688
689 /** Returns the path of the GLI directory. */
690 static public String getGLIDirectoryPath()
691 {
692 return gli_directory_path;
693 }
694
695
696 /** Returns the path of the GLI "metadata" directory. */
697 static public String getGLIMetadataDirectoryPath()
698 {
699 return getGLIDirectoryPath() + "metadata" + File.separator;
700 }
701
702
703 /** Returns the path of the GLI user directory. */
704 static public String getGLIUserDirectoryPath()
705 {
706 return gli_user_directory_path;
707 }
708
709
710 /** Returns the path of the GLI user "cache" directory. */
711 static public String getGLIUserCacheDirectoryPath()
712 {
713 return getGLIUserDirectoryPath() + "cache" + File.separator;
714 }
715
716
717 /** Returns the path of the GLI user "log" directory. */
718 static public String getGLIUserLogDirectoryPath()
719 {
720 return getGLIUserDirectoryPath() + "log" + File.separator;
721 }
722
723
724 static public String getSitesDirectoryPath()
725 {
726 return Configuration.gsdl3_path + "sites" + File.separator;
727 }
728
729
730 static public void setCollectDirectoryPath(String collect_directory_path)
731 {
732 non_standard_collect_directory_path = collect_directory_path;
733 if (!non_standard_collect_directory_path.endsWith(File.separator)) {
734 non_standard_collect_directory_path = non_standard_collect_directory_path + File.separator;
735 }
736 }
737
738
739 static public void setGLIDirectoryPath(String gli_directory_path_arg)
740 {
741 gli_directory_path = gli_directory_path_arg;
742 }
743
744
745 static public void setGLIUserDirectoryPath(String gli_user_directory_path_arg)
746 {
747 gli_user_directory_path = gli_user_directory_path_arg;
748
749 // Ensure the GLI user directory exists
750 File gli_user_directory = new File(gli_user_directory_path);
751 if (!gli_user_directory.exists() && !gli_user_directory.mkdirs()) {
752 System.err.println("Error: Unable to make directory: " + gli_user_directory);
753 }
754 }
755
756
757 static public void refresh(int refresh_reason)
758 {
759 if (g_man != null) {
760
761 g_man.refresh(refresh_reason, c_man.ready());
762 }
763
764 // Now is a good time to force a garbage collect
765 System.gc();
766 }
767
768
769 // used to send reload coll messages to the tomcat server
770 static public void configGS3Server(String site, String command) {
771 if (Configuration.library_url == null){
772 System.out.println("Error: you have not provide the Greenstone Library address.");
773 return;
774
775 }
776
777 try {
778 // need to add the servlet name to the exec address
779 String raw_url = Configuration.library_url.toString() + Configuration.getServletPath() + command;
780 URL url = new URL(raw_url);
781 DebugStream.println("Action: " + raw_url);
782 HttpURLConnection library_connection = (HttpURLConnection) url.openConnection();
783 int response_code = library_connection.getResponseCode();
784 if(HttpURLConnection.HTTP_OK <= response_code && response_code < HttpURLConnection.HTTP_MULT_CHOICE) {
785 DebugStream.println("200 - Complete.");
786 }
787 else {
788 DebugStream.println("404 - Failed.");
789 }
790 url = null;
791 }
792 catch(java.net.ConnectException connectException) {
793 JOptionPane.showMessageDialog(g_man, Dictionary.get("Preferences.Connection.Library_Path_Connection_Failure", Configuration.library_url.toString()), Dictionary.get("General.Warning"), JOptionPane.WARNING_MESSAGE);
794 DebugStream.println(connectException.getMessage());
795 }
796 catch (Exception exception) {
797 DebugStream.printStackTrace(exception);
798 }
799 }
800
801
802 /** Used to 'spawn' a new child application when a file is double clicked.
803 * @param file The file to open
804 * @see org.greenstone.gatherer.Gatherer.ExternalApplication
805 */
806 static public void spawnApplication(File file) {
807 String [] commands = assoc_man.getCommand(file);
808 if(commands != null) {
809 ExternalApplication app = new ExternalApplication(commands);
810 apps.add(app);
811 app.start();
812 }
813 else {
814 ///ystem.err.println("No open command available.");
815 }
816 }
817
818
819 static public void spawnApplication(String command)
820 {
821 ExternalApplication app = new ExternalApplication(command);
822 apps.add(app);
823 app.start();
824 }
825
826 static public void spawnApplication(String command, String ID)
827 {
828 ExternalApplication app = new ExternalApplication(command, ID);
829 apps.add(app);
830 app.start();
831 }
832
833 static public void terminateApplication(String ID) {
834 for(int i = 0; i < apps.size(); i++) {
835 ExternalApplication app = (ExternalApplication)apps.get(i);
836 if(app.getID() != null && app.getID().equals(ID)) {
837 app.stopExternalApplication();
838 apps.remove(app);
839 }
840 }
841 }
842
843
844 /** Used to 'spawn' a new browser application or reset an existing one when the preview button is clicked
845 * @param url The url to open the browser at
846 * @see org.greenstone.gatherer.Gatherer.BrowserApplication
847 */
848 static public void spawnBrowser(String url) {
849 String command = assoc_man.getBrowserCommand(url);
850 if (command != null) {
851 BrowserApplication app = new BrowserApplication(command, url);
852 apps.add(app);
853 app.start();
854 }
855 else {
856 ///ystem.err.println("No browser command available.");
857 }
858 }
859
860
861 /** Prints a warning message about a missing library path, which means the final collection cannot be previewed in the Gatherer.
862 */
863 static private void missingEXEC() {
864 WarningDialog dialog;
865 String configPropertyName = "general.library_url"+Configuration.gliPropertyNameSuffix();
866
867 if (GS3) {
868 // Warning dialog with no cancel button and no "turn off warning" checkbox
869 dialog = new WarningDialog("warning.MissingEXEC", Dictionary.get("MissingEXEC_GS3.Title"), Dictionary.get("MissingEXEC_GS3.Message"), configPropertyName, false, false);
870 } else { // local case
871 dialog = new WarningDialog("warning.MissingEXEC", Dictionary.get("MissingEXEC.Title"), Dictionary.get("MissingEXEC.Message"), configPropertyName, false);
872 }
873
874 JTextField field = new URLField.Text(Configuration.getColor("coloring.editable_foreground", false), Configuration.getColor("coloring.editable_background", false));
875
876 // Set the default library URL to the tomcat server and port number
877 // specified in the build.properties located in the gsdl3_src_path
878 if (GS3) {
879 String host = "localhost";
880 String port = "8080";
881
882 File buildPropsFile = new File(Configuration.gsdl3_src_path + File.separator + "build.properties");
883 if(buildPropsFile.exists()) {
884 Properties props = new Properties();
885 try{
886 props.load(new FileInputStream(buildPropsFile));
887 host = props.getProperty("tomcat.server", host);
888 port = props.getProperty("tomcat.port", port);
889 }catch(Exception e){
890 DebugStream.println("Could not load build.properties file");
891 System.err.println("Could not load build.properties file");
892 }
893 props = null;
894 }
895 String defaultURL = "http://"+host+":"+port+"/"+"greenstone3/library";
896 field.setText(defaultURL);
897 field.selectAll();
898 }
899 dialog.setValueField(field);
900 dialog.display();
901 dialog.dispose();
902 dialog = null;
903
904 String library_url_string = Configuration.getString(configPropertyName, true);
905 if (!library_url_string.equals("")) {
906 try {
907 // WarningDialog does not allow invalid URLs, so the following is ignored:
908 // make sure the URL the user provided contains the http:// prefix
909 // and then save the corrected URL
910 if(!library_url_string.startsWith("http://")
911 && !library_url_string.startsWith("https://")) {
912 library_url_string = "http://"+library_url_string;
913 Configuration.setString(configPropertyName, true, configPropertyName);
914 }
915 Configuration.library_url = new URL(library_url_string);
916 }
917 catch (MalformedURLException exception) {
918 DebugStream.printStackTrace(exception);
919 }
920 }
921 }
922
923
924
925 /** Prints a warning message about a missing library path, which means the final collection cannot be previewed in the Gatherer.
926 */
927 static private void popupFedoraInfo() {
928
929 FedoraLogin dialog = new FedoraLogin("Fedora Login", false);
930
931 if (Configuration.library_url == null) {
932
933 String library_url_string = dialog.getLibraryURL();
934 if (!library_url_string.equals("")) {
935 try {
936 Configuration.library_url = new URL(library_url_string);
937 }
938 catch (MalformedURLException exception) {
939 DebugStream.printStackTrace(exception);
940 }
941 }
942 }
943
944 boolean showLogin = true;
945 do {
946 if(!dialog.loginRequested()) { // user pressed cancel to exit the FedoraLogin dialog
947 System.exit(0);
948 } else {
949 showLogin = dialog.loginRequested();
950 String hostname = dialog.getHostname();
951 String port = dialog.getPort();
952 String username = dialog.getUsername();
953 String password = dialog.getPassword();
954 String protocol = dialog.getProtocol();
955
956 Configuration.fedora_info.setHostname(hostname);
957 Configuration.fedora_info.setPort(port);
958 Configuration.fedora_info.setUsername(username);
959 Configuration.fedora_info.setPassword(password);
960 Configuration.fedora_info.setProtocol(protocol);
961
962 String ping_url_str = protocol + "://" + hostname + ":" + port + "/fedora";
963 String login_str = username + ":" + password;
964
965 String login_encoding = Base64.encodeBytes(login_str.getBytes());
966
967 try {
968 URL ping_url = new URL(ping_url_str);
969 URLConnection uc = ping_url.openConnection();
970 uc.setRequestProperty ("Authorization", "Basic " + login_encoding);
971 // Attempt to access some content ...
972 InputStream content = (InputStream)uc.getInputStream();
973
974 // if no exception occurred in the above, we would have come here:
975 showLogin = false;
976 dialog.dispose();
977 }
978 catch (Exception exception) {
979 // TODO: move into dictionary
980 String[] errorMessage = {"Failed to connect to the Fedora server.", "It might not be running, or",
981 "incorrect username and/or password."};
982 dialog.setErrorMessage(errorMessage);
983 //DebugStream.printStackTrace(exception);
984 // exception occurred, show the dialog again (do this after printing to
985 // debugStream, else the above does not get done for some reason).
986 dialog.setVisible(true);
987 }
988 }
989 } while(showLogin);
990
991 dialog = null; // no more need of the dialog
992
993 // Now we are connected.
994 }
995
996
997
998 static private void requestGLIServerURL()
999 {
1000 WarningDialog dialog;
1001 String[] defaultURLs = {
1002 "http://localhost:8080/greenstone3/cgi-bin/gliserver.pl",
1003 "http://localhost:8080/gsdl/cgi-bin/gliserver.pl"
1004 };
1005
1006 // Warning dialog with no cancel button and no "turn off warning" checkbox
1007 // (since user-input of the gliserver script is mandatory)
1008 dialog = new WarningDialog("warning.MissingGLIServer", Dictionary.get("MissingGLIServer.Title"), Dictionary.get("MissingGLIServer.Message"), "general.gliserver_url", false, false);
1009
1010 dialog.setValueField(new URLField.DropDown(Configuration.getColor("coloring.editable_foreground", false),
1011 Configuration.getColor("coloring.editable_background", false),
1012 defaultURLs, "general.gliserver_url",
1013 "general.open_collection"+Configuration.gliPropertyNameSuffix(),
1014 "gliserver.pl"));
1015
1016 if (Gatherer.default_gliserver_url!=null){
1017 dialog.setValueField(Gatherer.default_gliserver_url.toString());
1018 }
1019
1020 // A WarningDialog cannot always be made to respond (let alone to exit the program) on close. We
1021 // handle the response of this particular WarningDialog here: a URL for gliserver.pl is a crucial
1022 // piece of user-provided data. Therefore, if no URL was entered for gliserver.pl, it'll exit safely.
1023 dialog.addWindowListener(new WindowAdapter() {
1024 public void windowClosing(WindowEvent e) {
1025 Gatherer.exit();
1026 }
1027 });
1028
1029 dialog.display();
1030 dialog.dispose();
1031 dialog = null;
1032
1033
1034 String gliserver_url_string = Configuration.getString("general.gliserver_url", true);
1035 if (!gliserver_url_string.equals("")) {
1036 try {
1037 Configuration.gliserver_url = new URL(gliserver_url_string);
1038 Configuration.setString("general.gliserver_url", true, gliserver_url_string);
1039 }
1040 catch (MalformedURLException exception) {
1041 DebugStream.printStackTrace(exception);
1042 }
1043 }
1044 }
1045
1046
1047 /** Prints a warning message about a missing GSDL path, which although not fatal pretty much ensures nothing will work properly in the GLI.
1048 */
1049 static private void missingGSDL() {
1050 WarningDialog dialog = new WarningDialog("warning.MissingGSDL", Dictionary.get("MissingGSDL.Title"), Dictionary.get("MissingGSDL.Message"), null, false);
1051 dialog.display();
1052 dialog.dispose();
1053 dialog = null;
1054 }
1055
1056 /** Prints a warning message about missing a valid ImageMagick path, which although not fatal means building image collections won't work */
1057 static private void missingImageMagick() {
1058 WarningDialog dialog = new WarningDialog("warning.MissingImageMagick", Dictionary.get("MissingImageMagick.Title"), Dictionary.get("MissingImageMagick.Message"), null, false);
1059 dialog.display();
1060 dialog.dispose();
1061 dialog = null;
1062 }
1063
1064 /** Prints a warning message about missing a valid PERL path, which although not fatal pretty much ensures no collection creation/building will work properly in the GLI. */
1065 static private void missingPERL() {
1066 WarningDialog dialog = new WarningDialog("warning.MissingPERL", Dictionary.get("MissingPERL.Title"), Dictionary.get("MissingPERL.Message"), null, false);
1067 dialog.display();
1068 dialog.dispose();
1069 dialog = null;
1070 }
1071
1072
1073 /** Sets up the proxy connection by setting JVM Environment flags and creating a new Authenticator.
1074 * @see java.lang.Exception
1075 * @see java.lang.System
1076 * @see java.net.Authenticator
1077 * @see org.greenstone.gatherer.Configuration
1078 * @see org.greenstone.gatherer.GAuthenticator
1079 */
1080 static public void setProxy() {
1081 try {// Can throw several exceptions
1082 if(Configuration.get("general.use_proxy", true)) {
1083 System.setProperty("http.proxyType", "4");
1084 System.setProperty("http.proxyHost", Configuration.getString("general.proxy_host", true));
1085 System.setProperty("http.proxyPort", Configuration.getString("general.proxy_port", true));
1086 System.setProperty("http.proxySet", "true");
1087 } else {
1088 System.setProperty("http.proxyHost", "");
1089 System.setProperty("http.proxyPort", "");
1090 System.setProperty("http.proxySet", "false");
1091 }
1092 } catch (Exception error) {
1093 DebugStream.println("Error in Gatherer.initProxy(): " + error);
1094 DebugStream.printStackTrace(error);
1095 }
1096 }
1097
1098
1099 /** This private class contains an instance of an external application running within a JVM shell. It is important that this process sits in its own thread, but its more important that when we exit the Gatherer we don't actually System.exit(0) the Gatherer object until the user has volunteerily ended all of these child processes. Otherwise when we quit the Gatherer any changes the users may have made in external programs will be lost and the child processes are automatically deallocated. */
1100 static private class ExternalApplication
1101 extends Thread {
1102 private Process process = null;
1103 /** The initial command string given to this sub-process. */
1104 private String command = null;
1105 private String[] commands = null;
1106
1107 private String ID = null;
1108
1109 /** Constructor.
1110 * @param command The initial command <strong>String</strong>.
1111 */
1112 public ExternalApplication(String command) {
1113 this.command = command;
1114 }
1115
1116 public ExternalApplication(String[] commands) {
1117 this.commands = commands;
1118 }
1119
1120 public ExternalApplication(String command, String ID) {
1121 this.command = command;
1122 this.ID = ID;
1123 }
1124
1125 public String getID() {
1126 return ID;
1127 }
1128
1129 /** We start the child process inside a new thread so it doesn't block the rest of Gatherer.
1130 * @see java.lang.Exception
1131 * @see java.lang.Process
1132 * @see java.lang.Runtime
1133 * @see java.lang.System
1134 * @see java.util.Vector
1135 */
1136 public void run() {
1137 // Call an external process using the args.
1138 try {
1139 if(commands != null) {
1140 StringBuffer whole_command = new StringBuffer();
1141 for(int i = 0; i < commands.length; i++) {
1142 whole_command.append(commands[i]);
1143 whole_command.append(" ");
1144 }
1145 DebugStream.println("Running " + whole_command.toString());
1146 Runtime rt = Runtime.getRuntime();
1147 process = rt.exec(commands);
1148 process.waitFor();
1149 }
1150 else {
1151 DebugStream.println("Running " + command);
1152 Runtime rt = Runtime.getRuntime();
1153 process = rt.exec(command);
1154 process.waitFor();
1155 }
1156 }
1157 catch (Exception exception) {
1158 DebugStream.printStackTrace(exception);
1159 }
1160 // Remove ourself from Gatherer list of threads.
1161 apps.remove(this);
1162 // Call exit if we were the last outstanding child process thread.
1163 if (apps.size() == 0 && exit == true) {
1164 // In my opinion (DB) there is no need to exit here,
1165 // the 'run' method ending naturally brings this
1166 // thread to an end. In fact it is potentially
1167 // dangerous to exit here, as the main thread in the
1168 // Gatherer class may be stopped prematurely. As it so
1169 // happens the point at which the ExternalApplication thread
1170 // is asked to stop (Back in the main Gatherer thread) is after
1171 // various configuration files have been saved.
1172 //
1173 // A similar argument holds for BrowserApplication thread below.
1174 System.exit(exit_status);
1175 }
1176 }
1177 public void stopExternalApplication() {
1178 if(process != null) {
1179 process.destroy();
1180 }
1181 }
1182 }
1183 /** This private class contains an instance of an external application running within a JVM shell. It is important that this process sits in its own thread, but its more important that when we exit the Gatherer we don't actually System.exit(0) the Gatherer object until the user has volunteerily ended all of these child processes. Otherwise when we quit the Gatherer any changes the users may have made in external programs will be lost and the child processes are automatically deallocated. */
1184 static private class BrowserApplication
1185 extends Thread {
1186 private Process process = null;
1187 /** The initial command string given to this sub-process. */
1188 private String command = null;
1189 private String url = null;
1190 private String[] commands = null;
1191
1192 public BrowserApplication(String command, String url) {
1193 StringTokenizer st = new StringTokenizer(command);
1194 int num_tokens = st.countTokens();
1195 this.commands = new String [num_tokens];
1196 int i=0;
1197 while (st.hasMoreTokens()) {
1198 commands[i] = st.nextToken();
1199 i++;
1200 }
1201 //this.commands = commands;
1202 this.url = url;
1203 }
1204 /** We start the child process inside a new thread so it doesn't block the rest of Gatherer.
1205 * @see java.lang.Exception
1206 * @see java.lang.Process
1207 * @see java.lang.Runtime
1208 * @see java.lang.System
1209 * @see java.util.Vector
1210 */
1211 public void run() {
1212 // Call an external process using the args.
1213 if(commands == null) {
1214 apps.remove(this);
1215 return;
1216 }
1217 try {
1218 String prog_name = commands[0];
1219 String lower_name = prog_name.toLowerCase();
1220 if (lower_name.indexOf("mozilla") != -1 || lower_name.indexOf("netscape") != -1) {
1221 DebugStream.println("found mozilla or netscape, trying remote it");
1222 // mozilla and netscape, try using a remote command to get things in the same window
1223 String [] new_commands = new String[] {prog_name, "-raise", "-remote", "openURL("+url+",new-tab)"};
1224 printArray(new_commands);
1225
1226 Runtime rt = Runtime.getRuntime();
1227 process = rt.exec(new_commands);
1228 int exitCode = process.waitFor();
1229 if (exitCode != 0) { // if Netscape or mozilla was not open
1230 DebugStream.println("couldn't do remote, trying original command");
1231 printArray(commands);
1232 process = rt.exec(commands); // try the original command
1233 }
1234 } else {
1235 // just run what we have been given
1236 StringBuffer whole_command = new StringBuffer();
1237 for(int i = 0; i < commands.length; i++) {
1238 whole_command.append(commands[i]);
1239 whole_command.append(" ");
1240 }
1241 DebugStream.println("Running " + whole_command.toString());
1242 Runtime rt = Runtime.getRuntime();
1243 process = rt.exec(commands);
1244 process.waitFor();
1245 }
1246 }
1247
1248 catch (Exception exception) {
1249 DebugStream.printStackTrace(exception);
1250 }
1251 // Remove ourself from Gatherer list of threads.
1252 apps.remove(this);
1253 // Call exit if we were the last outstanding child process thread.
1254 if (apps.size() == 0 && exit == true) {
1255 System.exit(exit_status);
1256 }
1257 }
1258 public void printArray(String [] array) {
1259 for(int i = 0; i < array.length; i++) {
1260 DebugStream.print(array[i]+" ");
1261 System.err.println(array[i]+" ");
1262 }
1263 }
1264 public void stopBrowserApplication() {
1265 if(process != null) {
1266 process.destroy();
1267 }
1268 }
1269 }
1270
1271
1272 private class ImageMagickTest
1273 {
1274 public boolean found()
1275 {
1276 try {
1277 String[] command = new String[2];
1278 command[0] = (Utility.isWindows() ? "identify.exe" : "identify");
1279 command[1] = "-version";
1280 Process image_magick_process = Runtime.getRuntime().exec(command);
1281 image_magick_process.waitFor();
1282
1283 //new way of detection of ImageMagick
1284 InputStreamReader isr = new InputStreamReader(image_magick_process.getInputStream());
1285
1286 BufferedReader br = new BufferedReader(isr);
1287 // Capture the standard output stream and seach for two particular occurances: Version and ImageMagick.
1288
1289 String line = br.readLine();
1290 if (line == null) {
1291 return false;
1292 }
1293 String lc_line = line.toLowerCase();
1294 if (lc_line.indexOf("version") != -1 || lc_line.indexOf("imagemagick") != -1) {
1295 return true;
1296 } else {
1297 return false;
1298 }
1299
1300 //return (image_magick_process.exitValue() == 0);
1301 }
1302 catch (IOException exception) {
1303 return false;
1304 }
1305 catch (InterruptedException exception) {
1306 return false;
1307 }
1308 }
1309 }
1310
1311
1312 private class PerlTest
1313 {
1314 private String[] command = new String[2];
1315
1316 public PerlTest()
1317 {
1318 command[0] = (Utility.isWindows() ? Utility.PERL_EXECUTABLE_WINDOWS : Utility.PERL_EXECUTABLE_UNIX);
1319 command[1] = "-version";
1320 }
1321
1322 public boolean found()
1323 {
1324 try {
1325 Process perl_process = Runtime.getRuntime().exec(command);
1326 perl_process.waitFor();
1327 return (perl_process.exitValue() == 0);
1328 }
1329 catch (Exception exception) {
1330 return false;
1331 }
1332 }
1333
1334 public String toString() {
1335 return command[0];
1336 }
1337 }
1338}
Note: See TracBrowser for help on using the repository browser.