/* * Copyright 2000-2008 JetBrains s.r.o. * * 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 jetbrains.buildServer.vcs.fileSystem; import jetbrains.buildServer.vcs.*; import jetbrains.buildServer.vcs.patches.PatchBuilder; import jetbrains.buildServer.Used; import jetbrains.buildServer.log.Loggers; import jetbrains.buildServer.web.openapi.WebResourcesManager; import jetbrains.buildServer.serverSide.PropertiesProcessor; import jetbrains.buildServer.serverSide.InvalidProperty; import com.intellij.openapi.diagnostic.Logger; import java.io.*; import java.util.*; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.text.ParseException; import java.nio.channels.FileChannel; import java.nio.ByteBuffer; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** * Simplistic VCS implementation based on the file system. * The VCS does not support versions: the current version is always returned, only file additions are detected (by last modified time). * Deleted files are removed only if clean checkout is used. * * @author Yegor Yarko * Date: 12.04.2008 */ public class FileSystemVcsSupport extends VcsSupport { private final static Logger LOG = Loggers.VCS; private static final String DIRECTORY_PATH_PROP_NAME = "directoryPath"; private static final Date MIN_DATE = new Date(0); private DateFormat myFormat = new SimpleDateFormat(); private final Comparator myVersionComparator; FileSystemVcsSupport(WebResourcesManager resourcesManager, VcsManager vcsManager) { resourcesManager.addPluginResources("fileVcs", "fileVcs.jar"); vcsManager.registerVcsSupport(this); myVersionComparator = new Comparator() { public int compare(String o1, String o2) { Date o1Date = versionToDate(o1); if (o1Date != null) { return versionToDate(o1).compareTo(versionToDate(o2)); } else { return 1; } } }; } /** * Only file additions are supported for reporting. * Always compares with the current version, ignoring "toVersion" parameter. */ public List collectBuildChanges(VcsRoot vcsRoot, String fromVersion, String toVersion, CheckoutRules checkoutRules) throws VcsException { final Date fromDate = versionToDate(fromVersion); final Date toDate = versionToDate(toVersion); if (fromDate.before(toDate) || fromDate.equals(toDate)) { return new ArrayList(); } final String dirName = getDirectoryPath(vcsRoot); return getModifications(new File(dirName), fromDate, "", vcsRoot); } @NotNull private static String getDirectoryPath(VcsRoot vcsRoot) throws VcsException { final String dirName = vcsRoot.getProperty(DIRECTORY_PATH_PROP_NAME); if (dirName == null || "".equals(dirName.trim())) { throw new VcsException("The directory is not specified."); } return dirName; } private List getModifications(File dir, Date fromDate, String relativePath, VcsRoot vcsRoot) { LOG.debug("Gathering modifications for dir '" + dir.getAbsolutePath() + "', from '" + fromDate + "'"); List result = new ArrayList(); File children[] = dir.listFiles(); for (File file : children) { String fileNestedPath = ("".equals(relativePath) ? relativePath + "/" : "") + file.getName(); if (file.isDirectory()) { if (fromDate.before(new Date(file.lastModified()))) { VcsChange change = new VcsChange(VcsChangeInfo.Type.DIRECTORY_CHANGED, file.getAbsolutePath(), fileNestedPath, null, null); LOG.debug("Adding directory change of " + file + ". fileName: '" + file.getAbsolutePath() + "', relativeFileName: '" + fileNestedPath + "'."); System.out.println("Adding directory change of " + file + ". fileName: '" + file.getAbsolutePath() + "', relativeFileName: '" + fileNestedPath + "'."); result.add(new ModificationData(new Date(file.lastModified()), Collections.singletonList(change), "directory added", "unknown", vcsRoot, dateToVersion(new Date(file.lastModified())), dateToVersion(new Date(file.lastModified())))); } result.addAll(getModifications(file, fromDate, fileNestedPath, vcsRoot)); } else { if (fromDate.before(new Date(file.lastModified()))) { VcsChange change = new VcsChange(VcsChangeInfo.Type.CHANGED, file.getAbsolutePath(), fileNestedPath, null, null); LOG.debug("Adding file change of " + file + ". fileName: '" + file.getAbsolutePath() + "', relativeFileName: '" + fileNestedPath + "'."); System.out.println("Adding file change of " + file + ". fileName: '" + file.getAbsolutePath() + "', relativeFileName: '" + fileNestedPath + "'."); result.add(new ModificationData(new Date(file.lastModified()), Collections.singletonList(change), "file modified/added", "unknown", vcsRoot, dateToVersion(new Date(file.lastModified())), dateToVersion(new Date(file.lastModified())))); } } } return result; } public void buildPatch(VcsRoot vcsRoot, String fromVersion, String toVersion, PatchBuilder builder, CheckoutRules checkoutRules) throws IOException, VcsException { final String dirName = getDirectoryPath(vcsRoot); addToPatch(new File(dirName), builder, "", versionToDate(fromVersion)); //TODO: apply checkout rules } private void addToPatch(File dir, PatchBuilder builder, String nestedPath, Date fromDate) throws IOException { LOG.debug("Adding to patch dir '" + dir.getAbsolutePath() + "', from '" + fromDate + "', nested path '" + nestedPath + "'"); File children[] = dir.listFiles(); for (File file : children) { String fileNestedPath = ("".equals(nestedPath) ? nestedPath + "/" : "") + file.getName(); if (file.isDirectory()) { builder.createDirectory(new File(fileNestedPath)); addToPatch(file, builder, fileNestedPath, fromDate); } else { final File actualFile = new File(fileNestedPath); if (fromDate.before(new Date(actualFile.lastModified()))) { builder.changeOrCreateBinaryFile(actualFile, null, new FileInputStream(file), file.length()); } } } } @NotNull public byte[] getContent(VcsModification vcsModification, VcsChangeInfo change, VcsChangeInfo.ContentType contentType, VcsRoot vcsRoot) throws VcsException { return getContent(change.getFileName(), vcsRoot, vcsModification.getVersion()); } @NotNull public byte[] getContent(String filePath, VcsRoot versionedRoot, String version) throws VcsException { final String dirName = getDirectoryPath(versionedRoot); return getFileContent(filePath, dirName); } private byte[] getFileContent(String filePath, String dirName) throws VcsException { File file = new File(filePath); if (!file.exists()) { throw new VcsException("File " + file.getAbsolutePath() + " does not exist."); } if (!file.canRead()) { throw new VcsException("File " + file.getAbsolutePath() + " cannot be read."); } try { return getFileContent(file); } catch (IOException e) { throw new VcsException("Error reading file " + file.getAbsolutePath() + ",", e); } } private byte[] getFileContent(File file) throws IOException { FileInputStream fis = new FileInputStream(file); FileChannel fc = fis.getChannel(); byte[] data = new byte[(int) fc.size()]; ByteBuffer bb = ByteBuffer.wrap(data); fc.read(bb); return data; } public String getName() { return "fileVcs"; } @Used("jsp") public String getDisplayName() { return "File System"; } public PropertiesProcessor getVcsPropertiesProcessor() { return new PropertiesProcessor() { public Collection process(Map properties) { final ArrayList result = new ArrayList(); final String dirName = properties.get(DIRECTORY_PATH_PROP_NAME); if (dirName == null || "".equals(dirName.trim())) { result.add(new InvalidProperty(DIRECTORY_PATH_PROP_NAME, "The directory is not specified.")); } else { File dir = new File(dirName); if (!dir.exists()) { result.add(new InvalidProperty(DIRECTORY_PATH_PROP_NAME, "The directory does not exist on the server.")); //TODO: should be revised when agent-side checkout is implemented. } } return result; } }; } public String getVcsSettingsJspFilePath() { return "fileSystemSettings.jsp"; } public String getCurrentVersion(VcsRoot vcsRoot) throws VcsException { final String dirName = getDirectoryPath(vcsRoot); File dir = new File(dirName); if (!dir.exists()) { return dateToVersion(MIN_DATE); } return dateToVersion(getMaxLastModified(dir)); } private Date getMaxLastModified(File dir) { Date maxLastModified = MIN_DATE; File children[] = dir.listFiles(); for (int i = 0; i < children.length; i++) { File file = children[i]; Date fileLastModified = new Date(file.lastModified()); if (fileLastModified.after(maxLastModified)) { maxLastModified = fileLastModified; } if (file.isDirectory()) { Date nestedLastModified = getMaxLastModified(file); if (nestedLastModified.after(maxLastModified)) { maxLastModified = nestedLastModified; } } } return maxLastModified; } public String describeVcsRoot(VcsRoot vcsRoot) { return "filesystem:" + vcsRoot.getProperty(DIRECTORY_PATH_PROP_NAME, "undefined"); } public boolean isTestConnectionSupported() { return true; } public String testConnection(VcsRoot vcsRoot) throws VcsException { //TODO: revise for the agent checkout final String dirName = vcsRoot.getProperty(DIRECTORY_PATH_PROP_NAME); if ((dirName == null) || "".equals(dirName.trim())) { throw new VcsException("Directory name is not specified."); } File dir = new File(dirName); if (dir.exists()) { return "Directory " + dirName + " exists."; } else { throw new VcsException("Directory " + dirName + " does not exist."); } } @Nullable public Map getDefaultVcsProperties() { return new HashMap(); } public String getVersionDisplayName(String version, VcsRoot root) throws VcsException { return version; } @NotNull public Comparator getVersionComparator() { return myVersionComparator; } private Date versionToDate(String version) { if (version == null) { LOG.warn("Supplied version '" + version + "' is not a valid fileSystem VCS version."); return MIN_DATE; } try { return myFormat.parse(version); } catch (ParseException e) { LOG.warn("Supplied version '" + version + "' is not a valid fileSystem VCS version."); return MIN_DATE; } } private String dateToVersion(Date date) { return myFormat.format(date); } }