FileUtils.groovy

/*
 * Copyright (C) 2003-2014 eXo Platform SAS.
 *
 * This file is part of eXo Platform - Add-ons Manager.
 *
 * eXo Platform - Add-ons Manager is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 *
 * eXo Platform - Add-ons Manager software 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with eXo Platform - Add-ons Manager; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see <http://www.gnu.org/licenses/>.
 */
package org.exoplatform.platform.am.utils

import org.exoplatform.platform.am.ex.AddonsManagerException
import org.exoplatform.platform.am.ex.UnknownErrorException

import java.nio.channels.FileChannel

/**
 * Miscellaneous utilities
 */
class FileUtils {

  /**
   * Logger
   */
  private static final Logger LOG = Logger.getInstance()

  /**
   * Downloads a file following redirects if required
   * @param message the logging message to display
   * @param url The URL from which to download
   * @param destFile The file to populate
   * @throws IOException If there is an IO error
   */
  static downloadFile(String message, URL url, File destFile) throws IOException {
    downloadFile(message, url.toString(), destFile)
  }

  /**
   * Downloads a file following redirects if required
   * @param message the logging message to display
   * @param url The URL from which to download
   * @param destFile The file to populate
   * @throws AddonsManagerException If there is an error while transferring the file
   */
  static downloadFile(String message, String url, File destFile) throws AddonsManagerException {
    String originalUrl = url
    // Let's do it for each redirection
    while (url) {
      new URL(url).openConnection().with { URLConnection conn ->
        if (conn instanceof HttpURLConnection) {
          conn.instanceFollowRedirects = true
        }
        url = conn.getHeaderField("Location")
        // No more Location, let's download
        if (!url) {
          if (destFile.exists()) {
            LOG.debug("remoteFile lastModified : ${conn.lastModified}")
            LOG.debug("remoteFile size : ${conn.contentLength}")
            LOG.debug("destFile lastModified : ${destFile.lastModified()}")
            LOG.debug("destFile size : ${destFile.size()}")
          }
          // Same size and date more recent locally, don't touch it.
          if (destFile.exists() && conn.contentLength == destFile.size() && conn.lastModified <= destFile.lastModified()) {
            LOG.withStatusOK("File ${destFile.name} already up-to-date. Skipping download.")
            return
          }
          if (!message) {
            message = "Downloading ${originalUrl} to ${destFile}"
          }
          LOG.withStatus(message) {
            try {
              destFile.withOutputStream { out ->
                conn.inputStream.with { inp ->
                  out << inp
                  inp?.close()
                }
              }
            } catch (FileNotFoundException fnfe) {
              // AM-95 : Don't keep an empty/corrupted downloaded file
              if (destFile.exists()) {
                destFile.delete()
              }
              throw new UnknownErrorException("File not found at URL ${originalUrl}", fnfe)
            } catch (IOException ioe) {
              // AM-95 : Don't keep an empty/corrupted downloaded file
              if (destFile.exists()) {
                destFile.delete()
              }
              throw new UnknownErrorException("I/O error while downloading ${originalUrl}", ioe)
            }
          }
        }
      }
    }
  }

  /**
   * Copy a local file to another location using NIO
   * @param message the logging message to display
   * @param sourceFile the source file to copy
   * @param destFile where the file should be copied
   * @throws IOException If there is an IO error
   */
  static void copyFile(String message, File sourceFile, File destFile) throws IOException {
    copyFile(message, sourceFile, destFile, true)
  }

  /**
   * Copy a local file to another location using NIO
   * @param message the logging message to display
   * @param sourceFile the source file to copy
   * @param destFile where the file should be copied
   * @param warnIfOverride Display a warning if destFile already exists
   * @throws IOException If there is an IO error
   */
  static void copyFile(String message, File sourceFile, File destFile, boolean warnIfOverride) throws IOException {
    if (!destFile.exists()) {
      destFile.createNewFile();
    } else {
      LOG.debug("sourceFile lastModified : ${sourceFile.lastModified()}")
      LOG.debug("sourceFile size : ${sourceFile.size()}")
      LOG.debug("destFile lastModified : ${destFile.lastModified()}")
      LOG.debug("destFile size : ${destFile.size()}")
      // Same size and destFile date more recent, don't touch it.
      if (sourceFile.size() == destFile.size() && sourceFile.lastModified() <= destFile.lastModified()) {
        LOG.withStatusOK("Skipping copy of ${sourceFile.name}. File ${destFile.name} already up-to-date.")
        return
      }
      if (warnIfOverride) {
        LOG.warn("File ${destFile.name} already exists. Replacing it.")
      }
    }
    if (!message) {
      message = "Copying ${sourceFile} to ${destFile}"
    }
    LOG.withStatus(message) {
      FileChannel source = null;
      FileChannel destination = null;

      try {
        source = new FileInputStream(sourceFile).getChannel();
        destination = new FileOutputStream(destFile).getChannel();
        destination.transferFrom(source, 0, source.size());
      }
      finally {
        if (source != null) {
          source.close();
        }
        if (destination != null) {
          destination.close();
        }
      }
    }
  }

  /**
   * Create a directory and its required parents.
   * @param dirToCreate The directory to create
   * @throws IOException If an error occurs
   */
  static void mkdirs(File dirToCreate) throws IOException {
    if (!dirToCreate.mkdirs()) {
      throw new IOException("Unable to create directory ${dirToCreate}")
    }
  }

  static String extractFilename(String fullpath) {
    return fullpath.substring(fullpath.lastIndexOf('/') + 1, fullpath.length())
  }

  static String extractDirPath(String fullpath) {
    return fullpath.substring(0, fullpath.lastIndexOf('/'))
  }

  static String extractParentAndFilename(String fullpath) {
    if (fullpath.lastIndexOf("/") < 0) return fullpath
    String subPath = fullpath.substring(0, fullpath.lastIndexOf("/") -1)
    if (subPath.indexOf("/") < 0) {
      return fullpath
    } else {
      return fullpath.substring(subPath.lastIndexOf("/") + 1)
    }
  }

}