/* 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.AbstractProjectProperties; import com.qumasoft.qvcslib.ArchiveDirManagerInterface; import com.qumasoft.qvcslib.ArchiveDirManagerReadOnlyViewInterface; import com.qumasoft.qvcslib.ArchiveInfoInterface; import com.qumasoft.qvcslib.DirectoryCoordinate; import com.qumasoft.qvcslib.DirectoryManagerInterface; import com.qumasoft.qvcslib.QVCSConstants; import com.qumasoft.qvcslib.QVCSException; import com.qumasoft.qvcslib.RemoteViewProperties; import com.qumasoft.qvcslib.ServerResponseFactoryInterface; import com.qumasoft.qvcslib.Utility; import com.qumasoft.qvcslib.commandargs.CreateArchiveCommandArgs; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.Map; import java.util.TreeMap; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.event.ChangeListener; /** * Archive directory manager for read-only date based view. * * @author Jim Voris */ public final class ArchiveDirManagerForReadOnlyDateBasedView implements ArchiveDirManagerInterface, ArchiveDirManagerReadOnlyViewInterface { // Create our logger object. private static final Logger LOGGER = Logger.getLogger("com.qumasoft.server"); /** * The oldest revision for this type of view can just be today. We're not going to enforce the license for these kinds of views. */ private static final Date SPOOF_OLDEST_REVISION = new Date(); private final Date anchorDate; private final String viewName; private final String projectName; private final String appendedPath; private final String userName; private final RemoteViewProperties remoteViewProperties; private final Map<String, ArchiveInfoInterface> archiveInfoMap; /** * Remote listeners for changes to this directory. */ private final ArrayList<ServerResponseFactoryInterface> logfileListeners = new ArrayList<>(); /** * Creates a new instance of ArchiveDirManagerForReadOnlyDateBasedView. * * @param anchrDate the anchor date for this date-based view. * @param rvProperties the view's properties. * @param view the name of the view * @param path the appended path for this directory. * @param user the user name * @param response object to identify the client. */ public ArchiveDirManagerForReadOnlyDateBasedView(Date anchrDate, RemoteViewProperties rvProperties, String view, String path, String user, ServerResponseFactoryInterface response) { this.archiveInfoMap = Collections.synchronizedMap(new TreeMap<String, ArchiveInfoInterface>()); this.anchorDate = anchrDate; this.viewName = view; this.appendedPath = path; this.remoteViewProperties = rvProperties; this.userName = user; this.projectName = rvProperties.getProjectName(); populateCollection(response); } /** * This is not used on the server. * @return null, since this is not used on the server. */ @Override public Date getMostRecentActivityDate() { return null; } @Override public void setDirectoryManager(DirectoryManagerInterface directoryManager) { // We don't need to do anything. } @Override public String getAppendedPath() { return appendedPath; } @Override public String getProjectName() { return projectName; } @Override public String getViewName() { return viewName; } @Override public String getUserName() { return userName; } /** * Get the anchor date for this view. * @return the anchor date for this view. */ public Date getAnchorDate() { return anchorDate; } @Override public AbstractProjectProperties getProjectProperties() { return remoteViewProperties; } private RemoteViewProperties getRemoteViewProperties() { return remoteViewProperties; } @Override public ArchiveInfoInterface getArchiveInfo(String shortWorkfileName) { return archiveInfoMap.get(Utility.getArchiveKey(getProjectProperties(), shortWorkfileName)); } @Override public boolean createArchive(CreateArchiveCommandArgs commandLineArgs, String fullWorkfilename, ServerResponseFactoryInterface response) throws IOException, QVCSException { return false; } @Override public void createReferenceCopy(AbstractProjectProperties projectProperties, ArchiveInfoInterface logfile, byte[] buffer) { // We don't need to do anything. } @Override public void deleteReferenceCopy(AbstractProjectProperties projectProperties, ArchiveInfoInterface logfile) { // We don't need to do anything. } @Override public boolean createDirectory() { return false; } @Override public void addChangeListener(ChangeListener listener) { throw new UnsupportedOperationException("Not supported on the server!"); } @Override public void removeChangeListener(ChangeListener listener) { throw new UnsupportedOperationException("Not supported on the server!"); } @Override public void startDirectoryManager() { // We don't need to do anything. } @Override public void notifyListeners() { throw new UnsupportedOperationException("Not supported on the server!"); } @Override public void setFastNotify(boolean flag) { } @Override public boolean getFastNotify() { return false; } @Override public boolean renameArchive(String user, String oldShortWorkfileName, String newShortWorkfileName, ServerResponseFactoryInterface response) throws IOException, QVCSException { return false; } @Override public Map<String, ArchiveInfoInterface> getArchiveInfoCollection() { return archiveInfoMap; } @Override public long getOldestRevision() { return SPOOF_OLDEST_REVISION.getTime(); } @Override public int getDirectoryID() { // This is NOT a real directory!! return -1; } @Override public void addLogFileListener(ServerResponseFactoryInterface logfileListener) { synchronized (logfileListeners) { logfileListeners.add(logfileListener); } } @Override public void removeLogFileListener(ServerResponseFactoryInterface logfileListener) { synchronized (logfileListeners) { logfileListeners.remove(logfileListener); } } private void populateCollection(ServerResponseFactoryInterface response) { // TODO -- I'm not sure that I have to 'navigate' from the root to the requested directory -- it may be as simple as going to the database to find what files // were in the given directory at the given time.... The navigation might be required if the parent branch for this branch is not the trunk. try { String[] segments = Utility.convertToStandardPath(getAppendedPath()).split("/"); // Work our way up from the root of the project until we get to this // directory... DirectoryCoordinate directoryCoordinate = new DirectoryCoordinate(getProjectName(), QVCSConstants.QVCS_TRUNK_VIEW, ""); ArchiveDirManagerInterface projectRootArchiveDirManager = ArchiveDirManagerFactoryForServer.getInstance().getDirectoryManager(QVCSConstants.QVCS_SERVER_SERVER_NAME, directoryCoordinate, QVCSConstants.QVCS_SERVED_PROJECT_TYPE, getUserName(), response, true); int projectRootDirectoryID = projectRootArchiveDirManager.getDirectoryID(); ProjectView projectView = ViewManager.getInstance().getView(getProjectName(), getViewName()); DirectoryContentsManager directoryContentsManager = DirectoryContentsManagerFactory.getInstance().getDirectoryContentsManager(getProjectName()); DirectoryContents projectRootDirectoryContents = directoryContentsManager.getDirectoryContentsForDateBasedView(projectView, "", projectRootDirectoryID, response); // 'Navigate' to the current 'directory' so we can get its contents. int segmentIndex; if (getAppendedPath().length() > 0) { segmentIndex = 0; } else { segmentIndex = 1; } DirectoryContents directoryContents = projectRootDirectoryContents; while (segmentIndex < segments.length) { // Look through the child directories for the one that matches // this directory segment... Map<Integer, String> childDirectories = directoryContents.getChildDirectories(); Iterator<Map.Entry<Integer, String>> entrySetIt = childDirectories.entrySet().iterator(); while (entrySetIt.hasNext()) { Map.Entry<Integer, String> directoryEntry = entrySetIt.next(); String directoryName = directoryEntry.getValue(); if (0 == directoryName.compareTo(segments[segmentIndex])) { DirectoryContents childDirectoryContents = directoryContentsManager.getDirectoryContentsForDateBasedView(projectView, getAppendedPath(), directoryEntry.getKey().intValue(), response); if (childDirectoryContents != null) { childDirectoryContents.setParentDirectoryID(directoryContents.getDirectoryID()); directoryContents = childDirectoryContents; LOGGER.log(Level.INFO, "Found directory contents for: [" + getAppendedPath() + "]"); } break; } } segmentIndex++; } boolean ignoreOurCaseFlag = getProjectProperties().getIgnoreCaseFlag(); // Ok. We have 'navigated' to the requested directory. Now we need // to build the collection of archiveInfoInterface objects to represent // the files present in the directory for the given view. Map<Integer, String> files = directoryContents.getFiles(); // Now, iterate over the directory contents files, and create the // objects that populate this object's container. Iterator<Integer> it = files.keySet().iterator(); while (it.hasNext()) { int fileID = it.next().intValue(); FileIDInfo fileIDInfo = FileIDDictionary.getInstance().lookupFileIDInfo(getProjectName(), QVCSConstants.QVCS_TRUNK_VIEW, fileID); int directoryID = fileIDInfo.getDirectoryID(); String filenameForView = files.get(Integer.valueOf(fileID)); // Lookup the archiveDirManager for the file's current location... ArchiveDirManager archiveDirManager = DirectoryIDDictionary.getInstance().lookupArchiveDirManager(getProjectName(), directoryID, response, true); String keyToFile = Utility.getArchiveKey(archiveDirManager.getProjectProperties(), fileIDInfo.getShortFilename()); // Get the file's current archiveInfo... LogFile archiveInfo = (LogFile) archiveDirManager.getArchiveInfo(keyToFile); if (archiveInfo != null) { // Create the read-only date-based view of that archiveInfo on the // trunk or on the floating branch. String branchLabel = getRemoteViewProperties().getDateBasedViewBranch(); if ((0 == branchLabel.compareTo(QVCSConstants.QVCS_TRUNK_VIEW)) || (archiveInfo.getLogFileHeaderInfo().hasLabel(branchLabel))) { ArchiveInfoForReadOnlyDateBasedView archiveInfoForReadOnlyDateBasedView = new ArchiveInfoForReadOnlyDateBasedView(filenameForView, archiveInfo, getRemoteViewProperties()); if (archiveInfoForReadOnlyDateBasedView.getLogfileInfo() != null) { String keyToOurFile = filenameForView; if (ignoreOurCaseFlag) { keyToOurFile = keyToOurFile.toLowerCase(); } // And store that read-only date-based view in our map... archiveInfoMap.put(keyToOurFile, archiveInfoForReadOnlyDateBasedView); LOGGER.log(Level.INFO, "Adding file id: " + fileID + " filename: " + filenameForView); } else { LOGGER.log(Level.INFO, "Skipping file id: " + fileID + " filename: " + filenameForView + " as no revisions were created after " + getRemoteViewProperties().getDateBasedDate()); } } else { LOGGER.log(Level.INFO, "Skipping [" + filenameForView + "] because it does not have label [" + branchLabel + "]."); } } else { LOGGER.log(Level.WARNING, "Internal error: Archive not found for [" + fileIDInfo.getShortFilename() + "] for directory ID [" + archiveDirManager.getDirectoryID() + "] " + "and appended path of [" + archiveDirManager.getAppendedPath() + "]."); } } } catch (QVCSException e) { LOGGER.log(Level.WARNING, "Caught exception in populateCollection: " + e.getLocalizedMessage()); LOGGER.log(Level.WARNING, Utility.expandStackTraceToString(e)); } } @Override public boolean moveArchive(String user, String shortWorkfileName, final ArchiveDirManagerInterface targetArchiveDirManager, ServerResponseFactoryInterface response) throws IOException, QVCSException { return false; } @Override public boolean deleteArchive(String user, String shortWorkfileName, ServerResponseFactoryInterface response) throws IOException, QVCSException { return false; } @Override public boolean unDeleteArchive(String user, String shortWorkfileName, ServerResponseFactoryInterface response) throws IOException, QVCSException { return false; } }