001/* 002 * Syncany, www.syncany.org 003 * Copyright (C) 2011-2016 Philipp C. Heckel <philipp.heckel@gmail.com> 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU General Public License as published by 007 * the Free Software Foundation, either version 3 of the License, or 008 * (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU General Public License for more details. 014 * 015 * You should have received a copy of the GNU General Public License 016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 017 */ 018package org.syncany.util; 019 020import java.io.File; 021import java.io.FileInputStream; 022import java.io.IOException; 023import java.io.RandomAccessFile; 024import java.nio.channels.FileLock; 025import java.nio.file.Files; 026import java.nio.file.InvalidPathException; 027import java.nio.file.LinkOption; 028import java.nio.file.Path; 029import java.nio.file.Paths; 030import java.nio.file.attribute.DosFileAttributes; 031import java.security.MessageDigest; 032import java.security.NoSuchAlgorithmException; 033import java.text.DecimalFormat; 034 035/** 036 * A file utility class 037 * 038 * @author Philipp C. Heckel (philipp.heckel@gmail.com) 039 */ 040public class FileUtil { 041 public static String getRelativePath(File base, File file) { 042 return removeTrailingSlash(base.toURI().relativize(file.toURI()).getPath()); 043 } 044 045 public static String getRelativeDatabasePath(File base, File file) { 046 String relativeFilePath = getRelativePath(base, file); 047 return getDatabasePath(relativeFilePath); 048 } 049 050 public static String getDatabasePath(String filePath) { 051 // Note: This is more important than it seems. Unix paths may contain backslashes 052 // so that 'black\white.jpg' is a perfectly valid file path. Windows file names 053 // may never contain backslashes, so that '\' can be safely transformed to the 054 // '/'-separated database path! 055 056 if (EnvironmentUtil.isWindows()) { 057 return filePath.toString().replaceAll("\\\\", "/"); 058 } 059 else { 060 return filePath; 061 } 062 } 063 064 public static String removeTrailingSlash(String filename) { 065 if (filename.endsWith("/")) { 066 return filename.substring(0, filename.length() - 1); 067 } 068 else { 069 return filename; 070 } 071 } 072 073 public static File getCanonicalFile(File file) { 074 try { 075 return file.getCanonicalFile(); 076 } 077 catch (IOException ex) { 078 return file; 079 } 080 } 081 082 public static byte[] createChecksum(File filename, String digestAlgorithm) throws NoSuchAlgorithmException, IOException { 083 FileInputStream fis = new FileInputStream(filename); 084 085 byte[] buffer = new byte[1024]; 086 MessageDigest complete = MessageDigest.getInstance(digestAlgorithm); 087 int numRead; 088 089 do { 090 numRead = fis.read(buffer); 091 if (numRead > 0) { 092 complete.update(buffer, 0, numRead); 093 } 094 } while (numRead != -1); 095 096 fis.close(); 097 return complete.digest(); 098 } 099 100 public static boolean isFileLocked(File file) { 101 if (!file.exists()) { 102 return false; 103 } 104 105 if (file.isDirectory()) { 106 return false; 107 } 108 109 if (isSymlink(file)) { 110 return false; 111 } 112 113 RandomAccessFile randomAccessFile = null; 114 boolean fileLocked = false; 115 116 try { 117 // Test 1 missing permissions or locked file parts. If File is not readable == locked 118 randomAccessFile = new RandomAccessFile(file, "r"); 119 randomAccessFile.close(); 120 } 121 catch (Exception e) { 122 fileLocked = true; 123 } 124 125 if (!fileLocked && file.canWrite()) { 126 try { 127 // Test 2:Locked file parts 128 randomAccessFile = new RandomAccessFile(file, "rw"); 129 130 // Test 3: Set lock and release it again 131 FileLock fileLock = randomAccessFile.getChannel().tryLock(); 132 133 if (fileLock == null) { 134 fileLocked = true; 135 } 136 else { 137 try { 138 fileLock.release(); 139 } 140 catch (Exception e) { /* Nothing */ 141 } 142 } 143 } 144 catch (Exception e) { 145 fileLocked = true; 146 } 147 148 if (randomAccessFile != null) { 149 try { 150 randomAccessFile.close(); 151 } 152 catch (IOException e) { /* Nothing */ 153 } 154 } 155 } 156 157 return fileLocked; 158 } 159 160 public static boolean isSymlink(File file) { 161 return Files.isSymbolicLink(Paths.get(file.getAbsolutePath())); 162 } 163 164 public static String readSymlinkTarget(File file) { 165 try { 166 return Files.readSymbolicLink(Paths.get(file.getAbsolutePath())).toString(); 167 } 168 catch (IOException e) { 169 return null; 170 } 171 } 172 173 public static void createSymlink(String targetPathStr, File symlinkFile) throws Exception { 174 Path targetPath = Paths.get(targetPathStr); 175 Path symlinkPath = Paths.get(symlinkFile.getPath()); 176 177 Files.createSymbolicLink(symlinkPath, targetPath); 178 } 179 180 public static String dosAttrsToString(DosFileAttributes dosFileAttributes) { 181 return LimitedDosFileAttributes.toString(dosFileAttributes); 182 } 183 184 public static LimitedDosFileAttributes dosAttrsFromString(String dosFileAttributes) { 185 return new LimitedDosFileAttributes(dosFileAttributes); 186 } 187 188 /** 189 * Replaces the <code>exists()</code> method in the <code>File</code> class by taking symlinks into account. 190 * The method returns <code>true</code> if the file exists, <code>false</code> otherwise. 191 * 192 * <p>Note: The method returns <code>true</code>, if a symlink exists, but points to a 193 * non-existing target. This behavior is different from the classic 194 * {@link #exists(File) exists()}-method in the <code>File</code> class. 195 * 196 * @param file A file 197 * @return Returns <code>true</code> if a file exists (even if its symlink target does not), <code>false</code> otherwise 198 */ 199 public static boolean exists(File file) { 200 try { 201 return Files.exists(Paths.get(file.getAbsolutePath()), LinkOption.NOFOLLOW_LINKS); 202 } 203 catch (InvalidPathException e) { 204 return false; 205 } 206 } 207 208 public static boolean isDirectory(File file) { 209 try { 210 return Files.isDirectory(Paths.get(file.getAbsolutePath()), LinkOption.NOFOLLOW_LINKS); 211 } 212 catch (InvalidPathException e) { 213 return false; 214 } 215 } 216 217 public static String formatFileSize(long size) { 218 if (size <= 0) { 219 return "0"; 220 } 221 222 final String[] units = new String[] { "B", "KB", "MB", "GB", "TB" }; 223 int digitGroups = (int) (Math.log10(size) / Math.log10(1024)); 224 225 return new DecimalFormat("#,##0.#").format(size / Math.pow(1024, digitGroups)) + " " + units[digitGroups]; 226 } 227}