/*
* PhoneGap is available under *either* the terms of the modified BSD license *or* the
* MIT License (2008). See http://opensource.org/licenses/alphabetical for full text.
*
* Copyright (c) 2005-2010, Nitobi
* Copyright (c) 2010-2011, IBM Corporation
*/
package com.phonegap.file;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import javax.microedition.io.Connector;
import javax.microedition.io.file.FileConnection;
import javax.microedition.io.file.FileSystemRegistry;
import net.rim.device.api.io.FileNotFoundException;
import net.rim.device.api.io.IOUtilities;
import net.rim.device.api.io.MIMETypeAssociations;
import net.rim.device.api.system.Application;
import com.phonegap.PhoneGapExtension;
import com.phonegap.util.Logger;
/**
* Contains file utility methods.
*/
public class FileUtils {
public static final String FILE_SEPARATOR = System.getProperty("file.separator");
public static final String LOCAL_PROTOCOL = "local:///";
private static final String APP_TMP_DIR = "tmp" + PhoneGapExtension.getAppID();
/**
* Reads file as byte array.
* @param filePath Full path of the file to be read
* @param mode One of Connector.READ, READ_WRITE, WRITE
* @return file content as a byte array
*/
public static byte[] readFile(String filePath, int mode) throws FileNotFoundException, IOException {
byte[] blob = null;
DataInputStream dis = null;
try {
dis = openDataInputStream(filePath, mode);
blob = IOUtilities.streamToBytes(dis);
}
finally {
try {
if (dis != null) dis.close();
}
catch (IOException ignored) {
}
}
return blob;
}
/**
* Utility function to open a DataInputStream from a file path.
*
* A file can be referenced with the following protocols:
* - System.getProperty("fileconn.dir.*")
* - local:/// references files bundled with the application
*
* @param filePath The full path to the file to open
* @param mode One of Connector.READ, READ_WRITE, WRITE
* @return Handle to the DataInputStream
*/
private static DataInputStream openDataInputStream(final String filePath, int mode) throws FileNotFoundException, IOException {
FileConnection fconn = null;
DataInputStream dis = null;
try {
if (filePath.startsWith(LOCAL_PROTOCOL)) {
// Remove local:// from filePath but leave a leading /
dis = new DataInputStream(Application.class.getResourceAsStream(filePath.substring(8)));
}
else {
fconn = (FileConnection)Connector.open(filePath, mode);
if (!fconn.exists()) {
throw new FileNotFoundException(filePath + " not found");
}
dis = fconn.openDataInputStream();
}
if (dis == null) {
throw new FileNotFoundException(filePath + " not found");
}
}
finally {
try {
if (fconn != null) fconn.close();
}
catch (IOException ignored) {
}
}
return dis;
}
/**
* Writes data to the specified file.
*
* @param filePath
* Full path of file to be written to
* @param data
* Data to be written
* @param position
* Position at which to begin writing
* @return length of data written to file
* @throws SecurityException
* if the application does not have write access to the file
* @throws IOException
* if directory structure does not exist or an unspecified error
* occurs
*/
public static int writeFile(String filePath, byte[] data, int position)
throws SecurityException, IOException {
FileConnection fconn = null;
OutputStream os = null;
try {
fconn = (FileConnection) Connector.open(filePath,
Connector.READ_WRITE);
if (!fconn.exists()) {
fconn.create();
}
os = fconn.openOutputStream(position);
os.write(data);
}
finally {
try {
if (os != null)
os.close();
if (fconn != null)
fconn.close();
}
catch (IOException ignored) {
}
}
return data.length;
}
/**
* Deletes the specified file or directory from file system. If the
* specified path is a directory, the deletion is recursive.
*
* @param path
* full path of file or directory to be deleted
* @throws IOException
*/
public static void delete(String path) throws IOException {
FileConnection fconn = null;
try {
fconn = (FileConnection)Connector.open(path, Connector.READ_WRITE);
if (fconn.exists()) {
// file
if (!fconn.isDirectory()) {
fconn.delete();
Logger.log(FileUtils.class.getName() + ": " + path + " deleted");
}
// directory
else {
if (!path.endsWith(FILE_SEPARATOR)) {
path += FILE_SEPARATOR;
}
// recursively delete directory contents
Enumeration contents = fconn.list("*", true);
if (contents.hasMoreElements()) {
fconn.close();
while (contents.hasMoreElements()) {
delete(path + contents.nextElement().toString());
}
fconn = (FileConnection)Connector.open(path, Connector.READ_WRITE);
}
// now delete this directory
fconn.delete();
Logger.log(FileUtils.class.getName() + ": " + path + " deleted");
}
}
}
finally {
try {
if (fconn != null) fconn.close();
}
catch (IOException ignored) {
}
}
}
/**
* Creates a directory. Directories in the specified path are not created
* recursively. If the directory already exists, no action is taken.
*
* @param dirPath
* full path of directory to create
* @throws IOException
* if the target file system is not accessible, or an
* unspecified error occurs
*/
public static void mkdir(String dirPath) throws IOException {
FileConnection fconn = null;
try {
fconn = (FileConnection)Connector.open(dirPath);
if (fconn.isDirectory()) {
// nothing to do
return;
}
fconn.mkdir();
}
finally {
try {
if (fconn != null) fconn.close();
}
catch (IOException ignored) {
}
}
}
/**
* Copies a file or directory to a new location. If copying a directory, the
* entire contents of the directory are copied recursively.
*
* @param srcPath
* the full path of the file or directory to be copied
* @param parent
* the full path of the target directory to which the file or
* directory should be copied
* @param newName
* the new name of the file or directory
* @throws IllegalArgumentException
* if an invalid source or destination path is provided
* @throws FileNotFoundException
* if the source path cannot be found on the file system
* @throws SecurityException
* if unable to create the new file or directory specified by
* destination path
* @throws IOException
* if an attempt is made to copy the contents of a directory
* into itself, or if the source and destination paths are
* identical, or if a general error occurs
*/
public static void copy(String srcPath, String parent, String newName)
throws IllegalArgumentException, FileNotFoundException,
SecurityException, IOException {
FileConnection src = null;
FileConnection dst = null;
try {
src = (FileConnection)Connector.open(srcPath, Connector.READ_WRITE);
// ensure source exists
if (!src.exists()) {
throw new FileNotFoundException("Path not found: " + srcPath);
}
// ensure target parent directory exists
if (!isDirectory(parent)) {
throw new FileNotFoundException("Target directory not found: " + parent);
}
// form full destination path
if (!parent.endsWith(FileUtils.FILE_SEPARATOR)) {
parent += FileUtils.FILE_SEPARATOR;
}
String dstPath = parent + newName;
// source is a directory
if (src.isDirectory()) {
// target should also be directory; append file separator
if (!dstPath.endsWith(FILE_SEPARATOR)) {
dstPath += FILE_SEPARATOR;
}
// can't copy directory into itself
// file:///SDCard/tmp/ --> file:///SDCard/tmp/tmp/ ==> NO!
// file:///SDCard/tmp/ --> file:///SDCard/tmp/ ==> NO!
// file:///SDCard/tmp/ --> file:///SDCard/tmp2/ ==> OK
String srcURL = src.getURL();
if (dstPath.startsWith(srcURL)) {
throw new IOException("Cannot copy directory into itself.");
}
// create the destination directory
mkdir(dstPath);
// recursively copy directory contents
Enumeration contents = src.list("*", true);
if (contents.hasMoreElements()) {
src.close();
while (contents.hasMoreElements()) {
String name = contents.nextElement().toString();
copy(srcURL + name, dstPath, name);
}
}
}
// source is a file
else {
// can't copy file onto itself
if (dstPath.equals(srcPath)) {
throw new IOException("Cannot copy file onto itself.");
}
dst = (FileConnection) Connector.open(dstPath, Connector.READ_WRITE);
// replace existing file, but not directory
if (dst.exists()) {
if (dst.isDirectory()) {
throw new IOException(
"Cannot overwrite existing directory.");
}
else {
dst.delete();
}
}
dst.create();
// copy the contents - wish there was a better way
InputStream is = null;
OutputStream os = null;
try {
is = src.openInputStream();
os = dst.openOutputStream();
byte[] buf = new byte[1024];
int len;
while ((len = is.read(buf)) > 0) {
os.write(buf, 0, len);
}
}
finally {
if (is != null) is.close();
if (os != null) os.close();
}
}
}
finally {
try {
if (src != null) src.close();
if (dst != null) dst.close();
}
catch (IOException ignored) {
}
}
}
/**
* Creates an temporary directory for the application. The temporary
* directory is created in the following location:
* <code><root>/tmpGUID/</code> where <code><root>/</code>
* is the path of the writable directory on the file system (could be the SD
* card, if present, or the root file system on internal storage); and
* <code>tmpGUID/</code> is a application temporary directory that is
* created using the unique application GUID. If the application temporary
* directory does not exist, invoking this method will create it.
* <em>NOTE:</em> The <code><root>/tmpGUID/</code> application
* temporary directory and all its contents are deleted upon application
* exit.
*
* @return full path name of the application temporary directory
* @throws IOException
* if there are no file systems mounted, or an unspecified error
* occurs
*/
public static String createApplicationTempDirectory() throws IOException {
// <root>/tmpGUID/
String tmpDir = getApplicationTempDirPath();
mkdir(tmpDir);
return tmpDir;
}
/**
* Creates a temporary directory on the writable storage area of the file
* system. The temporary directory is created in the following location:
* <code><root>/tmpGUID/dirName/</code> where
* <code><root>/tmpGUID/</code> is an application temporary
* directory that is created using the unique application GUID; and
* <code>dirName/</code> is an optional directory name to create beneath the
* application temporary directory. If the application temporary directory
* does not exist, invoking this method will create it. <em>NOTE:</em> The
* <code><root>/tmpGUID/</code> application temporary directory
* and all its contents are deleted upon application exit.
*
* @param dirName
* name of directory to be created beneath the application
* temporary directory
* @return full path name of the directory that was created
* @throws IOException
* if there are no file systems mounted, or an unspecified error
* occurs
*/
public static String createTempDirectory(String dirName) throws IOException {
// create the application temp directory
String tmpDir = createApplicationTempDirectory();
// create specified sub-directory as "<root>/tmpGUID/dirName/"
dirName = (dirName == null) ? "" : dirName.trim();
if (dirName.length() > 0) {
if (!dirName.endsWith(FILE_SEPARATOR)) {
dirName += FILE_SEPARATOR;
}
tmpDir += dirName;
mkdir(tmpDir);
}
return tmpDir;
}
/**
* Attempts to delete the application temporary directory and all contents.
* The application temporary directory is:
* <code><root>/tmpGUID/</code>, where <code><root></code> is
* the file system root (could be the SD card or internal storage); and
* <code>tmpGUID</code> is the application temporary directory that is
* created using the unique application GUID. <em>NOTE:</em> The
* <code>tmpGUID</code> application temporary directory and all
* sub-directories are deleted upon application exit.
*
* @throws IOException
* if an unspecified error occurs
*/
public synchronized static void deleteApplicationTempDirectory()
throws IOException {
String tmpDir = getApplicationTempDirPath();
delete(tmpDir);
}
/**
* Returns the full path of the application temporary directory. The path
* points to the following location: <code><root>/tmpGUID/</code>
* where <code><root>/</code> is the path of the writable directory on
* the file system (could be the SD card, if present, or the root file system
* on internal storage); and <code>tmpGUID/</code> is a application temporary
* directory that is created using the unique application GUID. The
* directory may not exist. Invoke
* <code>createApplicationTempDirectory</code> to create it.
*
* @return the full path name of the application temporary directory
*/
public static String getApplicationTempDirPath() {
return getFileSystemRoot() + APP_TMP_DIR + FILE_SEPARATOR;
}
/**
* Returns the full path of a root file system. Will return the path of the
* SD card first, if it exists, or the root file system located on internal
* storage.
*
* @return full path that can be used to store files
*/
public static String getFileSystemRoot() {
String root = null;
String sdcard = getSDCardPath();
// retrieve root list
Enumeration e = FileSystemRegistry.listRoots();
while (e.hasMoreElements()) {
root = "file:///" + (String) e.nextElement();
// system directory won't be writable
if (root.endsWith("system/")) {
continue;
}
// prefer the SDCard
else if (root.equals(sdcard)) {
break;
}
}
return root;
}
/**
* Returns the full path name to external storage (SD card, e.g.
* file:///SDCard/).
*
* @return full path name to the external storage (SD card)
*/
public static String getSDCardPath() {
return System.getProperty("fileconn.dir.memorycard");
}
/**
* Returns the full path name of the user directory located on internal
* storage (e.g. file:///store/home/user/).
*
* @return full path name of the user directory
*/
public static String getUserPath() {
// grab the music folder
String musicDir = System.getProperty("fileconn.dir.music");
// ignore trailing '/'
int i = musicDir.lastIndexOf('/', musicDir.length() - 2);
// strip off the last directory
return musicDir.substring(0, i + 1);
}
/**
* Returns the available size of the file system that the path resides on.
*
* @param path
* full path of a file system entry
* @return available size, in bytes, of the root file system
* @throws IllegalArgumentException
* if path is invalid
* @throws IOException
* if an error occurs
*/
public static long availableSize(String path)
throws IllegalArgumentException, IOException {
long availableSize = 0;
FileConnection fconn = null;
try {
fconn = (FileConnection) Connector.open(path);
availableSize = fconn.availableSize();
}
finally {
try {
if (fconn != null)
fconn.close();
}
catch (IOException ignored) {
}
}
return availableSize;
}
/**
* Determines if the specified file system path exists.
* @param path full path of file or directory
* @return true if the file or directory exists
*/
public static boolean exists(String path) {
boolean exists = false;
FileConnection fconn = null;
try {
fconn = (FileConnection)Connector.open(path);
exists = fconn.exists();
}
catch (IllegalArgumentException e) {
Logger.log(FileUtils.class.getName() + ": " + e);
}
catch (IOException e) {
Logger.log(FileUtils.class.getName() + ": " + e);
}
finally {
try {
if (fconn != null) fconn.close();
}
catch (IOException ignored) {
}
}
return exists;
}
/**
* Determines if the specified file system path refers to a directory.
* @param path full path of file or directory
* @return true if the file path exists, is accessible, and is a directory
*/
public static boolean isDirectory(String path) {
boolean isDirectory = false;
FileConnection fconn = null;
try {
fconn = (FileConnection)Connector.open(path);
isDirectory = fconn.isDirectory();
}
catch (IllegalArgumentException e) {
Logger.log(FileUtils.class.getName() + ": " + e);
}
catch (IOException e) {
Logger.log(FileUtils.class.getName() + ": " + e);
}
finally {
try {
if (fconn != null) fconn.close();
}
catch (IOException ignored) {
}
}
return isDirectory;
}
/**
* Lists the contents of a directory. Lists both files and sub-directories.
*
* @param path
* full path of the directory to list
* @return Enumeration containing names of files and sub-directories.
* @throws FileNotFoundException
* if path is not found
* @throws IOException
* if an error occurs
*/
public static Enumeration listDirectory(String path)
throws FileNotFoundException, IOException {
FileConnection fconn = null;
Enumeration listing = null;
try {
fconn = (FileConnection) Connector.open(path);
if (!fconn.exists()) {
throw new FileNotFoundException(path + " does not exist.");
}
listing = fconn.list();
}
finally {
try {
if (fconn != null)
fconn.close();
}
catch (IOException ignored) {
}
}
return listing;
}
public static File getFileProperties(String filePath) throws FileNotFoundException {
File file = new File(stripSeparator(filePath));
FileConnection fconn = null;
try {
fconn = (FileConnection)Connector.open(filePath);
if (!fconn.exists()) {
throw new FileNotFoundException();
}
file.setLastModifiedDate(fconn.lastModified());
file.setName(stripSeparator(fconn.getName()));
file.setType(MIMETypeAssociations.getMIMEType(filePath));
file.setSize(fconn.fileSize());
}
catch (IllegalArgumentException e) {
Logger.log(FileUtils.class.getName() + ": " + e);
}
catch (IOException e) {
Logger.log(FileUtils.class.getName() + ": " + e);
}
finally {
try {
if (fconn != null) fconn.close();
}
catch (IOException ignored) {
}
}
return file;
}
/**
* Strips the trailing slash from path names.
*
* @param path
* full or relative path name
* @return formatted path (without trailing slash)
*/
public static String stripSeparator(String path) {
int len = FILE_SEPARATOR.length();
while (path.endsWith(FILE_SEPARATOR)) {
path = path.substring(0, path.length() - len);
}
return path;
}
}