001/**
002 * BioJava development code
003 *
004 * This code may be freely distributed and modified under the terms of the GNU
005 * Lesser General Public Licence. This should be distributed with the code. If
006 * you do not have a copy, see:
007 *
008 * http://www.gnu.org/copyleft/lesser.html
009 *
010 * Copyright for this code is held jointly by the individual authors. These
011 * should be listed in @author doc comments.
012 *
013 * For more information on the BioJava project and its aims, or to join the
014 * biojava-l mailing list, visit the home page at:
015 *
016 * http://www.biojava.org/
017 *
018 * Created on Feb 23, 2012 Created by Andreas Prlic
019 *
020 * @since 3.0.2
021 */
022package org.biojava.nbio.core.util;
023
024import java.io.File;
025import java.io.FileInputStream;
026import java.io.FileOutputStream;
027import java.io.IOException;
028import java.io.InputStream;
029import java.net.HttpURLConnection;
030import java.net.SocketTimeoutException;
031import java.net.URL;
032import java.net.URLConnection;
033import java.nio.channels.Channels;
034import java.nio.channels.FileChannel;
035import java.nio.channels.ReadableByteChannel;
036import java.nio.file.FileVisitResult;
037import java.nio.file.Files;
038import java.nio.file.Path;
039import java.nio.file.Paths;
040import java.nio.file.SimpleFileVisitor;
041import java.nio.file.attribute.BasicFileAttributes;
042
043import org.slf4j.Logger;
044import org.slf4j.LoggerFactory;
045
046public class FileDownloadUtils {
047
048        private static final Logger logger = LoggerFactory.getLogger(FileDownloadUtils.class);
049
050        /**
051         * Copy the content of file src to dst TODO since java 1.7 this is provided
052         * in java.nio.file.Files
053         *
054         * @param src
055         * @param dst
056         * @throws IOException
057         */
058        @SuppressWarnings("resource")
059        public static void copy(File src, File dst) throws IOException {
060
061                // Took following recipe from
062                // http://stackoverflow.com/questions/106770/standard-concise-way-to-copy-a-file-in-java
063                // The nio package seems to be the most efficient way to copy a file
064                FileChannel source = null;
065                FileChannel destination = null;
066
067                try {
068                        // we need the supress warnings here (the warning that the stream is not closed is harmless)
069                        // see http://stackoverflow.com/questions/12970407/does-filechannel-close-close-the-underlying-stream
070                        source = new FileInputStream(src).getChannel();
071                        destination = new FileOutputStream(dst).getChannel();
072                        destination.transferFrom(source, 0, source.size());
073                } finally {
074                        if (source != null) {
075                                source.close();
076                        }
077                        if (destination != null) {
078                                destination.close();
079                        }
080                }
081        }
082
083        /**
084         * Gets the file extension of a file, excluding '.'.
085         * If the file name has no extension the file name is returned.
086         * @param f a File
087         * @return The extension
088         */
089        public static String getFileExtension(File f) {
090                String fileName = f.getName();
091                String ext = "";
092                int mid = fileName.lastIndexOf(".");
093                ext = fileName.substring(mid + 1, fileName.length());
094                return ext;
095        }
096
097        /**
098         * Gets the file name up to and excluding the first
099         * '.' character. If there is no extension, the full filename
100         * is returned.
101         * @param f A file
102         * @return A possibly empty but non-null String.
103         */
104        public static String getFilePrefix(File f) {
105                String fileName = f.getName();
106                int mid = fileName.indexOf(".");
107                if (mid < 0) {
108                        return fileName;
109                }
110                return fileName.substring(0, mid);
111        }
112
113        /**
114         * Download the content provided at URL url and store the result to a local
115         * file, using a temp file to cache the content in case something goes wrong
116         * in download. A timeout of 60 seconds is hard-coded and 10 retries are attempted.
117         *
118         * @param url
119         * @param destination
120         * @throws IOException
121         */
122        public static void downloadFile(URL url, File destination) throws IOException {
123                int count = 0;
124                int maxTries = 10;
125                int timeout = 60000; //60 sec
126
127                File tempFile = File.createTempFile(getFilePrefix(destination), "." + getFileExtension(destination));
128
129                // Took following recipe from stackoverflow:
130                // http://stackoverflow.com/questions/921262/how-to-download-and-save-a-file-from-internet-using-java
131                // It seems to be the most efficient way to transfer a file
132                // See: http://docs.oracle.com/javase/7/docs/api/java/nio/channels/FileChannel.html
133                ReadableByteChannel rbc = null;
134                FileOutputStream fos = null;
135                while (true) {
136                        try {
137                                URLConnection connection = prepareURLConnection(url.toString(), timeout);
138                                connection.connect();
139                                InputStream inputStream = connection.getInputStream();
140
141                                rbc = Channels.newChannel(inputStream);
142                                fos = new FileOutputStream(tempFile);
143                                fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
144                                break;
145                        } catch (SocketTimeoutException e) {
146                                if (++count == maxTries) throw e;
147                        } finally {
148                                if (rbc != null) {
149                                        rbc.close();
150                                }
151                                if (fos != null) {
152                                        fos.close();
153                                }
154                        }
155                }
156
157                logger.debug("Copying temp file {} to final location {}", tempFile, destination);
158                copy(tempFile, destination);
159
160                // delete the tmp file
161                tempFile.delete();
162
163        }
164
165        /**
166         * Converts path to Unix convention and adds a terminating slash if it was
167         * omitted. 
168         *
169         * @param path original platform dependent path
170         * @return path in Unix convention
171         * @author Peter Rose
172         * @since 3.2
173         */
174        public static String toUnixPath(String path) {
175                String uPath = path;
176                if (uPath.contains("\\")) {
177                        uPath = uPath.replaceAll("\\\\", "/");
178                }
179                // this should be removed, it's need since "\" is added AtomCache code
180                if (uPath.endsWith("//")) {
181                        uPath = uPath.substring(0, uPath.length() - 1);
182                }
183                if (!uPath.endsWith("/")) {
184                        uPath = uPath + "/";
185                }
186                return uPath;
187        }
188
189        /**
190         * Expands ~ in paths to the user's home directory.
191         *
192         * <p>
193         * This does not work for some special cases for paths: Other users' homes
194         * (~user/...), and Tilde expansion within the path (/.../~/...). In these cases
195         *  the original argument is returned.
196         *
197         * @param file A filepath starting with a tilde
198         * @return An absolute path
199         */
200        public static String expandUserHome(String file) {
201                // replace any / with the proper separator (/ or \ for Linux and Windows respectively).
202                file = file.replaceAll("/", "\\"+File.separator); //The "\\" is to escape the separator if needed.
203                if (file.startsWith("~") && (file.length() == 1 || File.separator.equals(file.substring(1, 2)))) {
204                        file = System.getProperty("user.home") + file.substring(1);
205                }
206                return file;
207        }
208
209        /**
210         * Pings a HTTP URL. This effectively sends a HEAD request and returns
211         * <code>true</code> if the response code is in the 200-399 range.
212         *
213         * @param url The HTTP URL to be pinged.
214         * @param timeout The timeout in millis for both the connection timeout and
215         * the response read timeout. Note that the total timeout is effectively two
216         * times the given timeout.
217         * @return <code>true</code> if the given HTTP URL has returned response
218         * code 200-399 on a HEAD request within the given timeout, otherwise
219         * <code>false</code>.
220         * @author BalusC,
221         * http://stackoverflow.com/questions/3584210/preferred-java-way-to-ping-a-http-url-for-availability
222         */
223        public static boolean ping(String url, int timeout) {
224                //url = url.replaceFirst("https", "http"); // Otherwise an exception may be thrown on invalid SSL certificates.
225
226                try {
227                        HttpURLConnection connection = (HttpURLConnection) prepareURLConnection(url, timeout);
228                        connection.setRequestMethod("HEAD");
229                        int responseCode = connection.getResponseCode();
230                        return (200 <= responseCode && responseCode <= 399);
231                } catch (IOException exception) {
232                        return false;
233                }
234        }
235
236        /**
237         * Prepare {@link URLConnection} with customised timeouts.
238         *
239         * @param url The URL
240         * @param timeout The timeout in millis for both the connection timeout and
241         * the response read timeout. Note that the total timeout is effectively two
242         * times the given timeout.
243         *
244         * <p>
245         * Example of code.      <code>
246                 * UrlConnection conn = prepareURLConnection("http://www.google.com/", 20000);
247         * conn.connect();
248         * conn.getInputStream();
249         * </code>
250         * <p>
251         *
252         * <bold>NB. User should execute connect() method before getting input
253         * stream.</bold>
254         * @return
255         * @throws IOException
256         * @author Jacek Grzebyta
257         */
258        public static URLConnection prepareURLConnection(String url, int timeout) throws IOException {
259                URLConnection connection = new URL(url).openConnection();
260                connection.setReadTimeout(timeout);
261                connection.setConnectTimeout(timeout);
262                return connection;
263        }
264
265        /**
266         * Recursively delete a folder & contents
267         *
268         * @param dir directory to delete
269         */
270        public static void deleteDirectory(Path dir) throws IOException {
271                if(dir == null || !Files.exists(dir))
272                        return;
273                Files.walkFileTree(dir, new SimpleFileVisitor<Path>() {
274                @Override
275                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
276                    Files.delete(file);
277                    return FileVisitResult.CONTINUE;
278                }
279
280                @Override
281                public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException {
282                    if (e != null) {
283                        throw e;
284                    }
285                    Files.delete(dir);
286                    return FileVisitResult.CONTINUE;
287                }
288            });
289        }
290        /**
291         * Recursively delete a folder & contents
292         *
293         * @param dir directory to delete
294         */
295        public static void deleteDirectory(String dir) throws IOException {
296                deleteDirectory(Paths.get(dir));
297        }
298
299}