| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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 org.apache.commons.vfs2.tasks; |
| |
| import java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.Set; |
| import java.util.StringTokenizer; |
| |
| import org.apache.commons.vfs2.FileName; |
| import org.apache.commons.vfs2.FileObject; |
| import org.apache.commons.vfs2.NameScope; |
| import org.apache.commons.vfs2.Selectors; |
| import org.apache.commons.vfs2.util.Messages; |
| import org.apache.tools.ant.BuildException; |
| import org.apache.tools.ant.Project; |
| |
| /** |
| * An abstract file synchronization task. Scans a set of source files and |
| * folders, and a destination folder, and performs actions on missing and |
| * out-of-date files. Specifically, performs actions on the following: |
| * <ul> |
| * <li>Missing destination file. |
| * <li>Missing source file. |
| * <li>Out-of-date destination file. |
| * <li>Up-to-date destination file. |
| * </ul> |
| * |
| * TODO - Deal with case where dest file maps to a child of one of the source files.<br> |
| * TODO - Deal with case where dest file already exists and is incorrect type (not file, not a folder).<br> |
| * TODO - Use visitors.<br> |
| * TODO - Add default excludes.<br> |
| * TOOD - Allow selector, mapper, filters, etc to be specified.<br> |
| * TODO - Handle source/dest directories as well.<br> |
| * TODO - Allow selector to be specified for choosing which dest files to sync. |
| */ |
| public abstract class AbstractSyncTask |
| extends VfsTask |
| { |
| private final ArrayList<SourceInfo> srcFiles = new ArrayList<SourceInfo>(); |
| private String destFileUrl; |
| private String destDirUrl; |
| private String srcDirUrl; |
| private boolean srcDirIsBase; |
| private boolean failonerror = true; |
| private String filesList; |
| |
| /** |
| * Sets the destination file. |
| * @param destFile The destination file name. |
| */ |
| public void setDestFile(final String destFile) |
| { |
| this.destFileUrl = destFile; |
| } |
| |
| /** |
| * Sets the destination directory. |
| * @param destDir The destination directory. |
| */ |
| public void setDestDir(final String destDir) |
| { |
| this.destDirUrl = destDir; |
| } |
| |
| /** |
| * Sets the source file. |
| * @param srcFile The source file name. |
| */ |
| public void setSrc(final String srcFile) |
| { |
| final SourceInfo src = new SourceInfo(); |
| src.setFile(srcFile); |
| addConfiguredSrc(src); |
| } |
| |
| /** |
| * Sets the source directory. |
| * @param srcDir The source directory. |
| */ |
| public void setSrcDir(final String srcDir) |
| { |
| this.srcDirUrl = srcDir; |
| } |
| |
| /** |
| * Sets whether the source directory should be consider as the base directory. |
| * @param srcDirIsBase true if the source directory is the base directory. |
| */ |
| public void setSrcDirIsBase(final boolean srcDirIsBase) |
| { |
| this.srcDirIsBase = srcDirIsBase; |
| } |
| |
| /** |
| * Sets whether we should fail if there was an error or not. |
| * @param failonerror true if the operation should fail if there is an error. |
| */ |
| public void setFailonerror(final boolean failonerror) |
| { |
| this.failonerror = failonerror; |
| } |
| |
| /** |
| * Sets whether we should fail if there was an error or not. |
| * @return true if the operation should fail if there was an error. |
| */ |
| public boolean isFailonerror() |
| { |
| return failonerror; |
| } |
| |
| /** |
| * Sets the files to includes. |
| * @param filesList The list of files to include. |
| */ |
| public void setIncludes(final String filesList) |
| { |
| this.filesList = filesList; |
| } |
| |
| /** |
| * Adds a nested <src> element. |
| * @param srcInfo A nested source element. |
| * @throws BuildException if the SourceInfo doesn't reference a file. |
| */ |
| public void addConfiguredSrc(final SourceInfo srcInfo) |
| throws BuildException |
| { |
| if (srcInfo.file == null) |
| { |
| final String message = Messages.getString("vfs.tasks/sync.no-source-file.error"); |
| throw new BuildException(message); |
| } |
| srcFiles.add(srcInfo); |
| } |
| |
| /** |
| * Executes this task. |
| * @throws BuildException if an error occurs. |
| */ |
| @Override |
| public void execute() throws BuildException |
| { |
| // Validate |
| if (destFileUrl == null && destDirUrl == null) |
| { |
| final String message = |
| Messages.getString("vfs.tasks/sync.no-destination.error"); |
| logOrDie(message, Project.MSG_WARN); |
| return; |
| } |
| |
| if (destFileUrl != null && destDirUrl != null) |
| { |
| final String message = |
| Messages.getString("vfs.tasks/sync.too-many-destinations.error"); |
| logOrDie(message, Project.MSG_WARN); |
| return; |
| } |
| |
| // Add the files of the includes attribute to the list |
| if (srcDirUrl != null && !srcDirUrl.equals(destDirUrl) && filesList != null && filesList.length() > 0) |
| { |
| if (!srcDirUrl.endsWith("/")) |
| { |
| srcDirUrl += "/"; |
| } |
| final StringTokenizer tok = new StringTokenizer(filesList, ", \t\n\r\f", false); |
| while (tok.hasMoreTokens()) |
| { |
| String nextFile = tok.nextToken(); |
| |
| // Basic compatibility with Ant fileset for directories |
| if (nextFile.endsWith("/**")) |
| { |
| nextFile = nextFile.substring(0, nextFile.length() - 2); |
| } |
| |
| final SourceInfo src = new SourceInfo(); |
| src.setFile(srcDirUrl + nextFile); |
| addConfiguredSrc(src); |
| } |
| } |
| |
| if (srcFiles.size() == 0) |
| { |
| final String message = Messages.getString("vfs.tasks/sync.no-source-files.warn"); |
| logOrDie(message, Project.MSG_WARN); |
| return; |
| } |
| |
| // Perform the sync |
| try |
| { |
| if (destFileUrl != null) |
| { |
| handleSingleFile(); |
| } |
| else |
| { |
| handleFiles(); |
| } |
| } |
| catch (final BuildException e) |
| { |
| throw e; |
| } |
| catch (final Exception e) |
| { |
| throw new BuildException(e.getMessage(), e); |
| } |
| } |
| |
| protected void logOrDie(final String message, final int level) |
| { |
| if (!isFailonerror()) |
| { |
| log(message, level); |
| return; |
| } |
| throw new BuildException(message); |
| } |
| |
| /** |
| * Copies the source files to the destination. |
| */ |
| private void handleFiles() throws Exception |
| { |
| // Locate the destination folder, and make sure it exists |
| final FileObject destFolder = resolveFile(destDirUrl); |
| destFolder.createFolder(); |
| |
| // Locate the source files, and make sure they exist |
| FileName srcDirName = null; |
| if (srcDirUrl != null) |
| { |
| srcDirName = resolveFile(srcDirUrl).getName(); |
| } |
| final ArrayList<FileObject> srcs = new ArrayList<FileObject>(); |
| for (int i = 0; i < srcFiles.size(); i++) |
| { |
| // Locate the source file, and make sure it exists |
| final SourceInfo src = srcFiles.get(i); |
| final FileObject srcFile = resolveFile(src.file); |
| if (!srcFile.exists()) |
| { |
| final String message = |
| Messages.getString("vfs.tasks/sync.src-file-no-exist.warn", srcFile); |
| |
| logOrDie(message, Project.MSG_WARN); |
| } |
| else |
| { |
| srcs.add(srcFile); |
| } |
| } |
| |
| // Scan the source files |
| final Set<FileObject> destFiles = new HashSet<FileObject>(); |
| for (int i = 0; i < srcs.size(); i++) |
| { |
| final FileObject rootFile = srcs.get(i); |
| final FileName rootName = rootFile.getName(); |
| |
| if (rootFile.isFile()) |
| { |
| // Build the destination file name |
| String relName = null; |
| if (srcDirName == null || !srcDirIsBase) |
| { |
| relName = rootName.getBaseName(); |
| } |
| else |
| { |
| relName = srcDirName.getRelativeName(rootName); |
| } |
| final FileObject destFile = destFolder.resolveFile(relName, NameScope.DESCENDENT); |
| |
| // Do the copy |
| handleFile(destFiles, rootFile, destFile); |
| } |
| else |
| { |
| // Find matching files |
| // If srcDirIsBase is true, select also the sub-directories |
| final FileObject[] files = rootFile.findFiles(srcDirIsBase |
| ? Selectors.SELECT_ALL : Selectors.SELECT_FILES); |
| |
| for (final FileObject srcFile : files) |
| { |
| // Build the destination file name |
| String relName = null; |
| if (srcDirName == null || !srcDirIsBase) |
| { |
| relName = rootName.getRelativeName(srcFile.getName()); |
| } |
| else |
| { |
| relName = srcDirName.getRelativeName(srcFile.getName()); |
| } |
| |
| final FileObject destFile = |
| destFolder.resolveFile(relName, NameScope.DESCENDENT); |
| |
| // Do the copy |
| handleFile(destFiles, srcFile, destFile); |
| } |
| } |
| } |
| |
| // Scan the destination files for files with no source file |
| if (detectMissingSourceFiles()) |
| { |
| final FileObject[] allDestFiles = destFolder.findFiles(Selectors.SELECT_FILES); |
| for (final FileObject destFile : allDestFiles) |
| { |
| if (!destFiles.contains(destFile)) |
| { |
| handleMissingSourceFile(destFile); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Handles a single file, checking for collisions where more than one |
| * source file maps to the same destination file. |
| */ |
| private void handleFile(final Set<FileObject> destFiles, |
| final FileObject srcFile, |
| final FileObject destFile) throws Exception |
| |
| { |
| // Check for duplicate source files |
| if (destFiles.contains(destFile)) |
| { |
| final String message = Messages.getString("vfs.tasks/sync.duplicate-source-files.warn", destFile); |
| logOrDie(message, Project.MSG_WARN); |
| } |
| else |
| { |
| destFiles.add(destFile); |
| } |
| |
| // Handle the file |
| handleFile(srcFile, destFile); |
| } |
| |
| /** |
| * Copies a single file. |
| */ |
| private void handleSingleFile() throws Exception |
| { |
| // Make sure there is exactly one source file, and that it exists |
| // and is a file. |
| if (srcFiles.size() > 1) |
| { |
| final String message = |
| Messages.getString("vfs.tasks/sync.too-many-source-files.error"); |
| logOrDie(message, Project.MSG_WARN); |
| return; |
| } |
| final SourceInfo src = srcFiles.get(0); |
| final FileObject srcFile = resolveFile(src.file); |
| if (!srcFile.isFile()) |
| { |
| final String message = |
| Messages.getString("vfs.tasks/sync.source-not-file.error", srcFile); |
| logOrDie(message, Project.MSG_WARN); |
| return; |
| } |
| |
| // Locate the destination file |
| final FileObject destFile = resolveFile(destFileUrl); |
| |
| // Do the copy |
| handleFile(srcFile, destFile); |
| } |
| |
| /** |
| * Handles a single source file. |
| */ |
| private void handleFile(final FileObject srcFile, |
| final FileObject destFile) |
| throws Exception |
| { |
| if (!destFile.exists() |
| || srcFile.getContent().getLastModifiedTime() > destFile.getContent().getLastModifiedTime()) |
| { |
| // Destination file is out-of-date |
| handleOutOfDateFile(srcFile, destFile); |
| } |
| else |
| { |
| // Destination file is up-to-date |
| handleUpToDateFile(srcFile, destFile); |
| } |
| } |
| |
| /** |
| * Handles an out-of-date file. |
| * <p> |
| * This is a file where the destination file |
| * either doesn't exist, or is older than the source file. |
| * <p> |
| * This implementation does nothing. |
| * |
| * @param srcFile The source file. |
| * @param destFile The destination file. |
| * @throws Exception Implementation can throw any Exception. |
| */ |
| protected void handleOutOfDateFile(final FileObject srcFile, |
| final FileObject destFile) |
| throws Exception |
| { |
| } |
| |
| /** |
| * Handles an up-to-date file. |
| * <p> |
| * This is where the destination file exists and is |
| * newer than the source file. |
| * <p> |
| * This implementation does nothing. |
| * |
| * @param srcFile The source file. |
| * @param destFile The destination file. |
| * @throws Exception Implementation can throw any Exception. |
| */ |
| protected void handleUpToDateFile(final FileObject srcFile, |
| final FileObject destFile) |
| throws Exception |
| { |
| } |
| |
| /** |
| * Handles a destination for which there is no corresponding source file. |
| * <p> |
| * This implementation does nothing. |
| * |
| * @param destFile The existing destination file. |
| * @throws Exception Implementation can throw any Exception. |
| */ |
| protected void handleMissingSourceFile(final FileObject destFile) |
| throws Exception |
| { |
| } |
| |
| /** |
| * Check if this task cares about destination files with a missing source |
| * file. |
| * <p> |
| * This implementation returns false. |
| * |
| * @return True if missing file is detected. |
| */ |
| protected boolean detectMissingSourceFiles() |
| { |
| return false; |
| } |
| |
| /** |
| * Information about a source file. |
| */ |
| public static class SourceInfo |
| { |
| private String file; |
| |
| public void setFile(final String file) |
| { |
| this.file = file; |
| } |
| } |
| |
| } |