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}