// Copyright 2004-2014 Jim Voris // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // package com.qumasoft.server; import com.qumasoft.qvcslib.QVCSConstants; import com.qumasoft.qvcslib.TimerManager; import com.qumasoft.qvcslib.Utility; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.TimerTask; import java.util.logging.Level; import java.util.logging.Logger; /** * This class and its associated store class are meant to capture the location of archive files, i.e. you can ask this class to * identify the directory that contains the given fileID at this point in time. If a file moves, then its entry in the store * maintained here gets updated to point to its new home. * * @author Jim Voris */ public final class FileIDDictionary { // This is a singleton. private static final FileIDDictionary FILE_ID_DICTIONARY = new FileIDDictionary(); /** * Wait 2 seconds before saving the latest file id dictionary */ private static final long SAVE_FILEID_DICTIONARY_DELAY = 1000L * 2L; private SaveFileIdDictionaryStoreTimerTask saveFileIdDictionaryStoreTimerTask; // Create our logger object private static final Logger LOGGER = Logger.getLogger("com.qumasoft.server"); private boolean isInitializedFlag; private String storeName; private String oldStoreName; private FileIDDictionaryStore store; /** * Creates a new instance of FileIDDictionary. */ private FileIDDictionary() { } /** * Get the FileIDDictionary instance. * * @return the singleton FileIDDictionary. */ public static FileIDDictionary getInstance() { return FILE_ID_DICTIONARY; } /** * Initialize the dictionary. */ public void initialize() { if (!isInitializedFlag) { oldStoreName = getStoreName() + ".old"; loadStore(); isInitializedFlag = true; } } /** * Reset the dictionary store. */ void resetStore() { File storeFile = new File(getStoreName()); if (storeFile.exists()) { storeFile.delete(); } } /** * Get the name of the store. * * @return the name of the store. */ private String getStoreName() { if (storeName == null) { storeName = System.getProperty("user.dir") + File.separator + QVCSConstants.QVCS_META_DATA_DIRECTORY + File.separator + QVCSConstants.QVCS_FILEID_DICT_STORE_NAME + "dat"; } return storeName; } private synchronized void loadStore() { File storeFile; FileInputStream fileStream = null; try { storeFile = new File(getStoreName()); fileStream = new FileInputStream(storeFile); ObjectInputStream inStream = new ObjectInputStream(fileStream); store = (FileIDDictionaryStore) inStream.readObject(); } catch (FileNotFoundException e) { // The file doesn't exist yet. Create a default store. store = new FileIDDictionaryStore(); writeStore(); } catch (Exception e) { // Serialization failed. Create a default store. store = new FileIDDictionaryStore(); writeStore(); } finally { if (fileStream != null) { try { fileStream.close(); } catch (IOException e) { LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e)); } } } } /** * Write the store to disk. */ public synchronized void writeStore() { FileOutputStream fileStream = null; try { File storeFile = new File(getStoreName()); File oldStoreFile = new File(oldStoreName); if (oldStoreFile.exists()) { oldStoreFile.delete(); } if (storeFile.exists()) { storeFile.renameTo(oldStoreFile); } File newStoreFile = new File(getStoreName()); // Make sure the needed directories exists if (!newStoreFile.getParentFile().exists()) { newStoreFile.getParentFile().mkdirs(); } fileStream = new FileOutputStream(newStoreFile); ObjectOutputStream outStream = new ObjectOutputStream(fileStream); outStream.writeObject(store); } catch (Exception e) { LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e)); } finally { if (fileStream != null) { try { fileStream.close(); } catch (IOException e) { LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e)); } } } } /** * Save the association between a given file (within a project) and its owing directory. * * @param projectName the name of the project. * @param viewName the name of the view. * @param fileID the fileID to look up * @param appendedPath the file's appended path. * @param shortFilename the file's short workfile name. * @param directoryID the directory id of the directory where the file is located. */ public synchronized void saveFileIDInfo(String projectName, String viewName, int fileID, String appendedPath, String shortFilename, int directoryID) { store.saveFileIDInfo(projectName, viewName, fileID, appendedPath, shortFilename, directoryID); scheduleSaveOfFileIdStore(); } /** * Lookup the directory that contains the archive file for the given fileID. * * @param projectName the name of the project. * @param viewName the name of the view. * @param fileID file file's fileID. * @return the fileIDInfo for the given file. */ public synchronized FileIDInfo lookupFileIDInfo(String projectName, String viewName, int fileID) { return store.lookupFileIDInfo(projectName, viewName, fileID); } /** * Remove the file id's for a given project. This should be called when a project is deleted. * * @param projectName the name of the project. * @return true on success (i.e. we found the project, and pruned it from the store), false otherwise. */ public synchronized boolean removeIDsForProject(String projectName) { boolean retVal = store.removeIDsForProject(projectName); if (retVal) { scheduleSaveOfFileIdStore(); } return retVal; } /** * Remove the file id's for the given view for a given project. This should be called when a view is deleted. * * @param projectName the name of the project. * @param viewName the name of the view within that project. * @return true on success (i.e. we found the project and the view, and pruned the view from the store). false otherwise. */ public synchronized boolean removeIDsForView(String projectName, String viewName) { boolean retVal = store.removeIDsForView(projectName, viewName); if (retVal) { scheduleSaveOfFileIdStore(); } return retVal; } /** * Schedule the save of the file id dictionary store. We want to save the file id dictionary store after things are quiet for * the SAVE_FILEID_DICTIONARY_DELAY amount of time so that the file id dictionary will have been preserved in the case of a * crash. */ private void scheduleSaveOfFileIdStore() { if (saveFileIdDictionaryStoreTimerTask != null) { saveFileIdDictionaryStoreTimerTask.cancel(); saveFileIdDictionaryStoreTimerTask = null; } saveFileIdDictionaryStoreTimerTask = new SaveFileIdDictionaryStoreTimerTask(); TimerManager.getInstance().getTimer().schedule(saveFileIdDictionaryStoreTimerTask, SAVE_FILEID_DICTIONARY_DELAY); } /** * Use a timer to write the file id dictionary store after a while so it will have been saved before a crash. */ class SaveFileIdDictionaryStoreTimerTask extends TimerTask { @Override public void run() { LOGGER.log(Level.INFO, "Performing scheduled save of file id dictionary store."); writeStore(); } } }