/* * Syncany, www.syncany.org * Copyright (C) 2011-2015 Philipp C. Heckel <philipp.heckel@gmail.com> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.syncany.tests.unit.util; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.io.RandomAccessFile; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.syncany.util.FileUtil; /** * This class provides file I/O helper methods for writing tests * * @author Philipp Heckel <philipp.heckel@gmail.com> * @author Nikolai Hellwig * @author Andreas Fenske */ public class TestFileUtil { private static String IGNORE_DIR_APPLICATION = ".syncany"; // same as Config.DIR_APPLICATION private static Random randomGen = new Random(); private static Random nonRandomGen = new Random(123456789L); // fixed seed! public static File copyFile(File fromFile, File toFile) throws IOException { InputStream in = new FileInputStream(fromFile); OutputStream out = new FileOutputStream(toFile); byte[] buffer = new byte[1024]; int length; // copy the file content in bytes while ((length = in.read(buffer)) > 0) { out.write(buffer, 0, length); } in.close(); out.close(); toFile.setLastModified(fromFile.lastModified()); // Windows changes last modified when copying file return toFile; } public static File createTempDirectoryInSystemTemp() throws Exception { return createTempDirectoryInSystemTemp("syncanytest"); } public static File getAppTempDir() { String tempDirStr = System.getProperty("org.syncany.test.tmpdir"); if (tempDirStr == null) { tempDirStr = System.getProperty("java.io.tmpdir"); } return new File(tempDirStr, "syncanytest"); } public static File createTempDirectoryInSystemTemp(String prefix) throws Exception { File tempDirectoryInSystemTemp = new File(getAppTempDir() + "/" + prefix); int i = 1; while (tempDirectoryInSystemTemp.exists()) { tempDirectoryInSystemTemp = new File(getAppTempDir() + "/" + prefix + "-" + i); i++; } if (!tempDirectoryInSystemTemp.mkdirs()) { throw new Exception("Cannot create temp. directory " + tempDirectoryInSystemTemp); } return tempDirectoryInSystemTemp; } public static boolean deleteDirectory(File path) { if (path != null && path.exists() && path.isDirectory()) { File[] files = path.listFiles(); for (int i = 0; i < files.length; i++) { if (files[i].isDirectory()) { deleteDirectory(files[i]); } else { files[i].delete(); } } } else return false; return (path.delete()); } public static boolean deleteFile(File file) { if (file != null && file.exists() && file.isFile()) { return file.delete(); } else return false; } public static void changeRandomPartOfBinaryFile(File file) throws IOException { if (file != null && !file.exists()) { throw new IOException("File does not exist: " + file); } if (file.isDirectory()) { throw new IOException("Cannot change directory: " + file); } // Prepare: random bytes at random position Random randomEngine = new Random(); int fileSize = (int) file.length(); int maxChangeBytesLen = 20; int maxChangeBytesStartPos = (fileSize - maxChangeBytesLen - 1 >= 0) ? fileSize - maxChangeBytesLen - 1 : 0; int changeBytesStartPos = (maxChangeBytesStartPos > 0) ? randomEngine.nextInt(maxChangeBytesStartPos) : 0; int changeBytesLen = (fileSize - changeBytesStartPos < maxChangeBytesLen) ? fileSize - changeBytesStartPos - 1 : maxChangeBytesLen; byte[] changeBytes = new byte[changeBytesLen]; randomEngine.nextBytes(changeBytes); // Write to file RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw"); randomAccessFile.seek(changeBytesStartPos); randomAccessFile.write(changeBytes); randomAccessFile.close(); } public static File getRandomFilenameInDirectory(File rootFolder) { String fileName = "rndFile-" + System.currentTimeMillis() + "-" + Math.abs(randomGen.nextInt()) + ".dat"; File newRandomFile = new File(rootFolder, fileName); return newRandomFile; } public static List<File> createRandomFileTreeInDirectory(File rootFolder, int maxFiles) throws IOException { List<File> randomFiles = new ArrayList<File>(); List<File> randomDirs = new ArrayList<File>(); File currentDir = rootFolder; for (int i = 0; i < maxFiles; i++) { if (!randomDirs.isEmpty()) { currentDir = randomDirs.get((int) Math.random() * randomDirs.size()); } if (Math.random() > 0.3) { File newFile = new File(currentDir + "/file" + i); int newFileSize = (int) Math.round(1000.0 + Math.random() * 500000.0); createRandomFile(newFile, newFileSize); randomFiles.add(newFile); } else { currentDir = new File(currentDir + "/folder" + i); currentDir.mkdir(); randomDirs.add(currentDir); randomFiles.add(currentDir); } } // Now copy some files (1:1 copy), and slightly change some of them (1:0.9) for (int i = maxFiles; i < maxFiles + maxFiles / 4; i++) { File srcFile = randomFiles.get((int) (Math.random() * (double) randomFiles.size())); File destDir = randomDirs.get((int) (Math.random() * (double) randomDirs.size())); if (srcFile.isDirectory()) { continue; } // Alter some of the copies (change some bytes) if (Math.random() > 0.5) { File destFile = new File(destDir + "/file" + i + "-almost-the-same-as-" + srcFile.getName()); FileUtils.copyFile(srcFile, destFile); changeRandomPartOfBinaryFile(destFile); randomFiles.add(destFile); } // Or simply copy them else { File destFile = new File(destDir + "/file" + i + "-copy-of-" + srcFile.getName()); FileUtils.copyFile(srcFile, destFile); randomFiles.add(destFile); } } return randomFiles; } public static List<File> createRandomFilesInDirectory(File rootFolder, long sizeInBytes, int numOfFiles) throws IOException { List<File> newRandomFiles = new ArrayList<File>(); for (int i = 0; i < numOfFiles; i++) { newRandomFiles.add(createRandomFileInDirectory(rootFolder, sizeInBytes)); } return newRandomFiles; } public static File createRandomFileInDirectory(File rootFolder, long sizeInBytes) throws IOException { File newRandomFile = getRandomFilenameInDirectory(rootFolder); createRandomFile(newRandomFile, sizeInBytes); return newRandomFile; } public static void createNonRandomFile(File fileToCreate, long sizeInBytes) throws IOException { createFile(fileToCreate, sizeInBytes, nonRandomGen); } public static void createFileWithContent(File fileToCreate,String content) throws IOException { if (fileToCreate != null && fileToCreate.exists()) { throw new IOException("File already exists"); } PrintWriter writer = new PrintWriter(fileToCreate); writer.print(content); writer.close(); } public static void createRandomFile(File fileToCreate, long sizeInBytes) throws IOException { createFile(fileToCreate, sizeInBytes, randomGen); } private static void createFile(File fileToCreate, long sizeInBytes, Random randomGen) throws IOException { if (fileToCreate != null && fileToCreate.exists()) { throw new IOException("File already exists"); } FileOutputStream fos = new FileOutputStream(fileToCreate); int bufSize = 4096; long cycles = sizeInBytes / (long) bufSize; for (int i = 0; i < cycles; i++) { byte[] randomByteArray = createArray(bufSize, randomGen); fos.write(randomByteArray); } // create last one // modulo cannot exceed integer range, so cast should be ok byte[] arr = createArray((int) (sizeInBytes % bufSize), randomGen); fos.write(arr); fos.close(); } public static void writeByteArrayToFile(byte[] inputByteArray, File fileToCreate) throws IOException { FileOutputStream fos = new FileOutputStream(fileToCreate); fos.write(inputByteArray); fos.close(); } public static byte[] createArray(int size, Random randomGen) { byte[] ret = new byte[size]; randomGen.nextBytes(ret); return ret; } public static byte[] createRandomArray(int size) { return createArray(size, randomGen); } public static byte[] createChecksum(File file) throws Exception { return FileUtil.createChecksum(file, "SHA1"); } public static Map<String, File> getLocalFiles(File root) throws FileNotFoundException { return getLocalFiles(root, null); } public static Map<String, File> getLocalFilesExcludeLockedAndNoRead(File root) throws FileNotFoundException { return getLocalFiles(root, new FileFilter() { @Override public boolean accept(File file) { return !FileUtil.isFileLocked(file) && canRead(file); } }); } public static Map<String, File> getLocalFiles(File root, FileFilter filter) throws FileNotFoundException { List<File> fileList = getRecursiveFileList(root, true, false); Map<String, File> fileMap = new HashMap<String, File>(); for (File file : fileList) { if (filter != null && !filter.accept(file)) { continue; } String relativePath = FileUtil.getRelativePath(root, file); if (relativePath.startsWith(IGNORE_DIR_APPLICATION)) { continue; } fileMap.put(relativePath, file); } return fileMap; } /** * Replaces the {@link File#canRead() canRead()} method in the {@link File} class by taking * symlinks into account. Returns <tt>true</tt> if a symlink exists even if its target file * does not exist and can hence not be read. * * @param file A file * @return Returns <tt>true</tt> if the file can be read (or the symlink exists), <tt>false</tt> otherwise */ public static boolean canRead(File file) { if (FileUtil.isSymlink(file)) { return FileUtil.exists(file); } else { return file.canRead(); } } public static void writeToFile(byte[] bytes, File file) throws IOException { FileOutputStream outputStream = new FileOutputStream(file); IOUtils.copy(new ByteArrayInputStream(bytes), outputStream); outputStream.close(); } public static String getBasename(String filename) { int dot = filename.lastIndexOf("."); if (dot == -1) { return filename; } return filename.substring(0, dot); } public static List<File> getRecursiveFileList(File root) throws FileNotFoundException { return getRecursiveFileList(root, false, false); } public static List<File> getRecursiveFileList(File root, boolean includeDirectories, boolean followSymlinkDirectories) throws FileNotFoundException { if (!root.isDirectory() || !root.canRead() || !root.exists()) { throw new FileNotFoundException("Invalid directory " + root); } List<File> result = getRecursiveFileListNoSort(root, includeDirectories, followSymlinkDirectories); Collections.sort(result); return result; } private static List<File> getRecursiveFileListNoSort(File root, boolean includeDirectories, boolean followSymlinkDirectories) { List<File> result = new ArrayList<File>(); List<File> filesDirs = Arrays.asList(root.listFiles()); for (File file : filesDirs) { boolean isDirectory = file.isDirectory(); boolean isSymlinkDirectory = isDirectory && FileUtil.isSymlink(file); boolean includeFile = !isDirectory || includeDirectories; boolean followDirectory = (isSymlinkDirectory && followSymlinkDirectories) || (isDirectory && !isSymlinkDirectory); if (includeFile) { result.add(file); } if (followDirectory) { List<File> deeperList = getRecursiveFileListNoSort(file, includeDirectories, followSymlinkDirectories); result.addAll(deeperList); } } return result; } }