/** *######################################################################### * * A component of the Gatherer application, part of the Greenstone digital * library suite from the New Zealand Digital Library Project at the * University of Waikato, New Zealand. * *

* * Author: John Thompson, Greenstone Digital Library, University of Waikato * *

* * Copyright (C) 1999 New Zealand Digital Library Project * *

* * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * *

* * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * *

* * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *######################################################################## */ package org.greenstone.gatherer.collection; import java.io.*; import java.lang.Class; import java.util.*; import javax.swing.*; import javax.swing.event.*; import javax.swing.filechooser.FileSystemView; import javax.swing.tree.*; import org.greenstone.gatherer.Dictionary; import org.greenstone.gatherer.Gatherer; import org.greenstone.gatherer.cdm.CollectionDesignManager; import org.greenstone.gatherer.cdm.CollectionMeta; import org.greenstone.gatherer.cdm.CollectionMetaManager; import org.greenstone.gatherer.cdm.CommandTokenizer; import org.greenstone.gatherer.collection.BasicCollectionConfiguration; import org.greenstone.gatherer.collection.Collection; import org.greenstone.gatherer.collection.SaveCollectionTask; import org.greenstone.gatherer.file.FileNode; import org.greenstone.gatherer.file.FileSystemModel; import org.greenstone.gatherer.gui.LockFileDialog; import org.greenstone.gatherer.gui.NewCollectionMetadataPrompt; import org.greenstone.gatherer.gui.ExternalCollectionPrompt; import org.greenstone.gatherer.gui.NewMetaSetPrompt; import org.greenstone.gatherer.gui.WarningDialog; import org.greenstone.gatherer.gui.tree.WorkspaceTree; import org.greenstone.gatherer.msm.ElementWrapper; import org.greenstone.gatherer.msm.MetadataXMLFileManager; import org.greenstone.gatherer.msm.GreenstoneArchiveParser; import org.greenstone.gatherer.msm.LegacyCollectionImporter; import org.greenstone.gatherer.msm.MetadataSet; import org.greenstone.gatherer.msm.MetadataSetManager; import org.greenstone.gatherer.msm.MSMEvent; import org.greenstone.gatherer.msm.MSMListener; import org.greenstone.gatherer.msm.MSMProfiler; import org.greenstone.gatherer.msm.MSMUtils; import org.greenstone.gatherer.shell.GShell; import org.greenstone.gatherer.shell.GShellEvent; import org.greenstone.gatherer.shell.GShellListener; import org.greenstone.gatherer.shell.GShellProgressMonitor; import org.greenstone.gatherer.undo.UndoManager; import org.greenstone.gatherer.util.ArrayTools; import org.greenstone.gatherer.util.Codec; import org.greenstone.gatherer.util.GSDLSiteConfig; import org.greenstone.gatherer.util.StaticStrings; import org.greenstone.gatherer.util.SynchronizedTreeModelTools; import org.greenstone.gatherer.util.Utility; import org.w3c.dom.*; /** 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. * @author John Thompson * @version 2.3 */ public class CollectionManager implements GShellListener, MSMListener { /** A reference to the metadata set manager. */ public MetadataSetManager msm; /** A reference to the undo manager. Although only one instance is shared between all collections, the undo queues are emptied between each. */ public UndoManager undo; /** Are we currently in the process of building? */ private boolean building = false; /** Are we currently in the process of importing? */ private boolean importing = false; /** The collection this manager is managing! */ static private Collection collection = null; /** The collection_model. */ private FileSystemModel collection_model = null; /** The workspace model. This becomes invalid on a collection change. */ // private FileSystemModel workspace_model = null; /** An inner class listener responsible for noting tree changes and resetting saved when they occur. */ private FMTreeModelListener fm_tree_model_listener = null; /** The monitor resposible for parsing the build process. */ private GShellProgressMonitor build_monitor = null; /** The monitor resposible for parsing the import process. */ private GShellProgressMonitor import_monitor = null; /** 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. */ private Thread closing_thread = null; /** The name of the standard lock file. */ static final public String LOCK_FILE = "gli.lck"; /** Used to indicate the source of the message is the file collection methods. */ static final public int COLLECT = 3; /** Used to indicate the source of the message is the building methods. */ static final public int BUILDING = 5; /** Constructor. */ public CollectionManager() { // Initialisation. this.building = false; this.importing = false; this.collection = null; this.undo = new UndoManager(); } /** Add a special directory mapping. * @param name The name for this directory mapping as a String. * @param file The directory this mapping maps to as a File. */ public void addDirectoryMapping(String name, File file) { // Update the information stored in the Gatherer config Gatherer.config.addDirectoryMapping(name, file); // Now update the tree Gatherer.g_man.gather_pane.refreshWorkspaceTree(WorkspaceTree.MAPPED_DIRECTORIES_CHANGED); } /** This method calls the builcol.pl scripts via a GShell so as to not lock up the processor. * @see org.greenstone.gatherer.Configuration * @see org.greenstone.gatherer.Gatherer * @see org.greenstone.gatherer.collection.Collection * @see org.greenstone.gatherer.gui.BuildOptions * @see org.greenstone.gatherer.shell.GShell * @see org.greenstone.gatherer.shell.GShellListener * @see org.greenstone.gatherer.shell.GShellProgressMonitor * @see org.greenstone.gatherer.util.Utility */ public void buildCollection() { Gatherer.println("CollectionManager.buildCollection()"); building = true; String lang = Gatherer.config.getLanguage(); String args[]; if(Utility.isWindows()) { args = new String[7]; args[0] = Gatherer.config.perl_path; args[1] = "-S"; args[2] = Gatherer.config.getScriptPath() + "buildcol.pl"; args[3] = "-gli"; args[4] = "-language"; args[5] = lang; args[6] = collection.getName(); } else { args = new String[5]; args[0] = Gatherer.config.getScriptPath() + "buildcol.pl"; args[1] = "-gli"; args[2] = "-language"; args[3] = lang; args[4] = collection.getName(); } args = ArrayTools.add(args, collection.build_options.getBuildValues()); GShell shell = new GShell(args, GShell.BUILD, BUILDING, this, build_monitor, GShell.GSHELL_BUILD); shell.addGShellListener(Gatherer.g_man.create_pane); shell.start(); Gatherer.println("CollectionManager.buildCollection().return"); } /** Used to determine whether the currently active collection has been built. * @return A boolean indicating the built status of the collection. */ public boolean built() { if(collection != null) { // Determine if the collection has been built by looking for the build.cfg file File build_cfg_file = new File(getCollectionIndex() + Utility.BUILD_CFG_FILENAME); return build_cfg_file.exists(); } return false; } /** a test method to see if we can delete a directory/file - returns false is the file or any of the contents of a directory cannot be deleted */ public boolean canDelete(File file) { if (!file.isDirectory()) { return file.canWrite(); } File [] file_list = file.listFiles(); for (int i=0; iMSMEvent containing details of the event that caused this message to be fired. * @see org.greenstone.gatherer.collection.Collection */ public void elementChanged(MSMEvent event) { // This means the state of the collections has changed, so we should set saved to false. collection.setSaved(false); } /** Used to retrieve the build options associated with the currently loaded collection. If none yet exist, default ones are created. * @return A BuildOptions object containing the build options for the current collection. * @see org.greenstone.gatherer.collection.Collection */ /* private BuildOptions getBuildOptions() { return collection.build_options; } */ /** Retrieve the current collection. * @return The Collection itself. */ public Collection getCollection() { return collection; } /** Constructs the absolute filename of the collection archive directory, which should resemble "$GSDLHOME/collect/<col_name>/archive/" * @return A String containing the filename. * @see org.greenstone.gatherer.Configuration * @see org.greenstone.gatherer.Gatherer * @see org.greenstone.gatherer.collection.Collection * @see org.greenstone.gatherer.util.Utility */ public String getCollectionArchive() { return Utility.getArchiveDir(Utility.getCollectionDir(Gatherer.config.gsdl_path, collection.getName())); } /** Constructs the absolute filename of the collection building directory, which should resemble "$GSDLHOME/collect/<col_name>/building/" * @return A String containing the filename. * @see org.greenstone.gatherer.Configuration * @see org.greenstone.gatherer.Gatherer * @see org.greenstone.gatherer.collection.Collection * @see org.greenstone.gatherer.util.Utility */ public String getCollectionBuild() { return Utility.getBuildDir(Utility.getCollectionDir(Gatherer.config.gsdl_path, collection.getName())); } /** Constructs the absolute filename of the collection config file, which should resemble "$GSDLHOME/collect/<col_name>/etc/collect.cfg" * @return A String containing the filename. * @see org.greenstone.gatherer.Configuration * @see org.greenstone.gatherer.Gatherer * @see org.greenstone.gatherer.collection.Collection * @see org.greenstone.gatherer.util.Utility */ public String getCollectionConfig() { return Utility.getConfigDir(Utility.getCollectionDir(Gatherer.config.gsdl_path, collection.getName())); } /** Constructs the absolute filename of the collection directory, which should resemble "$GSDLHOME/collect/<col_name>" * @return A String containing the directory name. * @see org.greenstone.gatherer.Configuration * @see org.greenstone.gatherer.Gatherer * @see org.greenstone.gatherer.collection.Collection * @see org.greenstone.gatherer.util.Utility */ public String getCollectionDirectory() { return Utility.getCollectionDir(Gatherer.config.gsdl_path, collection.getName()); } /** Constructs the absolute filename of the collection etc directory, which should resemble "$GSDLHOME/collect/<col_name>/etc/" * @return A String containing the filename. * @see org.greenstone.gatherer.Configuration * @see org.greenstone.gatherer.Gatherer * @see org.greenstone.gatherer.collection.Collection * @see org.greenstone.gatherer.util.Utility */ public String getCollectionEtc() { return Utility.getEtcDir(Utility.getCollectionDir(Gatherer.config.gsdl_path, collection.getName())); } /** Constructs the absolute filename of the collection file, which should resemble "$GSDLHOME/collect/<col_name>/<col_name>.col" * @return A String containing the filename. * @see org.greenstone.gatherer.Configuration * @see org.greenstone.gatherer.Gatherer * @see org.greenstone.gatherer.collection.Collection * @see org.greenstone.gatherer.util.Utility */ public String getCollectionFilename() { return Utility.getCollectionDir(Gatherer.config.gsdl_path, collection.getName()) + collection.getName() + ".col"; } /** Constructs the absolute filename of the collection images directory, which should resemble "$GSDLHOME/collect/<col_name>/images/" * @return A String containing the filename. * @see org.greenstone.gatherer.Configuration * @see org.greenstone.gatherer.Gatherer * @see org.greenstone.gatherer.collection.Collection * @see org.greenstone.gatherer.util.Utility */ public String getCollectionImages() { return Utility.getImagesDir(Utility.getCollectionDir(Gatherer.config.gsdl_path, collection.getName())); } /** Constructs the absolute filename of the collection import directory, which should resemble "$GSDLHOME/collect/<col_name>/import/" * @return A String containing the filename. * @see org.greenstone.gatherer.Configuration * @see org.greenstone.gatherer.Gatherer * @see org.greenstone.gatherer.collection.Collection * @see org.greenstone.gatherer.util.Utility */ public String getCollectionImport() { return Utility.getImportDir(Utility.getCollectionDir(Gatherer.config.gsdl_path, collection.getName())); } /** Constructs the absolute filename of the collection index directory, which should resemble "$GSDLHOME/collect/<col_name>/index/" * @return A String containing the filename. * @see org.greenstone.gatherer.Configuration * @see org.greenstone.gatherer.Gatherer * @see org.greenstone.gatherer.collection.Collection * @see org.greenstone.gatherer.util.Utility */ public String getCollectionIndex() { return Utility.getIndexDir(Utility.getCollectionDir(Gatherer.config.gsdl_path, collection.getName())); } /** Constructs the absolute filename of the collection log directory, which should resemble "$GSDLHOME/collect/<col_name>/log/" * @return A String containing the filename. * @see org.greenstone.gatherer.Configuration * @see org.greenstone.gatherer.Gatherer * @see org.greenstone.gatherer.collection.Collection * @see org.greenstone.gatherer.util.Utility */ public String getCollectionLog() { return Utility.getLogDir(Utility.getCollectionDir(Gatherer.config.gsdl_path, collection.getName())); } /** Constructs the absolute filename of the collection metadata directory, which should resemble "$GSDLHOME/collect/<col_name>/metadata/" * @return A String containing the filename. * @see org.greenstone.gatherer.Configuration * @see org.greenstone.gatherer.Gatherer * @see org.greenstone.gatherer.collection.Collection * @see org.greenstone.gatherer.util.Utility */ public String getCollectionMetadata() { return Utility.getMetadataDir(Utility.getCollectionDir(Gatherer.config.gsdl_path, collection.getName())); } /** This method either returns the title of the current collection, or a placeholder string of 'No Collection'. * @return A String which represents what we wish to display for a collection title. * @see org.greenstone.gatherer.collection.Collection */ /* private String getCollectionTitle() { if(collection != null) { return collection.getTitle(); } return Dictionary.get("Collection.No_Collection"); } */ /** Retrieve the record set (tree model) associated with the current collection. */ public TreeModel getRecordSet() { if(collection_model == null && collection != null) { // Use the import directory to generate a new FileSystemModel collection_model = new FileSystemModel(new FileNode(new File(getCollectionImport()), false)); // Ensure that the manager is a change listener for the tree. if(fm_tree_model_listener == null) { fm_tree_model_listener = new FMTreeModelListener(); } collection_model.addTreeModelListener(fm_tree_model_listener); } return collection_model; } static public FileNode getGreenstoneCollectionsMapping() { FileNode greenstone_collections_node = new FileNode(Dictionary.get("Tree.World")); greenstone_collections_node.unmap(); return greenstone_collections_node; } static public FileNode[] getCollectionSpecificMappings() { // Return any predefined special directories HashMap mappings = Gatherer.config.getDirectoryMappings(); FileNode[] mapping_nodes = new FileNode[mappings.size()]; Iterator mappings_iterator = mappings.keySet().iterator(); for (int i = 0; mappings_iterator.hasNext(); i++) { String mapping_name = (String) mappings_iterator.next(); File mapping_file = (File) mappings.get(mapping_name); mapping_nodes[i] = new FileNode(mapping_file, mapping_name); } return mapping_nodes; } /** This method when called, creates a new GShell in order to run the import.pl script. * @see org.greenstone.gatherer.Configuration * @see org.greenstone.gatherer.Gatherer * @see org.greenstone.gatherer.gui.BuildOptions * @see org.greenstone.gatherer.shell.GShell * @see org.greenstone.gatherer.shell.GShellListener * @see org.greenstone.gatherer.shell.GShellProgressMonitor * @see org.greenstone.gatherer.util.Utility */ public void importCollection() { importing = true; if(!saved()) { Gatherer.println("CollectionManager.importCollection().forcesave"); import_monitor.saving(); // Force save. try { SaveCollectionTask save_task = new SaveCollectionTask(collection); save_task.setImportAfter(true); save_task.start(); } catch(Exception error) { Gatherer.printStackTrace(error); } } else { Gatherer.println("CollectionManager.importCollection()"); //check that we can remove the old index before starting import File index_dir = new File(getCollectionIndex(), "temp.txt"); index_dir = index_dir.getParentFile(); if(index_dir.exists()) { Gatherer.println("Old Index = " + index_dir.getAbsolutePath()+", testing for deletability"); if (!canDelete(index_dir)) { // tell the user JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("CollectionManager.Cannot_Delete_Index"), Dictionary.get("General.Error"), JOptionPane.ERROR_MESSAGE); // tell the gui manager // a message for the building log GShellEvent event = new GShellEvent(this, 0, GShell.IMPORT, Dictionary.get("CollectionManager.Cannot_Delete_Index_Log"), GShell.ERROR); Gatherer.g_man.create_pane.message(event); event = new GShellEvent(this, 0, GShell.IMPORT, "", GShell.ERROR); Gatherer.g_man.create_pane.processComplete(event); importing = false; return; } } // Remove erroneous file windows file separator as it causes problems when running import.pl String collection_import = getCollectionImport(); if(collection_import.length() > 2 && collection_import.endsWith("\\")) { collection_import = collection_import.substring(0, collection_import.length() - 1); } String args[]; String lang = Gatherer.config.getLanguage(); if(Utility.isWindows()) { args = new String[9]; args[0] = Gatherer.config.perl_path; args[1] = "-S"; args[2] = Gatherer.config.getScriptPath() + "import.pl"; args[3] = "-gli"; args[4] = "-language"; args[5] = lang; args[6] = "-importdir"; args[7] = collection_import; args[8] = collection.getName(); } else { args = new String[7]; args[0] = Gatherer.config.getScriptPath() + "import.pl"; args[1] = "-gli"; args[2] = "-language"; args[3] = lang; args[4] = "-importdir"; args[5] = collection_import; args[6] = collection.getName(); } collection_import = null; args = ArrayTools.add(args, collection.build_options.getImportValues()); GShell shell = new GShell(args, GShell.IMPORT, BUILDING, this, import_monitor, GShell.GSHELL_IMPORT); shell.addGShellListener(Gatherer.g_man.create_pane); shell.start(); Gatherer.println("CollectionManager.importCollection().return"); } importing = false; } /** Determine if we are currently in the middle of importing (and thus, in this case, we can't allow the log writer to exit). Boy was this a mission to track down. The cascade of crap rolls out something like this: Joe Schmo clicks 'Build Collection', which calls the importCollection() method above, which in turn saves the collection with a saveTask, which fires a collectionChanged message once its finished, which drives the list of logs shown on the create pane to update, which fires a itemChanged() event to the OptionsPane who dutifully tells the current log writer thread to finish up writing (all zero lines its been asked to write) and then die. Wereapon Joe Schmo gets a pretty log to look at, but it isn't actually being written to file so the next time he tries to view it faeces hits the air motion cooling device. Joy. * @return true if the gli is currently importing */ public boolean isImporting() { return importing; } /** Attempts to load the given collection. Currently uses simple serialization of the collection class. * @param location The path to the collection as a String. * @see org.greenstone.gatherer.Configuration * @see org.greenstone.gatherer.Gatherer * @see org.greenstone.gatherer.collection.Collection * @see org.greenstone.gatherer.msm.MetadataSetManager * @see org.greenstone.gatherer.msm.MSMListener * @see org.greenstone.gatherer.util.Utility */ public boolean loadCollection(String location) { Gatherer.println("Load Collection '" + location + "'"); String[] args2 = new String[1]; args2[0] = location; boolean result = false; boolean non_gatherer_collection = false; // Check we have actually been given a .col file. if(!location.endsWith(".col")) { JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("CollectionManager.Not_Col_File", args2), Dictionary.get("General.Error"), JOptionPane.ERROR_MESSAGE); Gatherer.println("CollectionManager.loadCollection: Haven't been given a .col file."); return false; } // Check that there is the collection configuration file available File collection_file = new File(location); // Ensure that the directory exists. File collection_directory = collection_file.getParentFile(); if(!collection_directory.exists()) { // we cant open this collection_directory = null; return false; } // Special case of a user trying to open an old greenstone collection. File metadata_directory = new File(collection_directory, Utility.META_DIR); if(!metadata_directory.exists()) { Gatherer.println("CollectionManager.loadCollection: trying to load up a non-gatherer collection"); non_gatherer_collection = true; } String name = collection_directory.getName(); File lock_file = new File(collection_file.getParentFile(), LOCK_FILE); // Now determine if a lock already exists on this collection. int choice = LockFileDialog.YES_OPTION; if(lock_file.exists()) { LockFileDialog dialog = new LockFileDialog(Gatherer.g_man, name, lock_file); choice = dialog.getChoice(); dialog.dispose(); dialog = null; } if(choice != LockFileDialog.YES_OPTION) { // user has cancelled lock_file = null; collection_directory = null; return false; } try { if(lock_file.exists()) { lock_file.delete(); } // Create a lock file. createLockFile(lock_file); // This lock file may not have been created so check if(!lock_file.canWrite()) { // The lock file cannot be written to. Most likely cause incorrect file permissions. System.err.println("Cannot write lock file!"); String args[] = new String[2]; args[0] = args2[0]; args[1] = Dictionary.get("FileActions.Write_Not_Permitted_Title"); JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("CollectionManager.Cannot_Open_With_Reason", args), Dictionary.get("General.Error"), JOptionPane.ERROR_MESSAGE); args = null; return false; } // Open the collection file collection = new Collection(collection_file); if(collection.getTitle().equals(StaticStrings.ERROR_STR)) { collection = null; // Remove lock file if(lock_file.exists()) { lock_file.delete(); } throw(new Exception(Dictionary.get("CollectionManager.Missing_Config"))); } collection.msm = new MetadataSetManager(); msm = collection.msm; // Legacy collection.msm.load(); // if non-gatherer collection, need to add some metadata sets if (non_gatherer_collection) { if (!addSomeMetadataSets(collection_directory)) { // for now - return of false means its been cancelled. Any error messages should be sent from the function itself lock_file = null; collection_directory = null; closeCollection(); return false; } } collection.cdm = new CollectionDesignManager(new File(collection_file.getParent(), Utility.CONFIG_DIR)); if (non_gatherer_collection) { // We first recurse the Import folder tree, reading in any metadata.xml files, and then altering the non-namespaced element names to be valid GLI names LegacyCollectionImporter lci = new LegacyCollectionImporter(collection_directory, collection.cdm); lci.backupMetadataXMLFiles(collection_directory); lci.importMetadata(); lci.updateClassifiers(); lci = null; } // Whether the collection is legacy or not, we should now be able to prepare the MetadataXMLFileManager collection.gdm = new MetadataXMLFileManager(); // Tell everyone that it worked. String[] args = new String[1]; args[0] = name; Gatherer.println(Dictionary.get("CollectionManager.Loading_Successful", args)); // Now we need to hook up classes that depend on messages from the metadata set manager to keep their content fresh. collection.msm.addMSMListener(this); // We're done. Let everyone know. if(Gatherer.g_man != null) { // workspace_model = null; Gatherer.g_man.collectionChanged(ready()); } result = true; ///ystem.err.println("Done loadCollection()."); } catch (Exception error) { System.err.println("Exception occurred!"); error.printStackTrace(); // There is obviously no existing collection present. Gatherer.printStackTrace(error); if(error.getMessage() != null) { String[] args = new String[2]; args[0] = args2[0]; args[1] = error.getMessage(); JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("CollectionManager.Cannot_Open_With_Reason", args), Dictionary.get("General.Error"), JOptionPane.ERROR_MESSAGE); } else { JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("CollectionManager.Cannot_Open", args2), Dictionary.get("General.Error"), JOptionPane.ERROR_MESSAGE); } } lock_file = null; collection_directory = null; args2 = null; return result; } /** I started giving the user the choice of using an existing meta set or creating a new one. The second option being so that they didn't have to add/merge/ignore each element, they could all be added automatically. However, I am not sure where the merge prompt gets called from, and it is not essential, so I am leaving it for now - it shoudl be added back in and finished. [kjdon] */ private boolean addSomeMetadataSets(File collection_dir) { ExternalCollectionPrompt external_prompt = new ExternalCollectionPrompt(); int meta_choice = external_prompt.getMetadataChoice(); boolean cancelled = external_prompt.isCancelled(); if (cancelled) { return false; } /*if (meta_choice == ExternalCollectionPrompt.NEW_META_SET) { NewMetaSetPrompt nmsp = new NewMetaSetPrompt(); if (nmsp.isCancelled()) { return false; } String namespace_str = nmsp.getNamespace(); String name_str = nmsp.getName(); MetadataSet set = Gatherer.c_man.msm.addSet(namespace_str, name_str); } else if (meta_choice == ExternalCollectionPrompt.EXISTING_META_SET) { */ // now we reuse the newcoll metadata prompt for the user to select metadata sets NewCollectionMetadataPrompt ncm_prompt = new NewCollectionMetadataPrompt(true); if (ncm_prompt.isCancelled()) { return false; } ArrayList metadata_sets = ncm_prompt.getSets(); // Import default metadata sets if any. for(int i = 0; metadata_sets != null && i < metadata_sets.size(); i++) { MetadataSet metadata_set = (MetadataSet) metadata_sets.get(i); collection.msm.importMDS(metadata_set.getFile(), false); } /*} else { return false; }*/ // Always import the extracted metadata set collection.msm.importMDS(new File(Utility.METADATA_DIR + Utility.EXTRACTED_METADATA_NAMESPACE + StaticStrings.METADATA_SET_EXTENSION), false); return true; } // // now try to load in existing metadata, delete the existing metadata.xml files, then save the new ones. // private boolean findAndLoadExistingMetadata(File collection_dir) // { // TreeModel collection_tree = getRecordSet(); // FileNode root_node = (FileNode) collection_tree.getRoot(); // if (searchForMetadata(collection_tree, root_node) == false) { // return false; // } // collection.gdm.save(root_node); // return true; // } private boolean searchForMetadata(TreeModel collection_tree, FileNode current_node) { File source_file = current_node.getFile(); String source_file_name = source_file.getName(); if (source_file_name.equals(Utility.METADATA_XML) || source_file_name.equals("CVS")) { return true; } if (collection.msm.searchForMetadata(current_node, current_node, false)== false) { return false; } int num_children = collection_tree.getChildCount(current_node); for (int i=0; iGShellEvent which contains a the message. */ public synchronized void message(GShellEvent event) { } /** 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. * @param event A MSMEvent containing details of the event that caused this message to be fired. * @see org.greenstone.gatherer.collection.Collection */ public void metadataChanged(MSMEvent event) { // Again this change means we need to save the collection again. collection.setSaved(false); } /** This call is fired whenever a process within a GShell created by this class begins. * @param event A GShellEvent containing information about the GShell process. * @see org.greenstone.gatherer.Gatherer * @see org.greenstone.gatherer.gui.GUIManager * @see org.greenstone.gatherer.shell.GShell */ public synchronized void processBegun(GShellEvent event) { Gatherer.println("CollectionManager.processBegun(" + event.getType() + ")"); ///ystem.err.println("ProcessBegun " + event.getType()); // If this is one of the types where we wish to lock user control Gatherer.g_man.lockCollection((event.getType() == GShell.IMPORT), true); } /** This call is fired whenever a process within a GShell created by this class ends. * @param event A GShellEvent containing information about the GShell process. * @see org.greenstone.gatherer.Gatherer * @see org.greenstone.gatherer.gui.GUIManager * @see org.greenstone.gatherer.shell.GShell */ public synchronized void processComplete(GShellEvent event) { Gatherer.println("CollectionManager.processComplete(" + event.getType() + ")"); ///ystem.err.println("ProcessComplete " + event.getType()); Gatherer.g_man.lockCollection((event.getType() == GShell.IMPORT), false); ///ystem.err.println("Recieved process complete event - " + event); // If we were running an import, now run a build. if(event.getType() == GShell.IMPORT && event.getStatus() == GShell.OK) { // Finish import. collection.setImported(true); buildCollection(); } // If we were running a build, now is when we move files across. else if(event.getType() == GShell.BUILD && event.getStatus() == GShell.OK) { if(installCollection()) { // If we have a local library running (that we know about) then we ask it to add our newly create collection if(Gatherer.config.exec_file != null) { Gatherer.self.configServer(GSDLSiteConfig.ADD_COMMAND + collection.getName()); } // Fire a collection changed first to update the preview etc buttons Gatherer.g_man.collectionChanged(ready()); // Now display a message dialog saying its all built WarningDialog collection_built_warning_dialog = new WarningDialog("warning.CollectionBuilt", false); collection_built_warning_dialog.setMessageOnly(true); // Not a warning collection_built_warning_dialog.display(); collection_built_warning_dialog.dispose(); collection_built_warning_dialog = null; } else { JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("CollectionManager.Preview_Ready_Failed"), Dictionary.get("CollectionManager.Preview_Ready_Title"), JOptionPane.ERROR_MESSAGE); Gatherer.g_man.collectionChanged(ready()); } } else if(event.getStatus() == GShell.ERROR || event.getStatus() == GShell.CANCELLED) { Gatherer.println("There was an error in the gshell:"+ event.getMessage()); JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("CollectionManager.Preview_Ready_Failed"), Dictionary.get("CollectionManager.Preview_Ready_Title"), JOptionPane.ERROR_MESSAGE); Gatherer.g_man.collectionChanged(ready()); Gatherer.g_man.repaint(); // It appears Java's own dialogs have the same not always painting correct area bug that I suffer from. Well I don't suffer from it personally, but my ModalDialog components do. } } /** Determine if the manager is ready for actions apon its collection. * @return A boolean which is true to indicate a collection has been loaded and thus the collection is ready for editing, false otherwise. */ static public synchronized boolean ready() { if(collection != null) { return true; } else { return false; } } public synchronized boolean reallyReady() { // If the closing thread is non-null we should only allow that thread to see the collection as closed. if(closing_thread != null) { // Only the closing thread sees the truth if(Thread.currentThread() == closing_thread) { return (collection == null); } // All other threads are told a lie. else { return true; } } else { if(collection != null) { return true; } else { return false; } } } /** This method associates the collection build monitor with the build monitor created in CreatePane. * @param monitor A GShellProgressMonitor which we will use as the build monitor. */ public void registerBuildMonitor(GShellProgressMonitor monitor) { build_monitor = monitor; } /** This method associates the collection import monitor with the import monitor created in CreatePane. * @param monitor A GShellProgressMonitor which we will use as the import monitor. */ public void registerImportMonitor(GShellProgressMonitor monitor) { import_monitor = monitor; } /** Remove a previously assigned special directory mapping. * @param target The FileNode representing the special directory mapping to remove as a String. * @return The File of the mapping removed. * @see org.greenstone.gatherer.file.FileNode */ public File removeDirectoryMapping(FileNode target) { // Remove from config, remembering file File file = Gatherer.config.removeDirectoryMapping(target.toString()); // Update tree. Gatherer.g_man.gather_pane.refreshWorkspaceTree(WorkspaceTree.MAPPED_DIRECTORIES_CHANGED); return file; } /** Used to check whether all open collections have a 'saved' state. * @return A boolean which is true if the collection has been saved. * @see org.greenstone.gatherer.collection.Collection */ public boolean saved() { boolean result = true; if(collection != null) { result = collection.getSaved(); } return result; } /** Saves a collection by serializing it to file. * @param close_after true to cause the Gatherer to close the collection once save is complete, false otherwise. * @param exit_after true to cause the Gatherer to exit once save is complete, false otherwise. * @see org.greenstone.gatherer.Gatherer * @see org.greenstone.gatherer.gui.GUIManager * @see org.greenstone.gatherer.collection.Collection */ public void saveCollection(boolean close_after, boolean exit_after) { Gatherer.println("Save collection: " + collection.getName()); try { SaveCollectionTask save_task = new SaveCollectionTask(collection, close_after, exit_after); save_task.start(); // Run this in the same thread //save_task.run(); } catch(Exception error) { Gatherer.printStackTrace(error); } } /** Saves the current collection to a new filename, then restores the original collection. Finally opens the collection copy. * @param name The name collection name. */ public void saveCollectionAs(String name) { // We need to do this in a separate thread so create a SaveCollectionAsTask try { SaveCollectionTask save_task = new SaveCollectionTask(collection, name); save_task.start(); } catch(Exception error) { Gatherer.printStackTrace(error); } } /** 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. * @param event A MSMEvent containing details of the event that caused this message to be fired. * @see org.greenstone.gatherer.collection.Collection */ public void setChanged(MSMEvent event) { // Invalidate saved collection.setSaved(false); } public void setClosingThread(boolean set) { if(set) { closing_thread = Thread.currentThread(); } else { closing_thread = null; } } /** Updates the given workspace tree model to reference the private cache of the currently loaded collection. */ /* private void updatePrivateWorkspace(DefaultTreeModel model) { // Add Private workspace if a collection has been loaded. if(ready() && !Gatherer.config.get("workflow.mirror", true)) { FileNode root = (FileNode)model.getRoot(); // Remove old private workspace FileNode old = (FileNode)model.getChild(root, 2); model.removeNodeFromParent(old); // Create and insert new. FileNode private_workspace = new FileNode(new File(getCollectionCache()), Dictionary.get("Tree.Private")); model.insertNodeInto(private_workspace, root, 2); } } */ /** 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. * @param event A MSMEvent containing details of the event that caused this message to be fired. * @see org.greenstone.gatherer.collection.Collection */ public void valueChanged(MSMEvent event) { collection.setSaved(false); } /** Install collection by moving its files from building to index after a successful build. * @see org.greenstone.gatherer.Gatherer * @see org.greenstone.gatherer.util.Utility */ private boolean installCollection() { Gatherer.println("Build complete. Moving files."); try { // We have to ensure that the local library if(Gatherer.config.exec_file != null) { ///ystem.err.println("Local Library Found!"); //Gatherer.g_man./review_pane.configServer(GSDLSiteConfig.RELEASE_COMMAND + collection.getName()); Gatherer.self.configServer(GSDLSiteConfig.RELEASE_COMMAND + collection.getName()); } File index_dir = new File(getCollectionIndex(), "temp.txt"); index_dir = index_dir.getParentFile(); if(index_dir.exists()) { Gatherer.println("Index = " + index_dir.getAbsolutePath()); } File build_dir = new File(getCollectionBuild(), "temp.txt"); build_dir = build_dir.getParentFile(); Gatherer.println("Build = " + build_dir.getAbsolutePath()); // Get the build mode from the build options String build_mode = collection.build_options.getBuildValue("mode"); // Special case for build mode "all": replace index dir with building dir if (build_mode == null || build_mode.equals(Dictionary.get("CreatePane.Mode_All"))) { // Remove the old index directory if (index_dir.exists()) { Utility.delete(index_dir); // Check the delete worked if (index_dir.exists()) { throw new Exception("Index directory cannot be removed."); } } // Move the building directory to become the new index directory if (build_dir.renameTo(index_dir) == false) { throw new Exception("Build directory cannot be moved."); } // Create a new building directory File new_build = new File(getCollectionBuild(), "temp.txt"); new_build = new_build.getParentFile(); new_build.mkdir(); } // Otherwise copy everything in the building dir into the index dir else { File[] build_files = build_dir.listFiles(); for (int i = 0; i < build_files.length; i++) { File build_file = build_files[i]; File index_equivalent = new File(index_dir, build_file.getName()); // Remove the old file in the index directory (if it exists) if (index_equivalent.exists()) { Utility.delete(index_equivalent); // Check the delete worked if (index_equivalent.exists()) { throw new Exception("Index file " + index_equivalent + " cannot be removed."); } } // Move the file into the index directory if (build_file.renameTo(index_equivalent) == false) { throw new Exception("File " + build_file + " cannot be moved into index directory."); } } } } catch (Exception exception) { 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); return false; } return true; } private boolean searchArchivesForMetadata(File archive_directory) { /** @todo - ensure GreenstoneArchiveParser works as expected. */ return true; } /** now this returns true if successful, false if unsuccessful or cancelled */ private boolean searchForMetadata(File current_file) { boolean success = true; if(current_file.isFile() && current_file.getName().equals(Utility.METADATA_XML)) { success = collection.msm.searchForMetadata(null, new FileNode(current_file), false, true); // A dummy run only. } else { File[] children_files = current_file.listFiles(); for(int i = 0; success && children_files != null && i < children_files.length; i++) { success = searchForMetadata(children_files[i]); } } return success; } private void updateCollectionCFG(File base_cfg, File new_cfg, String description, String email, String title) { boolean first_name = true; boolean first_extra = true; String collection_path = (base_cfg.getParentFile().getParentFile()).getAbsolutePath(); HashMap mappings = collection.msm.profiler.getActions(collection_path); if(mappings == null) { Gatherer.println("Mappings is null, which is odd. Leaving all configuration commands which use metadata as they are."); } // 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. try { BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(base_cfg), "UTF-8")); BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(new_cfg), "UTF-8")); String command = null; while((command = in.readLine()) != null) { if (command.length()==0) { // output a new line out.newLine(); continue; } // We have to test the end of command for the special character '\'. If found, remove it and append the next line, then repeat. while(command.trim().endsWith("\\")) { command = command.substring(0, command.lastIndexOf("\\")); String next_line = in.readLine(); if(next_line != null) { command = command + next_line; } } // commands can extend over more than one line so use the CommandTokenizer which takes care of that CommandTokenizer tokenizer = new CommandTokenizer(command, in, false); String command_type_str = tokenizer.nextToken().toLowerCase(); if (command_type_str.equals(StaticStrings.COLLECTIONMETADATA_STR)) { // read the whole thing in, but for collectionname, collectionextra, iconcollection, iconcollectionsmall we will ignore them StringBuffer new_command = new StringBuffer(command_type_str); String meta_name = tokenizer.nextToken(); new_command.append(' '); new_command.append(meta_name); while (tokenizer.hasMoreTokens()) { new_command.append(' '); new_command.append(tokenizer.nextToken()); } if (meta_name.equals(StaticStrings.COLLECTIONMETADATA_COLLECTIONNAME_STR) || meta_name.equals(StaticStrings.COLLECTIONMETADATA_COLLECTIONEXTRA_STR) || meta_name.equals(StaticStrings.COLLECTIONMETADATA_ICONCOLLECTION_STR) || meta_name.equals(StaticStrings.COLLECTIONMETADATA_ICONCOLLECTIONSMALL_STR)) { // dont save } else { write(out, new_command.toString()); } new_command = null; continue; } // if collectionmeta if(command_type_str.equals(Utility.CFG_CLASSIFY)) { StringBuffer text = new StringBuffer(command_type_str); // Read in the classifier command watching for hfile, metadata and sort arguments. String buttonname = null; String hfile = null; String new_metadata = null; String old_metadata = null; while(tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); if(token.equals(Utility.CFG_CLASSIFY_HFILE)) { if(tokenizer.hasMoreTokens()) { text.append(" "); text.append(token); token = tokenizer.nextToken(); hfile = token; } } else if(token.equals(Utility.CFG_CLASSIFY_METADATA)) { if(tokenizer.hasMoreTokens()) { text.append(" "); text.append(token); String temp_metadata = tokenizer.nextToken(); String replacement = null; if(mappings != null) { replacement = (String) mappings.get(temp_metadata); } if(replacement != null) { token = replacement; old_metadata = temp_metadata; new_metadata = replacement; } else { token = temp_metadata; } temp_metadata = null; replacement = null; } } else if(token.equals(Utility.CFG_CLASSIFY_SORT)) { if(tokenizer.hasMoreTokens()) { text.append(" "); text.append(token); String temp_metadata = tokenizer.nextToken(); String replacement = null; if(mappings != null) { replacement = (String) mappings.get(temp_metadata); } if(replacement != null) { token = replacement; } else { token = temp_metadata; } temp_metadata = null; replacement = null; } } else if(token.equals(Utility.CFG_CLASSIFY_BUTTONNAME)) { buttonname = token; } text.append(' '); text.append(token); token = null; } // 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)! if(old_metadata != null && new_metadata != null && buttonname == null) { text.append(' '); text.append(Utility.CFG_CLASSIFY_BUTTONNAME); text.append(' '); text.append(old_metadata); } command = text.toString(); // Replace the hfile if we found it if(hfile != null && new_metadata != null) { command = command.replaceAll(hfile, new_metadata + ".txt"); } buttonname = null; hfile = null; new_metadata = null; old_metadata = null; write(out, command); } else { // the rest of the commands just want a string - we read in all the tokens from the tokeniser and get rid of it. StringBuffer new_command = new StringBuffer(command_type_str); while (tokenizer.hasMoreTokens()) { new_command.append(' '); new_command.append(tokenizer.nextToken()); } command = new_command.toString(); // There is still one special case, that of the format command. In such a command we have to search for [] to ensure we don't change parts of the format which have nothing to do with the metadata elements. // we really want to build up the whole command here boolean format_command = command_type_str.equals(Utility.CFG_FORMAT); // Replace mapping strings if(mappings != null) { for(Iterator keys = mappings.keySet().iterator(); keys.hasNext(); ) { String target = (String) keys.next(); String replacement = (String) mappings.get(target); if(format_command) { target = "\\[" + target + "\\]"; replacement = "{Or}{[" + replacement + "]," + target + "}"; } command = command.replaceAll(target, replacement); } } write(out, command); } tokenizer = null; } in.close(); in = null; out.flush(); out.close(); out = null; } catch(Exception error) { Gatherer.printStackTrace(error); } // All done, I hope. } private void write(BufferedWriter out, String message) throws Exception { out.write(message, 0, message.length()); out.newLine(); } /** The CollectionManager class is getting too confusing by half so I'll implement this TreeModelListener in a private class to make responsibility clear. */ private class FMTreeModelListener implements TreeModelListener { /** 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. * @param event A TreeModelEvent encompassing all the information about the event which has changed the tree. */ public void treeNodesChanged(TreeModelEvent event) { if(collection != null) { collection.setSaved(false); } } /** 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. * @param event A TreeModelEvent encompassing all the information about the event which has changed the tree. */ public void treeNodesInserted(TreeModelEvent event) { if(collection != null) { collection.setSaved(false); } } /** 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. * @param event A TreeModelEvent encompassing all the information about the event which has changed the tree. */ public void treeNodesRemoved(TreeModelEvent event) { if(collection != null) { collection.setSaved(false); } } /** 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. * @param event A TreeModelEvent encompassing all the information about the event which has changed the tree. */ public void treeStructureChanged(TreeModelEvent event) { if(collection != null) { collection.setSaved(false); } } } }