/* * Copyright 2004 - 2008 Christian Sprajc. All rights reserved. * * This file is part of PowerFolder. * * PowerFolder 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. * * PowerFolder 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 PowerFolder. If not, see <http://www.gnu.org/licenses/>. * * $Id$ */ package de.dal33t.powerfolder.util; import java.awt.Desktop; import java.io.EOFException; import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; import java.io.FileWriter; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.io.RandomAccessFile; import java.net.URL; import java.security.MessageDigest; import java.util.HashSet; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import java.util.zip.ZipEntry; import java.util.zip.ZipException; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; import de.dal33t.powerfolder.ConfigurationEntry; import de.dal33t.powerfolder.Constants; import de.dal33t.powerfolder.Controller; import de.dal33t.powerfolder.light.FolderInfo; import de.dal33t.powerfolder.util.os.OSUtil; import de.dal33t.powerfolder.util.os.Win32.WinUtils; import de.schlichtherle.truezip.file.TFile; import de.schlichtherle.truezip.file.TFileInputStream; import de.schlichtherle.truezip.file.TFileOutputStream; public class FileUtils { private static final Logger log = Logger.getLogger(FileUtils.class .getName()); private static final int BYTE_CHUNK_SIZE = 8192; public static final String DOWNLOAD_META_FILE = "(downloadmeta) "; public static final String DESKTOP_INI_FILENAME = "desktop.ini"; // no instances private FileUtils() { } /** * @param file * @return true if this file is the windows desktop.ini */ public static boolean isDesktopIni(File file) { if (file == null) { throw new NullPointerException("File is null"); } return file.getName().equalsIgnoreCase(DESKTOP_INI_FILENAME); } /** * @param file * @return true if the file is a valid zipfile */ public static boolean isValidZipFile(File file) { if (file == null) { throw new NullPointerException("File is null"); } try { new ZipFile(file); } catch (ZipException e) { return false; } catch (IOException e) { return false; } return true; } /** * #1882 Correct solution * * @param f * @return the suggested folder name */ public static String getSuggestedFolderName(File f) { if (f == null) { return null; } if (StringUtils.isNotBlank(f.getName())) { return f.getName(); } return f.getAbsolutePath(); } /** * Copies a file * * @param from * @param to * if file exists it will be overwritten! * @throws IOException */ public static void copyFile(File from, File to) throws IOException { if (from == null) { throw new NullPointerException("From file is null"); } if (!from.exists()) { throw new IOException("From file does not exists " + from.getAbsolutePath()); } if (from.equals(to)) { throw new IOException("cannot copy onto itself"); } copyFromStreamToFile(new TFileInputStream(from), to); } /** * Copies a file to disk from a stream. Overwrites the target file if exists * * @see #copyFromStreamToFile(InputStream, File, StreamCallback, int) * @param in * the input stream * @param to * the file where the stream should be written in * @throws IOException */ public static void copyFromStreamToFile(InputStream in, File to) throws IOException { copyFromStreamToFile(in, to, null, 0); } /** * Copies a file to disk from a stream. Overwrites the target file if * exists. The processe may be observed with a stream callback * * @param in * the input stream * @param to * the file wher the stream should be written in * @param callback * the callback to get information about the process, may be left * null * @param totalAvailableBytes * the byte total available * @throws IOException * any io excetion or the stream read is broken by the callback */ public static void copyFromStreamToFile(InputStream in, File to, StreamCallback callback, int totalAvailableBytes) throws IOException { if (in == null) { throw new NullPointerException("InputStream file is null"); } if (to == null) { throw new NullPointerException("To file is null"); } OutputStream out = null; try { if (to.exists()) { if (!to.delete()) { throw new IOException("Unable to delete old file " + to.getAbsolutePath()); } } if (to.getParentFile() != null && !to.getParentFile().exists()) { to.getParentFile().mkdirs(); } if (!to.createNewFile()) { throw new IOException("Unable to create file " + to.getAbsolutePath()); } if (!to.canWrite()) { throw new IOException("Unable to write to " + to.getAbsolutePath()); } out = new TFileOutputStream(to); byte[] buffer = new byte[BYTE_CHUNK_SIZE]; int read; int position = 0; do { read = in.read(buffer); if (read < 0) { break; } out.write(buffer, 0, read); position += read; if (callback != null) { // Execute callback boolean breakStream = callback.streamPositionReached( position, totalAvailableBytes); if (breakStream) { throw new IOException( "Stream read break requested by callback. " + callback); } } } while (read >= 0); } finally { // Close streams try { in.close(); } catch (IOException e) { } if (out != null) { try { out.close(); } catch (IOException e) { } } } } /** * Copies a given amount of data from one RandomAccessFile to another. * * @param in * the file to read the data from * @param out * the file to write the data to * @param n * the amount of bytes to transfer * @throws IOException * if an Exception occurred while reading or writing the data */ public static void ncopy(RandomAccessFile in, RandomAccessFile out, int n) throws IOException { int w = n; byte[] buf = new byte[BYTE_CHUNK_SIZE]; while (w > 0) { int read = in.read(buf); if (read < 0) { throw new EOFException(); } out.write(buf, 0, read); w -= read; } } /** * Execute the file. * * @param file * @return true if suceeded. false if not. */ public static boolean openFile(File file) { Reject.ifNull(file, "File is null"); if (Desktop.isDesktopSupported()) { try { if (OSUtil.isWindowsSystem() && !file.isDirectory()) { Runtime.getRuntime().exec( "rundll32 SHELL32.DLL,ShellExec_RunDLL \"" + file.toString() + "\""); } else { Desktop.getDesktop().open(file); } return true; } catch (IOException e) { log.warning("Unable to open file " + file + ". " + e); return false; } } else if (OSUtil.isLinux()) { // PFC-2314: Workaround for missing Java Desktop try { Runtime.getRuntime().exec( "/usr/bin/xdg-open " + file.toURI().toString()); return true; } catch (Exception e) { log.warning("Unable to open file " + file + ". " + e); return false; } } else { log.warning("Unable to open file " + file + ". Java Desktop not supported"); return false; } } /** * Sets file attributes on windows system * * @param file * the file to change * @param hidden * true if file should be hidden, false if it should be unhidden, * null if no change to the hidden status should be done. * @param system * true if file should be system, false if it should be marked as * non-system, null if no change to the system status should be * done. * @return true if succeeded */ public static boolean setAttributesOnWindows(File file, Boolean hidden, Boolean system) { if (!OSUtil.isWindowsSystem() || OSUtil.isWindowsMEorOlder()) { // Not set attributes on non-windows systems or win ME or older return false; } if (hidden == null && system == null) { // No actual change. return true; } try { String s = "attrib "; if (hidden != null) { if (hidden) { s += '+'; } else { s += '-'; } s += 'h'; s += ' '; } if (system != null) { if (system) { s += '+'; } else { s += '-'; } s += 's'; s += ' '; } s += " \"" + file.getAbsolutePath() + '\"'; Process proc = Runtime.getRuntime().exec(s); proc.getOutputStream(); proc.waitFor(); return true; } catch (IOException e) { log.log(Level.FINER, "IOException", e); return false; } catch (InterruptedException e) { log.log(Level.FINER, "InterruptedException", e); return false; } } /** * A recursive delete of a directory. * * @param file * directory to delete * @throws IOException */ public static void recursiveDelete(File file) throws IOException { recursiveDelete(file, new FileFilter() { public boolean accept(File pathname) { return true; } }); } /** * A recursive delete of a directory. * * @param file * directory to delete * @param filter * accept to delete * @throws IOException */ public static void recursiveDelete(File file, FileFilter filter) throws IOException { if (file == null) { return; } if (!filter.accept(file)) { return; } if (file.isDirectory()) { File[] files = file.listFiles(filter); for (File nextFile : files) { recursiveDelete(nextFile); } } if (file.exists() && !file.delete()) { throw new IOException("Could not delete file " + file.getAbsolutePath()); } } /** * A recursive move of one directory to another. * * @param sourceFile * @param targetFile * @throws IOException */ public static void recursiveMove(File sourceFile, File targetFile) throws IOException { Reject.ifNull(sourceFile, "Source directory is null"); Reject.ifNull(targetFile, "Target directory is null"); if (!sourceFile.exists()) { // Do nothing. return; } if (sourceFile.isDirectory() && !targetFile.exists()) { targetFile.mkdirs(); } if (sourceFile.isDirectory() && targetFile.isDirectory()) { if (isSubdirectory(sourceFile, targetFile)) { // Need to be careful if moving to a subdirectory, // avoid infinite recursion. throw new IOException("Move to a subdirectory not permitted"); } else { File[] files = sourceFile.listFiles(); for (File nextOriginalFile : files) { // Synthesize target file name. String lastPart = nextOriginalFile.getName(); File nextTargetFile = new TFile(targetFile, lastPart); recursiveMove(nextOriginalFile, nextTargetFile); } // Delete directory after move sourceFile.delete(); } } else if (!sourceFile.isDirectory() && !targetFile.isDirectory()) { sourceFile.renameTo(targetFile); } else { throw new UnsupportedOperationException( "Can only move directory to directory or file to file: " + sourceFile.getAbsolutePath() + " --> " + targetFile.getAbsolutePath()); } // Hide target if original is hidden. if (sourceFile.isHidden()) { setAttributesOnWindows(targetFile, true, null); } } /** * A recursive copy of one directory to another. * * @param sourceFile * @param targetFile * @throws IOException */ public static void recursiveCopy(File sourceFile, File targetFile) throws IOException { recursiveCopy(sourceFile, targetFile, new FileFilter() { public boolean accept(File pathname) { return true; } }); } /** * A recursive copy of one directory to another. * * @param sourceFile * @param targetFile * @param filter * the filter to apply while coping. null if all files should be * copied. * @throws IOException */ public static void recursiveCopy(File sourceFile, File targetFile, FileFilter filter) throws IOException { Reject.ifNull(sourceFile, "Source directory is null"); Reject.ifNull(targetFile, "Target directory is null"); if (!sourceFile.exists()) { // Do nothing. return; } if (sourceFile.isDirectory() && !targetFile.exists()) { targetFile.mkdirs(); } if (sourceFile.isDirectory() && targetFile.isDirectory()) { if (isSubdirectory(sourceFile, targetFile)) { // Need to be careful if copying to a subdirectory, // avoid infinite recursion. throw new IOException("Copy to a subdirectory not permitted"); } else { File[] sourceFiles = sourceFile.listFiles(filter); for (File nextOriginalFile : sourceFiles) { // Synthesize target file name. String lastPart = nextOriginalFile.getName(); File nextTargetFile = new TFile(targetFile, lastPart); recursiveCopy(nextOriginalFile, nextTargetFile, filter); } } } else if (!sourceFile.isDirectory() && !targetFile.isDirectory() && filter.accept(sourceFile)) { copyFile(sourceFile, targetFile); } else { throw new UnsupportedOperationException( "Can only copy directory to directory or file to file: " + sourceFile.getAbsolutePath() + " --> " + targetFile.getAbsolutePath()); } } /** * Creates a recursive mirror of one directory into another. Files in target * that do not exist in source will be deleted. * <p> * Does not mirror last modification dates. * * @param source * @param target * @throws IOException */ public static void recursiveMirror(File source, File target) throws IOException { recursiveMirror(source, target, new FileFilter() { public boolean accept(File pathname) { return true; } }); } /** * Creates a recursive mirror of one directory into another. Files in target * that do not exist in source will be deleted. * <p> * Does not mirror last modification dates. * * @param source * @param target * @param filter * the filter which answers to check * @throws IOException */ public static void recursiveMirror(File source, File target, FileFilter filter) throws IOException { Reject.ifNull(source, "Source directory is null"); Reject.ifNull(target, "Target directory is null"); Reject.ifNull(filter, "Filter is null"); if (!source.exists()) { // Do nothing. return; } if (source.isDirectory() && !target.exists()) { target.mkdirs(); } if (source.isDirectory() && target.isDirectory()) { if (filter.accept(target) && isSubdirectory(source, target)) { // Need to be careful if copying to a subdirectory, // avoid infinite recursion. throw new IOException("Copy to a subdirectory not permitted"); } else { File[] sourceDirFiles = source.listFiles(filter); Set<String> done = new HashSet<String>(sourceDirFiles.length); for (File sourceDirFile : sourceDirFiles) { // Synthesize target file name. String lastPart = sourceDirFile.getName(); File targetDirFile = new TFile(target, lastPart); recursiveMirror(sourceDirFile, targetDirFile, filter); done.add(lastPart); } for (File targetDirFile : target.listFiles(filter)) { String lastPart = targetDirFile.getName(); if (done.contains(lastPart)) { continue; } if (targetDirFile.isFile() && !targetDirFile.delete()) { throw new IOException( "Unable to delete file in target directory: " + targetDirFile); } else if (targetDirFile.isDirectory()) { recursiveDelete(targetDirFile); } } } } else if (!source.isDirectory() && !target.isDirectory() && filter.accept(source)) { copyFile(source, target); // Preserve modification date. target.setLastModified(source.lastModified()); } else { throw new UnsupportedOperationException( "Can only copy directory to directory or file to file: " + source.getAbsolutePath() + " --> " + target.getAbsolutePath()); } } private static final long MS_18_MAR_2013 = 1363357334684L + 1000L * 60 * 60 * 24 * 3; /** * Set / remove desktop ini in managed folders. * * @param controller * @param directory */ public static void maintainDesktopIni(Controller controller, File directory) { // Only works on Windows // Vista you must log off and on again to see change if (!OSUtil.isWindowsSystem() || OSUtil.isWebStart()) { return; } // Safty checks. if (directory == null || !directory.exists() || !directory.isDirectory()) { return; } // Look for a desktop ini in the folder. File desktopIniFile = new TFile(directory, DESKTOP_INI_FILENAME); boolean iniExists = desktopIniFile.exists(); boolean usePfIcon = ConfigurationEntry.USE_PF_ICON .getValueBoolean(controller); // Migration to 8 SP1: Correct older folder icon setup if (iniExists && desktopIniFile.lastModified() < MS_18_MAR_2013) { // PFC-1500: Migration iniExists = !desktopIniFile.delete(); } if (!iniExists && usePfIcon) { // Need to set up desktop ini. PrintWriter pw = null; try { // @todo Does anyone know a nicer way of finding the run time // directory? File hereFile = new TFile(""); String herePath = hereFile.getAbsolutePath(); String exeName = controller.getDistribution().getBinaryName() + ".exe"; File powerFolderFile = new TFile(herePath, exeName); if (!powerFolderFile.exists()) { // Try harder powerFolderFile = new TFile( WinUtils.getProgramInstallationPath(), exeName); if (!powerFolderFile.exists()) { log.fine("Could not find " + powerFolderFile.getName() + " at " + powerFolderFile.getAbsolutePath()); return; } } // Write desktop ini directory pw = new PrintWriter(new FileWriter(new TFile(directory, DESKTOP_INI_FILENAME))); pw.println("[.ShellClassInfo]"); pw.println("ConfirmFileOp=0"); pw.println("IconFile=" + powerFolderFile.getAbsolutePath()); pw.println("IconIndex=0"); pw.println("InfoTip=" + Translation.getTranslation("folder.info_tip")); // Required on Win7 pw.println("IconResource=" + powerFolderFile.getAbsolutePath() + ",0"); pw.println("[ViewState]"); pw.println("Mode="); pw.println("Vid="); pw.println("FolderType=Generic"); pw.flush(); // Hide the files setAttributesOnWindows(desktopIniFile, true, true); setAttributesOnWindows(directory, null, true); // #2047: Now need to set folder as system for desktop.ini to // work. // makeSystemOnWindows(desktopIniFile); } catch (IOException e) { log.log(Level.WARNING, "Problem writing Desktop.ini file(s). " + e); } finally { if (pw != null) { try { pw.close(); } catch (Exception e) { // Ignore } } } } else if (iniExists && !usePfIcon) { // Need to remove desktop ini. desktopIniFile.delete(); setAttributesOnWindows(directory, null, false); } } /** * Method to remove the desktop ini if it exists * * @param directory */ public static void deleteDesktopIni(File directory) { // Look for a desktop ini in the folder. File desktopIniFile = new TFile(directory, DESKTOP_INI_FILENAME); boolean iniExists = desktopIniFile.exists(); if (iniExists) { desktopIniFile.delete(); setAttributesOnWindows(directory, null, false); } } /** * Scans a directory and gets full size of all files and count of files. * * @param directory * @return the size in byte of the directory [0] and count of files [1]. */ public static Long[] calculateDirectorySizeAndCount(File directory) { return calculateDirectorySizeAndCount0(directory, 0); } private static Long[] calculateDirectorySizeAndCount0(File directory, int depth) { // Limit evil recursive symbolic links. if (depth == 100) { return new Long[]{0L, 0L}; } File[] files = directory.listFiles(); if (files == null) { return new Long[]{0L, 0L}; } long sum = 0; long count = 0; for (File file : files) { if (file.isDirectory()) { Long[] longs = calculateDirectorySizeAndCount0(file, depth + 1); sum += longs[0]; count += longs[1]; } else { sum += file.length(); count++; } } return new Long[]{sum, count}; } /** * Zips the file * * @param file * the file to zip * @param zipfile * the zip file * @throws IOException * @throws IllegalArgumentException */ public static void zipFile(File file, File zipfile) throws IOException { // Check that the directory is a directory, and get its contents if (!file.isFile()) { throw new IllegalArgumentException("Not a file: " + file); } ZipOutputStream out = new ZipOutputStream( new TFileOutputStream(zipfile)); FileInputStream in = new FileInputStream(file); // Stream to read // file ZipEntry entry = new ZipEntry(file.getName()); // Make a ZipEntry out.putNextEntry(entry); // Store entry int bytesRead; byte[] buffer = new byte[4096]; // Create a buffer for copying while ((bytesRead = in.read(buffer)) != -1) { out.write(buffer, 0, bytesRead); } in.close(); out.close(); } /** * Zip the contents of the directory, and save it in the zipfile * * @param dir * @param zipfile * @throws IOException * @throws IllegalArgumentException */ public static void zipDirectory(File dir, File zipfile) throws IOException { // Check that the directory is a directory, and get its contents if (!dir.isDirectory()) { throw new IllegalArgumentException("Not a directory: " + dir); } String[] entries = dir.list(); byte[] buffer = new byte[4096]; // Create a buffer for copying ZipOutputStream out = new ZipOutputStream( new TFileOutputStream(zipfile)); for (String entry1 : entries) { File f = new TFile(dir, entry1); if (f.isDirectory()) { continue;// Ignore directory } FileInputStream in = new FileInputStream(f); // Stream to read // file ZipEntry entry = new ZipEntry(f.getPath()); // Make a ZipEntry out.putNextEntry(entry); // Store entry int bytesRead; while ((bytesRead = in.read(buffer)) != -1) { out.write(buffer, 0, bytesRead); } in.close(); } out.close(); } /** * @param file * @param directory * @return true if a file in inside a directory. */ public static boolean isFileInDirectory(File file, File directory) { Reject.ifTrue(file == null || directory == null, "File and directory may not be null"); File fileParent = file.getParentFile(); String fileParentPath; if (fileParent == null) { fileParentPath = File.separator; } else { fileParentPath = fileParent.getAbsolutePath(); } String directoryPath = directory.getAbsolutePath(); if (log.isLoggable(Level.FINER)) { log.finer("File parent: " + fileParentPath); log.finer("Directory: " + directoryPath); } return fileParentPath.startsWith(directoryPath); } /** * Removes invalid characters from the filename. * * @param filename * @return */ public static String removeInvalidFilenameChars(String filename) { String invalidChars = "/\\:*?\"<>|"; for (int i = 0; i < invalidChars.length(); i++) { char c = invalidChars.charAt(i); while (filename.indexOf(c) != -1) { int index = filename.indexOf(c); filename = filename.substring(0, index) + filename.substring(index + 1, filename.length()); } } return filename; } /** * #2467: Encode URL in filename by substituting illegal chars with legal * one. * * @param url * @return */ public static String encodeURLinFilename(String url) { url = url.replace("://", "___"); url = url.replace("/", "_"); url = url.replace(":", "_"); return "_s_" + url + '_'; // url = url.replace("//", "="); // url = url.replace(":", ";"); // return "$s$" + url + '$'; } /** * #2467: Decode URL from filename by substituting chars back. * * @param filename * @return the url */ public static String decodeURLFromFilename(String filename) { if (!filename.contains("_s_")) { return null; } int start = filename.indexOf("_s_"); int endURL = filename.lastIndexOf("_"); if (start < 0 || endURL < 0) { return null; } String url = filename.substring(start + 3, endURL); url = url.replace("___", "://"); // GUESS try { new URL(url.replace("_", ":")); url = url.replace("_", ":"); } catch (Exception e) { url = url.replace("_", "/"); } return url; } /** * Searches and takes care that this directory is new and not yet existing. * If dir already exists with the same raw name it appends (1), (2), and so * on until it finds an non-existing sub directory. DOES NOT try to remove * ILLEGAL characters from * <p> * * @param baseDir * @param rawName * the raw name of the directory. is it NOT guranteed that it * will/can be named like this. if illegal characters should be * removed * @return the directory that is guranteed to be NEW and EMPTY. */ public static File createEmptyDirectory(File baseDir, String rawName) { Reject.ifNull(baseDir, "Base dir is null"); Reject.ifBlank(rawName, "Raw name is null"); String canName = FileUtils.removeInvalidFilenameChars(rawName); File candidate = new TFile(baseDir, canName); int suffix = 2; while (candidate.exists()) { candidate = new TFile(baseDir, canName + " (" + suffix + ')'); suffix++; if (suffix > 1000) { throw new IllegalStateException( "Unable to find empty directory. Tried " + candidate); } } candidate.mkdirs(); return candidate; } /** * Methods does two things: 1. Removes all invalid characters from the raw * name and 2. searches and takes care that this file is new and not yet * existing. If file already exists with the same raw name it appends (1), * (2), and so on until it finds an non-existing file. * <p> * * @param baseDir * @param rawName * the raw name of the file. is it NOT guranteed that it will/can * be named like this. * @return the file that is guranteed to be NOT EXISTING yet. */ public static File findNonExistingFile(File baseDir, String rawName) { Reject.ifNull(baseDir, "Base dir is null"); Reject.ifBlank(rawName, "Raw name is null"); String name = removeInvalidFilenameChars(rawName); File candidate = new TFile(baseDir, name); int suffix = 2; while (candidate.exists()) { candidate = new TFile(baseDir, name + " (" + suffix + ')'); suffix++; } return candidate; } /** * Helper method to perform hashing on a file. * * @param file * @param digest * the MessageDigest to use, MUST be in initial state - aka * either newly created or being reseted. * @param listener * @return the result of the hashing, usually size 16. * @throws IOException * if the file was not found or an error occured while reading. * @throws InterruptedException * if this thread got interrupted, this can be used to cancel a * ongoing hashing operation. */ public static byte[] digest(File file, MessageDigest digest, ProgressListener listener) throws IOException, InterruptedException { FileInputStream in = new FileInputStream(file); try { byte[] buf = new byte[BYTE_CHUNK_SIZE]; long size = file.length(); long pos = 0; int read; while ((read = in.read(buf)) > 0) { if (Thread.interrupted()) { throw new InterruptedException(); } digest.update(buf, 0, read); pos += read; if (listener != null) { listener.progressReached(pos * 100.0 / size); } } return digest.digest(); } finally { in.close(); } } /** * See if 'child' is a subdirectory of 'parent', recursively. * * @param parent * @param targetChild * @return */ public static boolean isSubdirectory(File parent, File targetChild) { if (parent.isDirectory() && targetChild.isDirectory()) { for (File child : parent.listFiles()) { if (child.isDirectory()) { if (child.equals(targetChild)) { return true; } if (isSubdirectory(child, targetChild)) { return true; } } } return false; } else { throw new IllegalArgumentException("Can conly compare directories."); } } /** * This method builds a real File from a base file (directory) and a * DiskItem relativeName. relativeNames are always unix separators ('/') so * this method ensures that the file is built using the correct underlying * OS separators. * * @param base * a base directory File * @param relativeName * the DiskItem relativeName, like bob/dir/sub * @return */ public static File buildFileFromRelativeName(File base, String relativeName) { Reject.ifNull(base, "Need a base directory"); Reject.ifNull(relativeName, "RelativeName required"); if (relativeName.indexOf('/') == -1) { return new TFile(base, relativeName); } else { String[] parts = relativeName.split("/"); File f = base; for (String part : parts) { f = new TFile(f, part); } return f; } } /** * Do not scan POWERFOLDER_SYSTEM_SUBDIR (".PowerFolder"). * * @param file * Guess what * @param foInfo * Guess what * @return true if file scan is allowed */ public static boolean isScannable(File file, FolderInfo foInfo) { return isScannable(file.getPath(), foInfo); } /** * Do not scan POWERFOLDER_SYSTEM_SUBDIR (".PowerFolder"). * * @param filePath * Guess what * @param foInfo * Guess what * @return true if file scan is allowed */ public static boolean isScannable(String filePath, FolderInfo foInfo) { if (filePath.endsWith(Constants.ATOMIC_COMMIT_TEMP_TARGET_DIR)) { return false; } if (filePath.endsWith("Icon\r")) { return false; } int firstSystemDir = filePath .indexOf(Constants.POWERFOLDER_SYSTEM_SUBDIR); if (firstSystemDir < 0) { return true; } if (foInfo.isMetaFolder()) { // MetaFolders are in the POWERFOLDER_SYSTEM_SUBDIR of the parent, // like // C:\Users\Harry\PowerFolders\1765X\.PowerFolder\meta\xyz // So look after the '.PowerFolder\meta' part int metaDir = filePath.indexOf(Constants.METAFOLDER_SUBDIR, firstSystemDir); if (metaDir >= 0) { // File is somewhere in the metaFolder file structure. // Make sure we are not in the metaFolder's system subdir. int secondSystemDir = filePath.indexOf( Constants.POWERFOLDER_SYSTEM_SUBDIR, metaDir + Constants.METAFOLDER_SUBDIR.length()); return secondSystemDir < 0; } } // In system subdirectory, so do not scan. return false; } /** * @param base * @return * @throws IllegalArgumentException */ public static boolean hasContents(File base) { Reject.ifNull(base, "Base is null"); Reject.ifFalse(base.isDirectory(), "Base is not folder"); String[] contents = base.list(new FilenameFilter() { public boolean accept(File dir, String name) { if (name.equals(Constants.POWERFOLDER_SYSTEM_SUBDIR)) { // Don't care about our .PowerFolder files, just the user's // stuff. return false; } return true; } }); return contents != null && contents.length > 0; } /** * Does a directory have any files, recursively? This ignores the * .PowerFolder dir. * * @param base * @return * @throws IllegalArgumentException */ public static boolean hasFiles(File base) { Reject.ifNull(base, "Base is null"); Reject.ifFalse(base.isDirectory(), "Base is not folder"); return hasFilesInternal(base, 0); } private static boolean hasFilesInternal(File dir, int depth) { if (depth > 100) { // Smells fishy. Should not be this deep into the structure. } if (dir.getName().equals(Constants.POWERFOLDER_SYSTEM_SUBDIR)) { // Don't care about our .PowerFolder files, just the user's stuff. return false; } for (File file : dir.listFiles()) { if (file.isDirectory()) { // TODO THIS IS SLOW if (hasFilesInternal(file, depth + 1)) { // A subdirectory has a file; we're out of here. return true; } } else { // We got one! return true; } } // No files here. return false; } }