source: trunk/gli/src/org/greenstone/gatherer/collection/CollectionManager.java@ 5761

Last change on this file since 5761 was 5761, checked in by jmt12, 21 years ago

Added a null-test to a debug string that was causing building to fail if the index folder was deleted

  • Property svn:keywords set to Author Date Id Revision
File size: 64.0 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 * <BR><BR>
9 *
10 * Author: John Thompson, Greenstone Digital Library, University of Waikato
11 *
12 * <BR><BR>
13 *
14 * Copyright (C) 1999 New Zealand Digital Library Project
15 *
16 * <BR><BR>
17 *
18 * This program is free software; you can redistribute it and/or modify
19 * it under the terms of the GNU General Public License as published by
20 * the Free Software Foundation; either version 2 of the License, or
21 * (at your option) any later version.
22 *
23 * <BR><BR>
24 *
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
29 *
30 * <BR><BR>
31 *
32 * You should have received a copy of the GNU General Public License
33 * along with this program; if not, write to the Free Software
34 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
35 *########################################################################
36 */
37package org.greenstone.gatherer.collection;
38
39import java.io.*;
40import java.lang.Class;
41import java.util.*;
42import javax.swing.*;
43import javax.swing.event.*;
44import javax.swing.filechooser.FileSystemView;
45import javax.swing.tree.*;
46import org.greenstone.gatherer.Dictionary;
47import org.greenstone.gatherer.Gatherer;
48import org.greenstone.gatherer.cdm.CollectionDesignManager;
49import org.greenstone.gatherer.collection.Collection;
50import org.greenstone.gatherer.collection.SaveCollectionTask;
51import org.greenstone.gatherer.file.FileNode;
52import org.greenstone.gatherer.file.FileSystemModel;
53import org.greenstone.gatherer.gui.LockFileDialog;
54import org.greenstone.gatherer.gui.NewCollectionMetadataPrompt;
55import org.greenstone.gatherer.msm.ElementWrapper;
56import org.greenstone.gatherer.msm.GDMManager;
57import org.greenstone.gatherer.msm.GreenstoneArchiveParser;
58import org.greenstone.gatherer.msm.MetadataSet;
59import org.greenstone.gatherer.msm.MetadataSetManager;
60import org.greenstone.gatherer.msm.MSMEvent;
61import org.greenstone.gatherer.msm.MSMListener;
62import org.greenstone.gatherer.msm.MSMProfiler;
63import org.greenstone.gatherer.msm.MSMUtils;
64import org.greenstone.gatherer.shell.GShell;
65import org.greenstone.gatherer.shell.GShellEvent;
66import org.greenstone.gatherer.shell.GShellListener;
67import org.greenstone.gatherer.shell.GShellProgressMonitor;
68import org.greenstone.gatherer.undo.UndoManager;
69import org.greenstone.gatherer.util.ArrayTools;
70import org.greenstone.gatherer.util.Codec;
71import org.greenstone.gatherer.util.GSDLSiteConfig;
72import org.greenstone.gatherer.util.StaticStrings;
73import org.greenstone.gatherer.util.SynchronizedTreeModelTools;
74import org.greenstone.gatherer.util.Utility;
75import org.w3c.dom.*;
76
77/** This class manages many aspects of the collection, from its creation via scripts, data access via methods and its importing and building into the final collection. It is also resposible for firing appropriate event when significant changes have occured within the collection, and for creating a new metadata set manager as necessary.
78 * @author John Thompson
79 * @version 2.3
80 */
81public class CollectionManager
82 implements GShellListener, MSMListener {
83 /** A reference to the metadata set manager. */
84 public MetadataSetManager msm;
85 /** A reference to the undo manager. Although only one instance is shared between all collections, the undo queues are emptied between each. */
86 public UndoManager undo;
87 /** Are we currently in the process of building? */
88 private boolean building = false;
89 /** Are we currently in the process of importing? */
90 private boolean importing = false;
91 /** The collection this manager is managing! */
92 private Collection collection = null;
93 /** The collection_model. */
94 private FileSystemModel collection_model = null;
95 /** The workspace model. This becomes invalid on a collection change. */
96 private FileSystemModel workspace_model = null;
97 /** An inner class listener responsible for noting tree changes and resetting saved when they occur. */
98 private FMTreeModelListener fm_tree_model_listener = null;
99 /** The monitor resposible for parsing the build process. */
100 private GShellProgressMonitor build_monitor = null;
101 /** The monitor resposible for parsing the copy process. */
102 private GShellProgressMonitor copy_monitor = null;
103 /** The monitor resposible for parsing the import process. */
104 private GShellProgressMonitor import_monitor = null;
105
106 private int special_case = -1;
107 /** Holds a reference to the thread responsible for closing the collection. If non-null then only calls from the given thread will see the collection is non-ready. All other threads will have to wait until closing thread, and all of it consequential calls, are completely finished. */
108 private Thread closing_thread = null;
109
110 /** The name of the standard lock file. */
111 static final public String LOCK_FILE = "gli.lck";
112 static final public int NO_SPECIAL_CASE = -1;
113 static final public int SPECIAL_DLS = 0;
114
115 /** Used to indicate the source of the message is the file collection methods. */
116 static final public int COLLECT = 3;
117 /** Used to indicate the source of the message is the building methods. */
118 static final public int BUILDING = 5;
119
120 /** Constructor. */
121 public CollectionManager() {
122 // Initialisation.
123 this.building = false;
124 this.importing = false;
125 this.collection = null;
126 this.undo = new UndoManager();
127 }
128 /** Add a special directory mapping.
129 * @param name The name for this directory mapping as a <strong>String</strong>.
130 * @param file The directory this mapping maps to as a <strong>File</strong>.
131 */
132 public void addDirectoryMapping(String name, File file) {
133 if(ready()) {
134 // Update the information stored in the collection
135 collection.addDirectoryMapping(name, file);
136 // Now update the tree
137 FileSystemModel model = (FileSystemModel) Gatherer.g_man.collection_pane.getWorkspaceTree().getModel();
138 FileNode parent = (FileNode) model.getRoot();
139 FileNode target = new FileNode(file, name);
140 SynchronizedTreeModelTools.insertNodeInto(model, parent, target);
141 }
142 }
143 /** This method calls the builcol.pl scripts via a GShell so as to not lock up the processor.
144 * @see org.greenstone.gatherer.Configuration
145 * @see org.greenstone.gatherer.Gatherer
146 * @see org.greenstone.gatherer.Message
147 * @see org.greenstone.gatherer.collection.Collection
148 * @see org.greenstone.gatherer.gui.BuildOptions
149 * @see org.greenstone.gatherer.shell.GShell
150 * @see org.greenstone.gatherer.shell.GShellListener
151 * @see org.greenstone.gatherer.shell.GShellProgressMonitor
152 * @see org.greenstone.gatherer.util.Utility
153 */
154 public void buildCollection() {
155 Gatherer.println("CollectionManager.buildCollection()");
156 building = true;
157
158 String args[];
159 if(Utility.isWindows()) {
160 args = new String[4];
161 args[0] = Gatherer.config.perl_path;
162 args[1] = "-S";
163 args[2] = Gatherer.config.getScriptPath() + "buildcol.pl";
164 args[3] = collection.getName();
165 }
166 else {
167 args = new String[2];
168 args[0] = Gatherer.config.getScriptPath() + "buildcol.pl";
169 args[1] = collection.getName();
170 }
171 args = ArrayTools.add(args, collection.build_options.getBuildValues());
172 GShell shell = new GShell(args, GShell.BUILD, BUILDING, this, build_monitor, GShell.GSHELL_BUILD);
173 shell.addGShellListener(Gatherer.g_man.create_pane);
174 shell.start();
175 Gatherer.println("CollectionManager.buildCollection().return");
176 }
177
178 /** Used to determine whether the currently active collection has been built.
179 * @return A boolean indicating the built status of the collection.
180 */
181 public boolean built() {
182 if(collection != null) {
183 // Determine if the collection has been built by looking for the build.cfg file
184 File build_cfg_file = new File(getCollectionIndex() + Utility.BUILD_CFG_FILENAME);
185 return build_cfg_file.exists();
186 }
187 return false;
188 }
189 /** Called to close the current collection and remove its lock file.
190 * @see org.greenstone.gatherer.Gatherer
191 * @see org.greenstone.gatherer.collection.Collection
192 * @see org.greenstone.gatherer.util.Utility
193 */
194 public void closeCollection() {
195 Gatherer.println("Close collection: " + collection.getName());
196 // We set the closing thread, as it should be the only one who can actually see the collection is closed, at least until the closing thread expires.
197 closing_thread = Thread.currentThread();
198 // Remove the lock on this file, then remove the collection.
199 File lock_file = new File(Utility.getCollectionDir(Gatherer.config.gsdl_path) + collection.getName() + File.separator + LOCK_FILE);
200 lock_file.delete();
201 if(lock_file.exists()) {
202 System.err.println("Lockfile was not successfully deleted.");
203 }
204 collection.msm.destroy();
205 collection = null;
206 collection_model = null;
207 workspace_model = null;
208 undo.clear();
209 Gatherer.config.setCollectionConfiguration(null);
210 Gatherer.g_man.collectionChanged(false);
211 // All of the consequences of a close should have been processed by now, so others should now see the collection as non-ready.
212 closing_thread = null;
213 }
214
215 /** Method that is called whenever something has changed in the configuration of this collection. */
216 public void configurationChanged() {
217 if(collection != null) {
218 collection.setSaved(false);
219 }
220 }
221
222 /** Used to set the current collection to the given collection. Note that this call should -always- be proceeded by a ready call, and if the colection is ready and the saved flag is unset then the user should be prompted to save. Also note that this method creates yet another GShell to run buildcol.pl.
223 * @param description a description of the collection as a String
224 * @param email the email address of the author/maintainer as a String
225 * @param name the short name of the collection, which will subsequently be used to refer to this particular collection, as a String
226 * @param title the longer title of the collection as a String
227 * @param base_collection_directory if the user has chosen to base their new collection on an existing one, this is the directory where this base collection can be found, as a File, otherwise its null
228 * @param metadata_sets if the user has decided to select several metadata sets with which to initially populate the GLI then this is an ArrayList of metadata set file names, otherwise its null
229 */
230 public void createCollection(String description, String email, String name, String title, File base_collection_directory, ArrayList metadata_sets) {
231 boolean cancelled = false;
232 special_case = NO_SPECIAL_CASE;
233 try {
234 // Create a progress monitor.
235 ProgressMonitor progress = new ProgressMonitor(Gatherer.g_man, Dictionary.get("CollectionManager.Creating_New"), "mkcol.pl", 0, 7);
236 // Create the new collection.
237 makeCollection(description, email, name, title);
238 progress.setProgress(1);
239
240 String a_dir = Utility.getCollectionDir(Gatherer.config.gsdl_path) + name + File.separator;
241
242 // ACTIVE_DIR/gcache/
243 File gcache_dir_temp = new File(Utility.getCacheDir(a_dir)+"temp.dat");
244 File gcache_dir = gcache_dir_temp.getParentFile();
245 gcache_dir.mkdirs();
246 if(progress != null) {
247 progress.setNote(Dictionary.get("CollectionManager.Gcache_Created"));
248 }
249
250 // ACTIVE_DIR/log/
251 File log_dir_temp = new File(Utility.getLogDir(a_dir)+"temp.dat");
252 File log_dir = log_dir_temp.getParentFile();
253 log_dir.mkdirs();
254 if(progress != null) {
255 progress.setNote(Dictionary.get("CollectionManager.Log_Created"));
256 }
257
258 progress.setProgress(2);
259
260 // Now create the collection object around the directory.
261 collection = new Collection(new File(a_dir, name + ".col"));
262 collection.msm = new MetadataSetManager();
263 msm = collection.msm; // Legacy
264 collection.msm.load();
265
266 // Import default metadata sets if any.
267 for(int i = 0; metadata_sets != null && i < metadata_sets.size(); i++) {
268 MetadataSet metadata_set = (MetadataSet) metadata_sets.get(i);
269 collection.msm.importMDS(metadata_set.getFile(), false);
270 }
271
272 boolean skip_import_phase = false;
273
274 // Before we create the CollectionDesignManager we have to check if we are basing it upon some other collection.
275 if(base_collection_directory != null) {
276 Gatherer.println("Basing new collection on existing one: " + base_collection_directory);
277 collection.setBaseCollection(base_collection_directory.getAbsolutePath());
278 // Try to import any existing metadata sets for this collection. Look in base_collection_directory/metadata and import any metadata sets found.
279 File base_metadata = new File(base_collection_directory, Utility.META_DIR);
280 if(base_metadata.exists()) {
281 Gatherer.println("Found the metadata directory.");
282 File[] possible_metadata_sets = base_metadata.listFiles();
283 for(int i = 0; possible_metadata_sets != null && i < possible_metadata_sets.length; i++) {
284 String filename = possible_metadata_sets[i].getName();
285 if(filename.endsWith(".mds")) {
286 Gatherer.println("+ Found a metadata set. Importing: " + possible_metadata_sets[i].getAbsolutePath());
287 collection.msm.importMDS(possible_metadata_sets[i], false);
288 skip_import_phase = true;
289 }
290 }
291 }
292 else {
293 Gatherer.println("This base collection has no metadata directory.");
294 }
295 // If no sets were imported, then create a new metadata with this new collections name. Note that if this is one of the 'big five' collections then I already know the importing mappings as well.
296 if(collection.msm.getSets().size() == 0) {
297 Gatherer.println("As there are no metadata sets loaded yet, we'll make special checks for the collections available on the GS2.39CD.");
298 CollectionConfiguration col_con = new CollectionConfiguration(new File(base_collection_directory, Utility.CONFIG_DIR));
299 // If the source collection is one of the 'big five' then we know what the metadata set is.
300 String collection_name = col_con.getName();
301 // Demo collection - part of the DLS
302 if(collection_name.equals(Utility.COLLECTION_DEMO) || collection_name.equals(Utility.COLLECTION_DLS)) {
303 Gatherer.println("+ This is a DLS.mds collection.");
304 special_case = SPECIAL_DLS;
305 String demo_directory = (new File(base_collection_directory.getParentFile(), Utility.COLLECTION_DEMO_DIRECTORY)).getAbsolutePath();
306 String dls_directory = (new File(base_collection_directory.getParentFile(), Utility.COLLECTION_DLS_DIRECTORY)).getAbsolutePath();
307 // Add the dls.mds
308 collection.msm.importMDS(new File(Utility.METADATA_DIR + Utility.DLS_MDS), false);
309 // Add the mappings for the dls (even if its not present).
310 collection.msm.profiler.addAction(dls_directory, "AZList", "dls.AZList");
311 collection.msm.profiler.addAction(dls_directory, "Keyword", "dls.Keyword");
312 collection.msm.profiler.addAction(dls_directory, "Language", "dls.Language");
313 collection.msm.profiler.addAction(dls_directory, "Organization", "dls.Organization");
314 collection.msm.profiler.addAction(dls_directory, "Subject", "dls.Subject");
315 collection.msm.profiler.addAction(dls_directory, "Title", "dls.Title");
316 // Add the mappings for the demo dls (even if its not present).
317 collection.msm.profiler.addAction(demo_directory, "AZList", "dls.AZList");
318 collection.msm.profiler.addAction(demo_directory, "Keyword", "dls.Keyword");
319 collection.msm.profiler.addAction(demo_directory, "Language", "dls.Language");
320 collection.msm.profiler.addAction(demo_directory, "Organization", "dls.Organization");
321 collection.msm.profiler.addAction(demo_directory, "Subject", "dls.Subject");
322 collection.msm.profiler.addAction(demo_directory, "Title", "dls.Title");
323 // Skip the import phase
324 skip_import_phase = true;
325 }
326 // Prompt the user so that they can choose at least one initial metadata set. We're sneaky here and just create a ncm_prompt
327 else {
328 Gatherer.println("This collection has no metadata sets, nor is it one of our special collections, thus we cannot import any metadata sets automatically. Present the user with the metadata set selection prompt.");
329 NewCollectionMetadataPrompt ncm_prompt = new NewCollectionMetadataPrompt();
330 // If cancelled then they really do mean to start a collection with no metadata sets.
331 if(!ncm_prompt.isCancelled()) {
332 ArrayList initial_sets = ncm_prompt.getSets();
333 for(int i = 0; initial_sets != null && i < initial_sets.size(); i++) {
334 MetadataSet metadata_set = (MetadataSet) initial_sets.get(i);
335 collection.msm.importMDS(metadata_set.getFile(), false);
336 metadata_set = null;
337 }
338 initial_sets = null;
339 }
340 ncm_prompt.dispose();
341 ncm_prompt = null;
342 }
343 }
344 // Do a dry metadata import run over the entire base collection, recording profile mappings. We do this by finding the archive files, and then iterating over them using the GreenstoneArchiveParser to retrieve metadata from them. We then process the importing of new metadata elements using the selectElement prompt used in a file action metadata import. However the big change is that we don't actually import any metadata, just create importing profiles.
345 if(!skip_import_phase) {
346 File base_archive = new File(base_collection_directory, Utility.ARCHIVE_DIR);
347 if(base_archive.exists()) {
348 Gatherer.println("+ Archive directory found. Inspecting archives for metadata information.");
349 ArrayList metadata_elements = GreenstoneArchiveParser.extractMetadataElements(base_archive);
350 for(int i = 0; !cancelled && i < metadata_elements.size(); i++) {
351 String metadata_name = (String) metadata_elements.get(i);
352 ElementWrapper target = collection.msm.prompt.selectElement(metadata_name);
353 cancelled = Gatherer.c_man.getCollection().msm.prompt.wasDialogCancelled();
354 if(!cancelled) {
355 if(target != null) {
356 collection.msm.profiler.addAction(base_collection_directory.getAbsolutePath(), metadata_name, target.getName());
357 }
358 else {
359 collection.msm.profiler.addAction(base_collection_directory.getAbsolutePath(), metadata_name, null);
360 }
361 }
362 }
363 // Hopefully mappings should now be in place for metadata extracted from this collection.
364 }
365 else {
366 Gatherer.println("+ Searching files for metadata.xml information.");
367 // Find the import directory
368 File base_import = new File(base_collection_directory, Utility.IMPORT_DIR);
369 if(base_import.exists()) {
370 searchForMetadata(base_import);
371 }
372 }
373 }
374 // And if that fails then we must have been asked by Satan himself to build the very digital collections of hell, because they don't match any goodly greenstone collection I have ever seen, so you can't blame me if I can't import them.
375
376 // Now we update our collect.cfg
377 Gatherer.println("Copy and update collect.cfg from base collection.");
378 updateCollectionCFG(new File(base_collection_directory, Utility.CONFIG_DIR), new File(a_dir, Utility.CONFIG_DIR), description, email, title);
379 }
380
381 // Always import the extracted metadata set if we didn't already
382 if(collection.msm.getSet(Utility.EXTRACTED_METADATA_NAMESPACE) == null) {
383 collection.msm.importMDS(new File(Utility.METADATA_DIR + Utility.EXTRACTED_METADATA_NAMESPACE + StaticStrings.METADATA_SET_EXTENSION), false);
384 }
385
386 collection.cdm = new CollectionDesignManager(new File(getCollectionConfig()));
387 collection.gdm = new GDMManager();
388
389 progress.setProgress(3);
390
391 // Has to be done after creating metadata set manager.
392 File gmeta_dir_temp = new File(getCollectionMetadata()+"temp.dat");
393 File gmeta_dir = gmeta_dir_temp.getParentFile();
394 gmeta_dir.mkdirs();
395 if(progress != null) {
396 progress.setNote("GMeta created");
397 }
398 progress.setProgress(4);
399
400 progress.setProgress(6);
401 // Register ourselves as being interested in what the msm has to say.
402 collection.msm.addMSMListener(this);
403 // Create a lock file.
404 File lock_file = new File(a_dir, LOCK_FILE);
405 FileOutputStream out = new FileOutputStream(lock_file);
406 out.write(LOCK_FILE.getBytes());
407 out.close();
408 out = null;
409 progress.setProgress(7);
410 String args[] = new String[1];
411 args[0] = name;
412 progress.setNote(Dictionary.get("CollectionManager.Session_Ready", args));
413 progress.close();
414 }
415 catch (Exception error) {
416 Gatherer.printStackTrace(error);
417 }
418 // Done.
419 if(Gatherer.g_man != null) {
420 workspace_model = null;
421 // set the view to Gather pane
422 Gatherer.g_man.setSelectedView(Gatherer.g_man.collection_pane);
423 Gatherer.g_man.collectionChanged(ready());
424 }
425 }
426
427 public void createLockFile(File destination) {
428 try {
429 Document default_lockfile = Utility.parse("xml/" + LOCK_FILE, true);
430 String user_name = System.getProperty("user.name");
431 Element person_element = (Element) MSMUtils.getNodeFromNamed(default_lockfile.getDocumentElement(), "User");
432 person_element.appendChild(default_lockfile.createTextNode(user_name));
433 person_element = null;
434 user_name = null;
435 String machine_name = Utility.getMachineName();
436 Element machine_element = (Element) MSMUtils.getNodeFromNamed(default_lockfile.getDocumentElement(), "Machine");
437 machine_element.appendChild(default_lockfile.createTextNode(machine_name));
438 machine_element = null;
439 machine_name = null;
440 String date_time = Utility.getDateString();
441 Element date_element = (Element) MSMUtils.getNodeFromNamed(default_lockfile.getDocumentElement(), "Date");
442 date_element.appendChild(default_lockfile.createTextNode(date_time));
443 date_element = null;
444 date_time = null;
445 Utility.export(default_lockfile, destination);
446 }
447 catch (Exception error) {
448 Gatherer.printStackTrace(error);
449 }
450 }
451
452 /** Method that is called whenever an element within a set is changed or modified. We want to mark the collection so that it needs saving again.
453 * @param event A <strong>MSMEvent</strong> containing details of the event that caused this message to be fired.
454 * @see org.greenstone.gatherer.collection.Collection
455 */
456 public void elementChanged(MSMEvent event) {
457 // This means the state of the collections has changed, so we should set saved to false.
458 collection.setSaved(false);
459 }
460 /** Used to retrieve the build options associated with the currently loaded collection. If none yet exist, default ones are created.
461 * @return A <strong>BuildOptions</strong> object containing the build options for the current collection.
462 * @see org.greenstone.gatherer.collection.Collection
463 */
464 public BuildOptions getBuildOptions() {
465 return collection.build_options;
466 }
467
468 /** Retrieve the current collection.
469 * @return The <strong>Collection</strong> itself.
470 */
471 public Collection getCollection() {
472 return collection;
473 }
474 /** Constructs the absolute filename of the collection archive directory, which should resemble "$GSDLHOME/collect/&lt;col_name&gt;/archive/"
475 * @return A <strong>String</strong> containing the filename.
476 * @see org.greenstone.gatherer.Configuration
477 * @see org.greenstone.gatherer.Gatherer
478 * @see org.greenstone.gatherer.collection.Collection
479 * @see org.greenstone.gatherer.util.Utility
480 */
481 public String getCollectionArchive() {
482 return Utility.getArchiveDir(Gatherer.config.gsdl_path, collection.getName());
483 }
484 /** Constructs the absolute filename of the collection building directory, which should resemble "$GSDLHOME/collect/&lt;col_name&gt;/building/"
485 * @return A <strong>String</strong> containing the filename.
486 * @see org.greenstone.gatherer.Configuration
487 * @see org.greenstone.gatherer.Gatherer
488 * @see org.greenstone.gatherer.collection.Collection
489 * @see org.greenstone.gatherer.util.Utility
490 */
491 public String getCollectionBuild() {
492 return Utility.getBuildDir(Utility.getCollectionDir(Gatherer.config.gsdl_path) + collection.getName() + File.separator);
493 }
494 /** Constructs the absolute filename of the collection cache directory, which should resemble "$GSDLHOME/collect/&lt;col_name&gt;/gcache/"
495 * @return A <strong>String</strong> containing the filename.
496 * @see org.greenstone.gatherer.Configuration
497 * @see org.greenstone.gatherer.Gatherer
498 * @see org.greenstone.gatherer.collection.Collection
499 * @see org.greenstone.gatherer.util.Utility
500 */
501 public String getCollectionCache() {
502 return Utility.getCacheDir(Utility.getCollectionDir(Gatherer.config.gsdl_path) + collection.getName() + File.separator);
503 }
504 /** Constructs the absolute filename of the collection config file, which should resemble "$GSDLHOME/collect/&lt;col_name&gt;/etc/collect.cfg"
505 * @return A <strong>String</strong> containing the filename.
506 * @see org.greenstone.gatherer.Configuration
507 * @see org.greenstone.gatherer.Gatherer
508 * @see org.greenstone.gatherer.collection.Collection
509 * @see org.greenstone.gatherer.util.Utility
510 */
511 public String getCollectionConfig() {
512 return Utility.getConfigDir(Utility.getCollectionDir(Gatherer.config.gsdl_path) + collection.getName() + File.separator);
513 }
514
515 /** Constructs the absolute filename of the collection directory, which should resemble "$GSDLHOME/collect/&lt;col_name&gt;"
516 * @return A <strong>String</strong> containing the directory name.
517 * @see org.greenstone.gatherer.Configuration
518 * @see org.greenstone.gatherer.Gatherer
519 * @see org.greenstone.gatherer.collection.Collection
520 * @see org.greenstone.gatherer.util.Utility
521 */
522 public String getCollectionDirectory() {
523 return Utility.getCollectionDir(Gatherer.config.gsdl_path) + collection.getName() + File.separator;
524 }
525
526 /** Constructs the absolute filename of the collection etc directory, which should resemble "$GSDLHOME/collect/&lt;col_name&gt;/etc/"
527 * @return A <strong>String</strong> containing the filename.
528 * @see org.greenstone.gatherer.Configuration
529 * @see org.greenstone.gatherer.Gatherer
530 * @see org.greenstone.gatherer.collection.Collection
531 * @see org.greenstone.gatherer.util.Utility
532 */
533 public String getCollectionEtc() {
534 return Utility.getEtcDir(Utility.getCollectionDir(Gatherer.config.gsdl_path) + collection.getName() + File.separator);
535 }
536 /** Constructs the absolute filename of the collection file, which should resemble "$GSDLHOME/collect/&lt;col_name&gt;/&lt;col_name&gt;.col"
537 * @return A <strong>String</strong> containing the filename.
538 * @see org.greenstone.gatherer.Configuration
539 * @see org.greenstone.gatherer.Gatherer
540 * @see org.greenstone.gatherer.collection.Collection
541 * @see org.greenstone.gatherer.util.Utility
542 */
543 public String getCollectionFilename() {
544 return Utility.getCollectionDir(Gatherer.config.gsdl_path) + collection.getName() + File.separator + collection.getName() + ".col";
545 }
546 /** Constructs the absolute filename of the collection import directory, which should resemble "$GSDLHOME/collect/&lt;col_name&gt;/import/"
547 * @return A <strong>String</strong> containing the filename.
548 * @see org.greenstone.gatherer.Configuration
549 * @see org.greenstone.gatherer.Gatherer
550 * @see org.greenstone.gatherer.collection.Collection
551 * @see org.greenstone.gatherer.util.Utility
552 */
553 public String getCollectionImport() {
554 return Utility.getImportDir(Utility.getCollectionDir(Gatherer.config.gsdl_path) + collection.getName() + File.separator);
555 }
556 /** Constructs the absolute filename of the collection index directory, which should resemble "$GSDLHOME/collect/&lt;col_name&gt;/index/"
557 * @return A <strong>String</strong> containing the filename.
558 * @see org.greenstone.gatherer.Configuration
559 * @see org.greenstone.gatherer.Gatherer
560 * @see org.greenstone.gatherer.collection.Collection
561 * @see org.greenstone.gatherer.util.Utility
562 */
563 public String getCollectionIndex() {
564 return Utility.getIndexDir(Utility.getCollectionDir(Gatherer.config.gsdl_path) + collection.getName() + File.separator);
565 }
566 /** Constructs the absolute filename of the collection log directory, which should resemble "$GSDLHOME/collect/&lt;col_name&gt;/log/"
567 * @return A <strong>String</strong> containing the filename.
568 * @see org.greenstone.gatherer.Configuration
569 * @see org.greenstone.gatherer.Gatherer
570 * @see org.greenstone.gatherer.collection.Collection
571 * @see org.greenstone.gatherer.util.Utility
572 */
573 public String getCollectionLog() {
574 return Utility.getLogDir(Utility.getCollectionDir(Gatherer.config.gsdl_path) + collection.getName() + File.separator);
575 }
576 /** Constructs the absolute filename of the collection metadata directory, which should resemble "$GSDLHOME/collect/&lt;col_name&gt;/metadata/"
577 * @return A <strong>String</strong> containing the filename.
578 * @see org.greenstone.gatherer.Configuration
579 * @see org.greenstone.gatherer.Gatherer
580 * @see org.greenstone.gatherer.collection.Collection
581 * @see org.greenstone.gatherer.util.Utility
582 */
583 public String getCollectionMetadata() {
584 return Utility.getMetadataDir(Utility.getCollectionDir(Gatherer.config.gsdl_path) + collection.getName() + File.separator);
585 }
586
587 /** This method either returns the title of the current collection, or a placeholder string of 'No Collection'.
588 * @return A <strong>String</strong> which represents what we wish to display for a collection title.
589 * @see org.greenstone.gatherer.collection.Collection
590 */
591 public String getCollectionTitle() {
592 if(collection != null) {
593 return collection.getTitle();
594 }
595 return Dictionary.get("Collection.No_Collection");
596 }
597
598 /** Retrieve the record set (tree model) associated with the current collection. */
599 public TreeModel getRecordSet() {
600 if(collection_model == null && collection != null) {
601 // Use the import directory to generate a new FileSystemModel
602 collection_model = new FileSystemModel(new FileNode(new File(getCollectionImport()), false));
603 // Ensure that the manager is a change listener for the tree.
604 if(fm_tree_model_listener == null) {
605 fm_tree_model_listener = new FMTreeModelListener();
606 }
607 collection_model.addTreeModelListener(fm_tree_model_listener);
608 }
609 return collection_model;
610 }
611 /** Create and return the model behind the workspace tree. Quite an extensive method, as it must first map known greenstone collections, then the local file system and finally any public or private download workspaces. */
612 public TreeModel getWorkspace() {
613 ///ystem.err.println("Get Workspace");
614 if(workspace_model != null) {
615 return workspace_model;
616 }
617 // Determine the local collection directory if any
618 String current_collection_directory = null;
619 if(collection != null) {
620 current_collection_directory = Utility.getCollectionDir(Gatherer.config.gsdl_path) + collection.getName();
621 }
622 // Create the workspace tree.
623 FileNode workspace_root = new FileNode("ABS_ROOT");
624 workspace_model = new FileSystemModel(workspace_root);
625 // Create and add Greenstone collections node.
626 // Starting at the collection directory of gsdl...
627 FileNode world_root = new FileNode(Dictionary.get("Tree.World"));
628 world_root.unmap();
629 workspace_root.insert(world_root);
630 // Create Local File space.
631 // Get all the available roots mounted on the system.
632 File roots[] = File.listRoots();
633 // If there is just one root use it as the tree root (linux)
634 if(roots != null) {
635 FileNode file_root;
636 String name = Dictionary.get("Tree.Root");
637 if(roots.length == 1) {
638 file_root = new FileNode(roots[0], name);
639 workspace_root.insert(file_root);
640 }
641 // Otherwise build a dummy node which has these nodes as children.
642 else {
643 file_root = new FileNode(name);
644 workspace_root.insert(file_root);
645 // Hopefully this does an alphabetic sort.
646 ArrayTools.sort(roots);
647 for(int i = 0; i < roots.length; i++) {
648 // Only add root if it isn't a floppy drive.
649 if(!FileSystemView.getFileSystemView().isFloppyDrive(roots[i])) {
650 FileNode child_root = new FileNode(roots[i]);
651 file_root.insert(child_root);
652 child_root = null;
653 }
654 }
655 }
656 name = null;
657 file_root = null;
658 }
659
660 // Now if we can determine user home folder information, generate a 'special mapping' to it
661 String home_folder_str = System.getProperty("user.home");
662 if(home_folder_str != null && home_folder_str.length() > 0) {
663 File home_folder = new File(home_folder_str);
664 String[] args = new String[1];
665 args[0] = home_folder.getName();
666 FileNode home_folder_node = new FileNode(home_folder, Dictionary.get("Tree.Home", args));
667 workspace_root.insert(home_folder_node);
668 }
669
670 // If mirroring is enabled show the public and private caches.
671 if(Gatherer.config.get("workflow.mirror", false)) {
672 // Add Public workspace
673 FileNode public_root = new FileNode(new File(Utility.CACHE_DIR), Dictionary.get("Tree.Public"));
674 workspace_root.insert(public_root);
675 // Add Private workspace if a collection has been loaded.
676 if(ready()) {
677 FileNode private_root = new FileNode(new File(getCollectionCache()), Dictionary.get("Tree.Private"));
678 workspace_root.insert(private_root);
679 }
680 }
681 // Finally we retrieve and map any predefined special directories.
682 if(ready()) {
683 HashMap mappings = collection.getDirectoryMappings();
684 for(Iterator names = mappings.keySet().iterator(); names.hasNext(); ) {
685 String name = (String) names.next();
686 File file = (File) mappings.get(name);
687 FileNode special_root = new FileNode(file, name);
688 //workspace_root.insert(special_root);
689 SynchronizedTreeModelTools.insertNodeInto(workspace_model, workspace_root, special_root);
690 }
691 }
692 ///ystem.err.println("Returning for getWorkspace()");
693 return workspace_model;
694 }
695 /** This method when called, creates a new GShell in order to run the import.pl script.
696 * @see org.greenstone.gatherer.Configuration
697 * @see org.greenstone.gatherer.Gatherer
698 * @see org.greenstone.gatherer.Message
699 * @see org.greenstone.gatherer.gui.BuildOptions
700 * @see org.greenstone.gatherer.shell.GShell
701 * @see org.greenstone.gatherer.shell.GShellListener
702 * @see org.greenstone.gatherer.shell.GShellProgressMonitor
703 * @see org.greenstone.gatherer.util.Utility
704 */
705 public void importCollection() {
706 Gatherer.println("CollectionManager.importCollection()");
707 if(!saved()) {
708 import_monitor.saving();
709 // Force save.
710 try {
711 SaveCollectionTask save_task = new SaveCollectionTask(collection);
712 save_task.setImportAfter(true);
713 save_task.start();
714 }
715 catch(Exception error) {
716 Gatherer.printStackTrace(error);
717 }
718 }
719 else {
720 importing = true;
721 // Remove erroneous file windows file separator as it causes problems when running import.pl
722 String collection_import = getCollectionImport();
723 if(collection_import.length() > 2 && collection_import.endsWith("\\")) {
724 collection_import = collection_import.substring(0, collection_import.length() - 1);
725 }
726 String args[];
727 if(Utility.isWindows()) {
728 args = new String[6];
729 args[0] = Gatherer.config.perl_path;
730 args[1] = "-S";
731 args[2] = Gatherer.config.getScriptPath() + "import.pl";
732 args[3] = "-importdir";
733 args[4] = collection_import;
734 args[5] = collection.getName();
735 }
736 else {
737 args = new String[4];
738 args[0] = Gatherer.config.getScriptPath() + "import.pl";
739 args[1] = "-importdir";
740 args[2] = collection_import;
741 args[3] = collection.getName();
742 }
743 collection_import = null;
744 args = ArrayTools.add(args, collection.build_options.getImportValues());
745 GShell shell = new GShell(args, GShell.IMPORT, BUILDING, this, import_monitor, GShell.GSHELL_IMPORT);
746 shell.addGShellListener(Gatherer.g_man.create_pane);
747 shell.start();
748 }
749 Gatherer.println("CollectionManager.importCollection().return");
750 }
751 /** Attempts to load the given collection. Currently uses simple serialization of the collection class.
752 * @param location The path to the collection as a <strong>String</strong>.
753 * @see org.greenstone.gatherer.Configuration
754 * @see org.greenstone.gatherer.Gatherer
755 * @see org.greenstone.gatherer.Message
756 * @see org.greenstone.gatherer.collection.Collection
757 * @see org.greenstone.gatherer.msm.MetadataSetManager
758 * @see org.greenstone.gatherer.msm.MSMListener
759 * @see org.greenstone.gatherer.util.Utility
760 */
761 public boolean loadCollection(String location) {
762 Gatherer.println("Load Collection '" + location + "'");
763 String[] args2 = new String[1];
764 args2[0] = location;
765 boolean result = false;
766 // Check we have actually been given a .col file.
767 if(location.endsWith(".col")) {
768 File collection_file = new File(location);
769 // Ensure that the directory exists.
770 File collection_directory = collection_file.getParentFile();
771 if(collection_directory.exists()) {
772 // Special case of a user trying to open an old greenstone collection. One day this will be supported, but for now complain, and explain how to actually re-build this collection
773 File metadata_directory = new File(collection_directory, Utility.META_DIR);
774 if(!metadata_directory.exists()) {
775 JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("CollectionManager.Old_Collection", args2), Dictionary.get("General.Warning"), JOptionPane.INFORMATION_MESSAGE);
776 }
777 // Normal GLI collection
778 else {
779 String name = collection_directory.getName();
780 File lock_file = new File(collection_file.getParentFile(), LOCK_FILE);
781 // Now determine if a lock already exists on this collection.
782 int choice = LockFileDialog.YES_OPTION;
783 if(lock_file.exists()) {
784 LockFileDialog dialog = new LockFileDialog(Gatherer.g_man, name, lock_file);
785 choice = dialog.getChoice();
786 dialog.dispose();
787 dialog = null;
788 }
789 if(choice == LockFileDialog.YES_OPTION) {
790 try {
791 if(lock_file.exists()) {
792 lock_file.delete();
793 }
794 // Create a lock file.
795 createLockFile(lock_file);
796 // Open the collection file
797 collection = new Collection(collection_file);
798 ///ystem.err.println("In CollectionManager::loadCollection(), collection: " + collection);
799 collection.msm = new MetadataSetManager();
800 msm = collection.msm; // Legacy
801 collection.msm.load();
802 collection.cdm = new CollectionDesignManager(new File(collection_file.getParent(), Utility.CONFIG_DIR));
803 collection.gdm = new GDMManager();
804 // Tell everyone that it worked.
805 String[] args = new String[1];
806 args[0] = name;
807 Gatherer.println(Dictionary.get("CollectionManager.Loading_Successful", args));
808 // Now we need to hook up classes that depend on messages from the metadata set manager to keep their content fresh.
809 collection.msm.addMSMListener(this);
810 // We're done. Let everyone know.
811 if(Gatherer.g_man != null) {
812 workspace_model = null;
813 Gatherer.g_man.collectionChanged(ready());
814 }
815 result = true;
816 ///ystem.err.println("Done loadCollection().");
817 } catch (Exception error) {
818 // There is obviously no existing collection present.
819 Gatherer.printStackTrace(error);
820 JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("CollectionManager.Cannot_Open", args2), Dictionary.get("General.Error"), JOptionPane.ERROR_MESSAGE);
821 }
822 }
823 lock_file = null;
824 }
825 }
826 else {
827 JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("CollectionManager.File_Not_Found", args2), Dictionary.get("General.Error"), JOptionPane.ERROR_MESSAGE);
828 }
829 collection_directory = null;
830 }
831 else {
832 JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("CollectionManager.Not_Col_File", args2), Dictionary.get("General.Error"), JOptionPane.ERROR_MESSAGE);
833 Gatherer.println("Not a Gatherer Collection.");
834 }
835 args2 = null;
836 return result;
837 }
838
839 public void makeCollection(String description, String email, String name, String title) {
840 // Encode the description so it is safe to write to shell
841 if(Utility.isWindows()) {
842 description = Codec.transform(description, Codec.TEXT_TO_SHELL_WINDOWS);
843 }
844 else {
845 description = Codec.transform(description, Codec.TEXT_TO_SHELL_UNIX);
846 }
847 // Run the mkcol command.
848 String command[];
849 if(Utility.isWindows()) {
850 if(description == null || email == null || title == null) {
851 command = new String[4];
852 }
853 else {
854 command = new String[10];
855 }
856 command[0] = Gatherer.config.perl_path;
857 command[1] = "-S";
858 command[2] = Gatherer.config.getScriptPath() + "mkcol.pl";
859 if(description == null || email == null || title == null) {
860 command[3] = name;
861 }
862 else {
863 command[3] = "-title";
864 command[4] = title;
865 command[5] = "-creator";
866 command[6] = email;
867 command[7] = "-about";
868 command[8] = description;
869 command[9] = name;
870 }
871 }
872 else {
873 if(description == null || email == null || title == null) {
874 command = new String[2];
875 command[0] = "mkcol.pl";
876 command[1] = name;
877 }
878 else {
879 command = new String[8];
880 command[0] = "mkcol.pl";
881 command[1] = "-title";
882 command[2] = title;
883 command[3] = "-creator";
884 command[4] = email;
885 command[5] = "-about";
886 command[6] = description;
887 command[7] = name;
888 }
889 }
890 GShell process = new GShell(command, GShell.NEW, COLLECT, this, null, GShell.GSHELL_NEW);
891 process.addGShellListener(this);
892 process.run(); // Don't bother threading this... yet
893 }
894
895 /** Any implementation of GShellListener must include this method to allow the GShell to send messages to listeners. However in this case the CollectionManager is in no way interested in what the messages are, just the import events which have a specific type and are handled elsewhere. Thus we can safely ignore this event.
896 * @param event A <strong>GShellEvent</strong> which contains a the message.
897 */
898 public synchronized void message(GShellEvent event) {
899 }
900 /** Called whenever the metadata value changes in some way, such as the addition of a new value. We want to mark the collection so that it needs saving again.
901 * @param event A <strong>MSMEvent</strong> containing details of the event that caused this message to be fired.
902 * @see org.greenstone.gatherer.collection.Collection
903 */
904 public void metadataChanged(MSMEvent event) {
905 // Again this change means we need to save the collection again.
906 collection.setSaved(false);
907 }
908 /** This call is fired whenever a process within a GShell created by this class begins.
909 * @param event A <strong>GShellEvent</strong> containing information about the GShell process.
910 * @see org.greenstone.gatherer.Gatherer
911 * @see org.greenstone.gatherer.gui.GUIManager
912 * @see org.greenstone.gatherer.shell.GShell
913 */
914 public synchronized void processBegun(GShellEvent event) {
915 Gatherer.println("CollectionManager.processBegun(" + event.getType() + ")");
916 ///ystem.err.println("ProcessBegun " + event.getType());
917 // If this is one of the types where we wish to lock user control
918 Gatherer.g_man.lockCollection((event.getType() == GShell.IMPORT), true);
919 }
920 /** This call is fired whenever a process within a GShell created by this class ends.
921 * @param event A <strong>GShellEvent</strong> containing information about the GShell process.
922 * @see org.greenstone.gatherer.Gatherer
923 * @see org.greenstone.gatherer.gui.GUIManager
924 * @see org.greenstone.gatherer.shell.GShell
925 */
926 public synchronized void processComplete(GShellEvent event) {
927 Gatherer.println("CollectionManager.processComplete(" + event.getType() + ")");
928 ///ystem.err.println("ProcessComplete " + event.getType());
929 Gatherer.g_man.lockCollection((event.getType() == GShell.IMPORT), false);
930 ///ystem.err.println("Recieved process complete event - " + event);
931 // If we were running an import, now run a build.
932 if(event.getType() == GShell.IMPORT && event.getStatus() != GShell.ERROR) {
933 // Finish import.
934 collection.setImported(true);
935 buildCollection();
936 }
937 // If we were running a build, now is when we move files across.
938 else if(event.getType() == GShell.BUILD && event.getStatus() != GShell.ERROR) {
939 ///ystem.err.println("Installing collection.");
940 installCollection();
941 // If we have a local library running (that we know about) then we ask it to add our newly create collection
942 ///ystem.err.println("Check if we should reset local server.");
943 if(Gatherer.config.exec_file != null) {
944 ///ystem.err.println("Local Library Found!");
945 Gatherer.g_man.preview_pane.configServer(GSDLSiteConfig.ADD_COMMAND + collection.getName());
946 }
947 //else {
948 ///ystem.err.println("GLI can't recognize a local library.");
949 //}
950 // Signal collection changed.
951 workspace_model = null;
952 Gatherer.g_man.collectionChanged(ready());
953 JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("CollectionManager.Preview_Ready"), Dictionary.get("CollectionManager.Preview_Ready_Title"), JOptionPane.INFORMATION_MESSAGE);
954 }
955 else if(event.getStatus() == GShell.ERROR) {
956 JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("CollectionManager.Preview_Ready_Failed"), Dictionary.get("CollectionManager.Preview_Ready_Title"), JOptionPane.ERROR_MESSAGE);
957 Gatherer.g_man.collectionChanged(ready());
958 }
959 }
960 /** Determine if the manager is ready for actions apon its collection.
961 * @return A <i>boolean</i> which is <i>true</i> to indicate a collection has been loaded and thus the collection is ready for editing, <i>false</i> otherwise.
962 */
963 public synchronized boolean ready() {
964 if(collection != null) {
965 return true;
966 }
967 else {
968 return false;
969 }
970 }
971
972 public synchronized boolean reallyReady() {
973 // If the closing thread is non-null we should only allow that thread to see the collection as closed.
974 if(closing_thread != null) {
975 // Only the closing thread sees the truth
976 if(Thread.currentThread() == closing_thread) {
977 return (collection == null);
978 }
979 // All other threads are told a lie.
980 else {
981 return true;
982 }
983 }
984 else {
985 if(collection != null) {
986 return true;
987 }
988 else {
989 return false;
990 }
991 }
992 }
993
994 /** Refresh the Greenstone Collections special mapping in the workspace tree to account for collections being added/removed
995 */
996 public void refreshGreenstoneCollections() {
997 FileNode root = (FileNode) workspace_model.getRoot();
998 FileNode greenstone_collections_node = null;
999 int root_child_count = root.getChildCount();
1000 String greenstone_collections_str = Dictionary.get("Tree.World");
1001 for(int i = 0; greenstone_collections_node == null && i < root_child_count; i++) {
1002 TreeNode child = root.getChildAt(i);
1003 if(child.toString().equals(greenstone_collections_str)) {
1004 greenstone_collections_node = (FileNode) child;
1005 }
1006 child = null;
1007 }
1008 greenstone_collections_str = null;
1009 root = null;
1010 if(greenstone_collections_node != null) {
1011 TreePath greenstone_collections_path = new TreePath(greenstone_collections_node.getPath());
1012 workspace_model.refresh(greenstone_collections_path);
1013 greenstone_collections_path = null;
1014 greenstone_collections_node = null;
1015 }
1016 }
1017
1018 /** Called to refresh the models upon which the trees are based.
1019 * @see org.greenstone.gatherer.collection.Collection
1020 */
1021 public void refreshTrees() {
1022 }
1023 /** This method associates the collection build monitor with the build monitor created in CreatePane.
1024 * @param monitor A <strong>GShellProgressMonitor</strong> which we will use as the build monitor.
1025 */
1026 public void registerBuildMonitor(GShellProgressMonitor monitor) {
1027 build_monitor = monitor;
1028 }
1029 /** This method associates the collection copy monitor with the copy monitor created in CreatePane.
1030 * @param monitor A <strong>GShellProgressMonitor</strong> which we will use as the copy monitor.
1031 */
1032 public void registerCopyMonitor(GShellProgressMonitor monitor) {
1033 copy_monitor = monitor;
1034 }
1035 /** This method associates the collection import monitor with the import monitor created in CreatePane.
1036 * @param monitor A <strong>GShellProgressMonitor</strong> which we will use as the import monitor.
1037 */
1038 public void registerImportMonitor(GShellProgressMonitor monitor) {
1039 import_monitor = monitor;
1040 }
1041 /** Remove a previously assigned special directory mapping.
1042 * @param name The symbolic name of the special directory mapping to remove as a <strong>String</strong>.
1043 * @return The <strong>File</strong> of the mapping removed.
1044 */
1045 public File removeDirectoryMapping(FileNode target) {
1046 File file = null;
1047 if(ready()) {
1048 // Remove from collection, remembering file
1049 file = collection.removeDirectoryMapping(target.toString());
1050 // Update tree.
1051 FileSystemModel model = (FileSystemModel) Gatherer.g_man.collection_pane.getWorkspaceTree().getModel();
1052 SynchronizedTreeModelTools.removeNodeFromParent(model, target);
1053 }
1054 return file;
1055 }
1056 /** Used to check whether all open collections have a 'saved' state.
1057 * @return A <i>boolean</i> which is <i>true</i> if the collection has been saved.
1058 * @see org.greenstone.gatherer.collection.Collection
1059 */
1060 public boolean saved() {
1061 boolean result = true;
1062 if(collection != null) {
1063 result = collection.getSaved();
1064 }
1065 return result;
1066 }
1067 /** Saves a collection by serializing it to file.
1068 * @param close_after <i>true</i> to cause the Gatherer to close the collection once save is complete, <i>false</i> otherwise.
1069 * @param exit_after <i>true</i> to cause the Gatherer to exit once save is complete, <i>false</i> otherwise.
1070 * @see org.greenstone.gatherer.Gatherer
1071 * @see org.greenstone.gatherer.Message
1072 * @see org.greenstone.gatherer.gui.GUIManager
1073 * @see org.greenstone.gatherer.gui.GConfigPane
1074 * @see org.greenstone.gatherer.collection.Collection
1075 */
1076 public void saveCollection(boolean close_after, boolean exit_after) {
1077 Gatherer.println("Save collection: " + collection.getName());
1078 try {
1079 SaveCollectionTask save_task = new SaveCollectionTask(collection, close_after, exit_after);
1080 save_task.start();
1081 // Run this in the same thread
1082 //save_task.run();
1083 }
1084 catch(Exception error) {
1085 Gatherer.printStackTrace(error);
1086 }
1087 }
1088 /** Saves the current collection to a new filename, then restores the original collection. Finally opens the collection copy.
1089 * @param name The name collection name.
1090 */
1091 public void saveCollectionAs(String name) {
1092 // We need to do this in a separate thread so create a SaveCollectionAsTask
1093 try {
1094 SaveCollectionTask save_task = new SaveCollectionTask(collection, name);
1095 save_task.start();
1096 }
1097 catch(Exception error) {
1098 Gatherer.printStackTrace(error);
1099 }
1100 }
1101
1102 /** Method that is called whenever the metadata set collection changes in some way, such as the addition of a new set or the merging of two sets. We want to mark the collection so that it needs saving again.
1103 * @param event A <strong>MSMEvent</strong> containing details of the event that caused this message to be fired.
1104 * @see org.greenstone.gatherer.collection.Collection
1105 */
1106 public void setChanged(MSMEvent event) {
1107 // Invalidate saved
1108 collection.setSaved(false);
1109 }
1110
1111 /** Updates the given workspace tree model to reference the private cache of the currently loaded collection. */
1112 public void updatePrivateWorkspace(DefaultTreeModel model) {
1113 // Add Private workspace if a collection has been loaded.
1114 if(ready() && !Gatherer.config.get("workflow.mirror", true)) {
1115 FileNode root = (FileNode)model.getRoot();
1116 // Remove old private workspace
1117 FileNode old = (FileNode)model.getChild(root, 2);
1118 model.removeNodeFromParent(old);
1119 // Create and insert new.
1120 FileNode private_workspace = new FileNode(new File(getCollectionCache()), Dictionary.get("Tree.Private"));
1121 model.insertNodeInto(private_workspace, root, 2);
1122 }
1123 }
1124 /** Called whenever the value tree of an metadata element changes in some way, such as the addition of a new value. We want to mark the collection so that it needs saving again.
1125 * @param event A <strong>MSMEvent</strong> containing details of the event that caused this message to be fired.
1126 * @see org.greenstone.gatherer.collection.Collection
1127 */
1128 public void valueChanged(MSMEvent event) {
1129 collection.setSaved(false);
1130 }
1131
1132 /** Install collection by moving its files from building to index after a successful build.
1133 * @see org.greenstone.gatherer.Gatherer
1134 * @see org.greenstone.gatherer.util.Utility
1135 */
1136 private void installCollection() {
1137 Gatherer.println("Build complete. Moving files.");
1138
1139 try {
1140 // We have to ensure that the local library
1141 if(Gatherer.config.exec_file != null) {
1142 ///ystem.err.println("Local Library Found!");
1143 Gatherer.g_man.preview_pane.configServer(GSDLSiteConfig.RELEASE_COMMAND + collection.getName());
1144 }
1145
1146 File index_dir = new File(getCollectionIndex(), "temp.txt");
1147 index_dir = index_dir.getParentFile();
1148 if(index_dir.exists()) {
1149 Gatherer.println("Index = " + index_dir.getAbsolutePath());
1150 }
1151
1152 File build_dir = new File(getCollectionBuild(), "temp.txt");
1153 build_dir = build_dir.getParentFile();
1154 Gatherer.println("Build = " + build_dir.getAbsolutePath());
1155
1156 // Get the build mode from the build options
1157 String build_mode = collection.build_options.getBuildValue("mode");
1158
1159 // Special case for build mode "all": replace index dir with building dir
1160 if (build_mode == null || build_mode.equals(Dictionary.get("CreatePane.Mode_All"))) {
1161 // Remove the old index directory
1162 if (index_dir.exists()) {
1163 Utility.delete(index_dir);
1164 // Check the delete worked
1165 if (index_dir.exists()) {
1166 throw new Exception("Index directory cannot be removed.");
1167 }
1168 }
1169
1170 // Move the building directory to become the new index directory
1171 if (build_dir.renameTo(index_dir) == false) {
1172 throw new Exception("Build directory cannot be moved.");
1173 }
1174
1175 // Create a new building directory
1176 File new_build = new File(getCollectionBuild(), "temp.txt");
1177 new_build = new_build.getParentFile();
1178 new_build.mkdir();
1179 }
1180
1181 // Otherwise copy everything in the building dir into the index dir
1182 else {
1183 File[] build_files = build_dir.listFiles();
1184 for (int i = 0; i < build_files.length; i++) {
1185 File build_file = build_files[i];
1186 File index_equivalent = new File(index_dir, build_file.getName());
1187
1188 // Remove the old file in the index directory (if it exists)
1189 if (index_equivalent.exists()) {
1190 Utility.delete(index_equivalent);
1191 // Check the delete worked
1192 if (index_equivalent.exists()) {
1193 throw new Exception("Index file " + index_equivalent + " cannot be removed.");
1194 }
1195 }
1196
1197 // Move the file into the index directory
1198 if (build_file.renameTo(index_equivalent) == false) {
1199 throw new Exception("File " + build_file + " cannot be moved into index directory.");
1200 }
1201 }
1202 }
1203 }
1204 catch (Exception exception) {
1205 JOptionPane.showMessageDialog(Gatherer.g_man, "Exception detected during collection install.\nMost likely caused by Windows or Local Library holding locks on files:\n" + exception.getMessage(), "Error", JOptionPane.ERROR_MESSAGE);
1206 }
1207 }
1208
1209 private boolean searchArchivesForMetadata(File archive_directory) {
1210 /** @todo - ensure GreenstoneArchiveParser works as expected. */
1211 return true;
1212 }
1213
1214 private boolean searchForMetadata(File current_file) {
1215 boolean cancelled = false;
1216 if(current_file.isFile() && current_file.getName().equals(Utility.METADATA_XML)) {
1217 cancelled = collection.msm.searchForMetadata(null, new FileNode(current_file), false, true); // A dummy run only.
1218 }
1219 else {
1220 File[] children_files = current_file.listFiles();
1221 for(int i = 0; !cancelled && children_files != null && i < children_files.length; i++) {
1222 cancelled = searchForMetadata(children_files[i]);
1223 }
1224 }
1225 return cancelled;
1226 }
1227
1228 private void updateCollectionCFG(File base_cfg, File new_cfg, String description, String email, String title) {
1229 boolean first_name = true;
1230 boolean first_extra = true;
1231 String collection_path = (base_cfg.getParentFile().getParentFile()).getAbsolutePath();
1232
1233 HashMap mappings = collection.msm.profiler.getActions(collection_path);
1234 if(mappings == null) {
1235 Gatherer.println("Mappings is null, which is odd. Leaving all configuration commands which use metadata as they are.");
1236 }
1237
1238 // Now read in base_cfg line by line, parsing important onces and/or replacing them with information pertinent to our collection. Each line is then written back out to the new collect.cfg file.
1239 try {
1240 BufferedReader in = new BufferedReader(new FileReader(base_cfg));
1241 BufferedWriter out = new BufferedWriter(new FileWriter(new_cfg, false)); // Overwrite whats there.
1242 String command = null;
1243 while((command = in.readLine()) != null) {
1244 // We have to test the end of command for the special character '\'. If found, remove it and append the next line, then repeat.
1245 while(command.trim().endsWith("\\")) {
1246 command = command.substring(0, command.lastIndexOf("\\"));
1247 String next_line = in.readLine();
1248 if(next_line != null) {
1249 command = command + next_line;
1250 }
1251 }
1252 ///ystem.err.println("Read: " + command);
1253 // Now we've finished parsing a line, determine what to do with it.
1254 String command_lc = command.toLowerCase();
1255 // We replace the creator string with our own.
1256 if(command_lc.startsWith(Utility.CFG_CREATOR)) {
1257 ///ystem.err.println("Found creator - replace with current");
1258 write(out, Utility.CFG_CREATOR + " " + email);
1259 }
1260 else if(command_lc.startsWith(Utility.CFG_MAINTAINER)) {
1261 ///ystem.err.println("Found maintainer - replace with current");
1262 write(out, Utility.CFG_MAINTAINER + " " + email);
1263 }
1264 else if(command_lc.startsWith(Utility.CFG_COLLECTIONMETA_COLLECTIONNAME)) {
1265 if(first_name) {
1266 write(out, Utility.CFG_COLLECTIONMETA_COLLECTIONNAME + " \"" + title + "\"");
1267 first_name = false;
1268 }
1269 }
1270 else if(command_lc.startsWith(Utility.CFG_COLLECTIONMETA_COLLECTIONEXTRA)) {
1271 if(first_extra) {
1272 write(out, Utility.CFG_COLLECTIONMETA_COLLECTIONEXTRA + " \"" + description + "\"");
1273 first_extra = false;
1274 }
1275 }
1276 else if(command_lc.startsWith(Utility.CFG_COLLECTIONMETA_ICONCOLLECTION)) {
1277 write(out, Utility.CFG_COLLECTIONMETA_ICONCOLLECTION + " \"\"");
1278 }
1279
1280 // Just before we try more general parsing there are the special cases to check. These are explicit changes required by some collections to produce sensible results.
1281 else if(special_case == SPECIAL_DLS && command_lc.equals("classify hierarchy -hfile azlist.txt -metadata azlist -sort title -buttonname title -hlist_at_top")) {
1282 write(out, "classify AZList -metadata dls.Title -buttonname Title");
1283 }
1284 else if(command_lc.startsWith(Utility.CFG_CLASSIFY)) {
1285 StringTokenizer tokenizer = new StringTokenizer(command);
1286 StringBuffer text = new StringBuffer(tokenizer.nextToken());
1287 // Read in the classifier command watching for hfile, metadata and sort arguments.
1288 String buttonname = null;
1289 String hfile = null;
1290 String new_metadata = null;
1291 String old_metadata = null;
1292 while(tokenizer.hasMoreTokens()) {
1293 String token = tokenizer.nextToken();
1294 if(token.equals(Utility.CFG_CLASSIFY_HFILE)) {
1295 if(tokenizer.hasMoreTokens()) {
1296 text.append(" ");
1297 text.append(token);
1298 token = tokenizer.nextToken();
1299 hfile = token;
1300 }
1301 }
1302 else if(token.equals(Utility.CFG_CLASSIFY_METADATA)) {
1303 if(tokenizer.hasMoreTokens()) {
1304 text.append(" ");
1305 text.append(token);
1306 String temp_metadata = tokenizer.nextToken();
1307 String replacement = null;
1308 if(mappings != null) {
1309 replacement = (String) mappings.get(temp_metadata);
1310 }
1311 if(replacement != null) {
1312 token = replacement;
1313 old_metadata = temp_metadata;
1314 new_metadata = replacement;
1315 }
1316 else {
1317 token = temp_metadata;
1318 }
1319 temp_metadata = null;
1320 replacement = null;
1321 }
1322 }
1323 else if(token.equals(Utility.CFG_CLASSIFY_SORT)) {
1324 if(tokenizer.hasMoreTokens()) {
1325 text.append(" ");
1326 text.append(token);
1327 String temp_metadata = tokenizer.nextToken();
1328 String replacement = null;
1329 if(mappings != null) {
1330 replacement = (String) mappings.get(temp_metadata);
1331 }
1332 if(replacement != null) {
1333 token = replacement;
1334 }
1335 else {
1336 token = temp_metadata;
1337 }
1338 temp_metadata = null;
1339 replacement = null;
1340 }
1341 }
1342 else if(token.equals(Utility.CFG_CLASSIFY_BUTTONNAME)) {
1343 buttonname = token;
1344 }
1345 text.append(' ');
1346 text.append(token);
1347 token = null;
1348 }
1349 tokenizer = null;
1350
1351 // If we replaced the metadata argument and didn't encounter a buttonname, then add one now pointing back to the old metadata name in order to accomodate macro files which required such names (buttonname is metadata name by default)!
1352 if(old_metadata != null && new_metadata != null && buttonname == null) {
1353 text.append(' ');
1354 text.append(Utility.CFG_CLASSIFY_BUTTONNAME);
1355 text.append(' ');
1356 text.append(old_metadata);
1357 }
1358 command = text.toString();
1359 // Replace the hfile if we found it
1360 if(hfile != null && new_metadata != null) {
1361 command = command.replaceAll(hfile, new_metadata + ".txt");
1362 }
1363 buttonname = null;
1364 hfile = null;
1365 new_metadata = null;
1366 old_metadata = null;
1367 write(out, command);
1368 }
1369 else {
1370 // There is still one special case, that of the format command. In such a command we have to search for [<target>] to ensure we don't change parts of the format which have nothing to do with the metadata elements.
1371 boolean format_command = command_lc.startsWith(Utility.CFG_FORMAT);
1372 // Replace mapping strings
1373 if(mappings != null) {
1374 for(Iterator keys = mappings.keySet().iterator(); keys.hasNext(); ) {
1375 String target = (String) keys.next();
1376 String replacement = (String) mappings.get(target);
1377 if(format_command) {
1378 target = "\\[" + target + "\\]";
1379 replacement = "{Or}{[" + replacement + "]," + target + "}";
1380 }
1381 command = command.replaceAll(target, replacement);
1382 }
1383 }
1384 write(out, command);
1385 }
1386 }
1387 in.close();
1388 in = null;
1389 out.flush();
1390 out.close();
1391 out = null;
1392 }
1393 catch(Exception error) {
1394 Gatherer.printStackTrace(error);
1395 }
1396 // All done, I hope.
1397 }
1398
1399 private void write(BufferedWriter out, String message)
1400 throws Exception {
1401 ///ystem.err.println("Writing: " + message);
1402 out.write(message, 0, message.length());
1403 out.newLine();
1404 }
1405
1406 /** The CollectionManager class is getting too confusing by half so I'll implement this TreeModelListener in a private class to make responsibility clear. */
1407 private class FMTreeModelListener
1408 implements TreeModelListener {
1409 /** Any action that changes one of the tree models within a collection, which are the only models we listen to, mean the collections contents have changed and so saved should be set to false.
1410 * @param event A <strong>TreeModelEvent</strong> encompassing all the information about the event which has changed the tree.
1411 */
1412 public void treeNodesChanged(TreeModelEvent event) {
1413 if(collection != null) {
1414 collection.setSaved(false);
1415 }
1416 }
1417 /** Any action that changes one of the tree models within a collection, which are the only models we listen to, mean the collections contents have changed and so saved should be set to false.
1418 * @param event A <strong>TreeModelEvent</strong> encompassing all the information about the event which has changed the tree.
1419 */
1420 public void treeNodesInserted(TreeModelEvent event) {
1421 if(collection != null) {
1422 collection.setSaved(false);
1423 }
1424 }
1425 /** Any action that changes one of the tree models within a collection, which are the only models we listen to, mean the collections contents have changed and so saved should be set to false.
1426 * @param event A <strong>TreeModelEvent</strong> encompassing all the information about the event which has changed the tree.
1427 */
1428 public void treeNodesRemoved(TreeModelEvent event) {
1429 if(collection != null) {
1430 collection.setSaved(false);
1431 }
1432 }
1433 /** Any action that changes one of the tree models within a collection, which are the only models we listen to, mean the collections contents have changed and so saved should be set to false.
1434 * @param event A <strong>TreeModelEvent</strong> encompassing all the information about the event which has changed the tree.
1435 */
1436 public void treeStructureChanged(TreeModelEvent event) {
1437 if(collection != null) {
1438 collection.setSaved(false);
1439 }
1440 }
1441 }
1442}
Note: See TracBrowser for help on using the repository browser.