| /* |
| * 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.FileObjectUtils; |
| 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> |
| * |
| * <ul> |
| * <li>TODO - Deal with case where dest file maps to a child of one of the source files.</li> |
| * <li>TODO - Deal with case where dest file already exists and is incorrect type (not file, not a folder).</li> |
| * <li>TODO - Use visitors.</li> |
| * <li>TODO - Add default excludes.</li> |
| * <li>TOOD - Allow selector, mapper, filters, etc to be specified.</li> |
| * <li>TODO - Handle source/dest directories as well.</li> |
| * <li>TODO - Allow selector to be specified for choosing which dest files to sync.</li> |
| * </ul> |
| */ |
| public abstract class AbstractSyncTask extends VfsTask { |
| private final ArrayList<SourceInfo> srcFiles = new ArrayList<>(); |
| 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.isEmpty()) { |
| 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<>(); |
| for (final SourceInfo src : srcFiles) { |
| 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<>(); |
| for (final FileObject rootFile : srcs) { |
| final FileName rootName = rootFile.getName(); |
| |
| if (rootFile.isFile()) { |
| // Build the destination file name |
| final String relName; |
| 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 |
| final String relName; |
| 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 (!FileObjectUtils.exists(destFile) |
| || 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> |
| * <p> |
| * This implementation does nothing. |
| * </p> |
| * |
| * @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 { |
| // noop |
| } |
| |
| /** |
| * Handles an up-to-date file. |
| * <p> |
| * This is where the destination file exists and is newer than the source file. |
| * </p> |
| * <p> |
| * This implementation does nothing. |
| * </p> |
| * |
| * @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 { |
| // noop |
| } |
| |
| /** |
| * Handles a destination for which there is no corresponding source file. |
| * <p> |
| * This implementation does nothing. |
| * </p> |
| * |
| * @param destFile The existing destination file. |
| * @throws Exception Implementation can throw any Exception. |
| */ |
| protected void handleMissingSourceFile(final FileObject destFile) throws Exception { |
| // noop |
| } |
| |
| /** |
| * Check if this task cares about destination files with a missing source file. |
| * <p> |
| * This implementation returns false. |
| * </p> |
| * |
| * @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; |
| } |
| } |
| |
| } |