package org.apache.cordova.file;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public abstract class Filesystem {
public String name;
public interface ReadFileCallback {
public void handleData(InputStream inputStream, String contentType) throws IOException;
}
public static JSONObject makeEntryForPath(String path, String fsName, Boolean isDir)
throws JSONException {
return makeEntryForPath(path, fsName, isDir, null);
}
public static JSONObject makeEntryForPath(String path, String fsName, Boolean isDir, String nativeURL)
throws JSONException {
JSONObject entry = new JSONObject();
int end = path.endsWith("/") ? 1 : 0;
String[] parts = path.substring(0,path.length()-end).split("/");
String fileName = parts[parts.length-1];
entry.put("isFile", !isDir);
entry.put("isDirectory", isDir);
entry.put("name", fileName);
entry.put("fullPath", path);
// The file system can't be specified, as it would lead to an infinite loop,
// but the filesystem name can be.
entry.put("filesystemName", fsName);
// Backwards compatibility
entry.put("filesystem", "temporary".equals(fsName) ? 0 : 1);
if (nativeURL != null) {
entry.put("nativeURL", nativeURL);
}
return entry;
}
public static JSONObject makeEntryForURL(LocalFilesystemURL inputURL, Boolean isDir) throws JSONException {
return makeEntryForURL(inputURL, isDir, null);
}
public static JSONObject makeEntryForURL(LocalFilesystemURL inputURL, Boolean isDir, String nativeURL) throws JSONException {
return makeEntryForPath(inputURL.fullPath, inputURL.filesystemName, isDir, nativeURL);
}
abstract JSONObject getEntryForLocalURL(LocalFilesystemURL inputURL) throws IOException;
abstract JSONObject getFileForLocalURL(LocalFilesystemURL inputURL, String path,
JSONObject options, boolean directory) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException;
abstract boolean removeFileAtLocalURL(LocalFilesystemURL inputURL) throws InvalidModificationException, NoModificationAllowedException;
abstract boolean recursiveRemoveFileAtLocalURL(LocalFilesystemURL inputURL) throws FileExistsException, NoModificationAllowedException;
abstract JSONArray readEntriesAtLocalURL(LocalFilesystemURL inputURL) throws FileNotFoundException;
abstract JSONObject getFileMetadataForLocalURL(LocalFilesystemURL inputURL) throws FileNotFoundException;
public JSONObject getParentForLocalURL(LocalFilesystemURL inputURL) throws IOException {
LocalFilesystemURL newURL = new LocalFilesystemURL(inputURL.URL);
if (!("".equals(inputURL.fullPath) || "/".equals(inputURL.fullPath))) {
int end = inputURL.fullPath.endsWith("/") ? 1 : 0;
int lastPathStartsAt = inputURL.fullPath.lastIndexOf('/', inputURL.fullPath.length()-end)+1;
newURL.fullPath = newURL.fullPath.substring(0,lastPathStartsAt);
}
return getEntryForLocalURL(newURL);
}
protected LocalFilesystemURL makeDestinationURL(String newName, LocalFilesystemURL srcURL, LocalFilesystemURL destURL) {
// I know this looks weird but it is to work around a JSON bug.
if ("null".equals(newName) || "".equals(newName)) {
newName = srcURL.URL.getLastPathSegment();;
}
String newDest = destURL.URL.toString();
if (newDest.endsWith("/")) {
newDest = newDest + newName;
} else {
newDest = newDest + "/" + newName;
}
return new LocalFilesystemURL(newDest);
}
/* Read a source URL (possibly from a different filesystem, srcFs,) and copy it to
* the destination URL on this filesystem, optionally with a new filename.
* If move is true, then this method should either perform an atomic move operation
* or remove the source file when finished.
*/
JSONObject copyFileToURL(LocalFilesystemURL destURL, String newName,
Filesystem srcFs, LocalFilesystemURL srcURL, boolean move) throws IOException, InvalidModificationException, JSONException, NoModificationAllowedException, FileExistsException {
// This is "the hard way" -- transfer data between arbitrary filesystem urls/
// Gets an input stream from src, and writes its contents to an output stream
// from dest.
// First, check to see that we can do it
if (!move || srcFs.canRemoveFileAtLocalURL(srcURL)) {
final LocalFilesystemURL destination = makeDestinationURL(newName, srcURL, destURL);
srcFs.readFileAtURL(srcURL, 0, -1, new ReadFileCallback() {
public void handleData(InputStream inputStream, String contentType) throws IOException {
if (inputStream != null) {
//write data to file
OutputStream os = getOutputStreamForURL(destination);
final int BUFFER_SIZE = 8192;
byte[] buffer = new byte[BUFFER_SIZE];
for (;;) {
int bytesRead = inputStream.read(buffer, 0, BUFFER_SIZE);
if (bytesRead <= 0) {
break;
}
os.write(buffer, 0, bytesRead);
}
os.close();
} else {
throw new IOException("Cannot read file at source URL");
}
}
});
if (move) {
// Delete original
srcFs.removeFileAtLocalURL(srcURL);
}
return makeEntryForURL(destination, false);
} else {
throw new NoModificationAllowedException("Cannot move file at source URL");
}
}
abstract OutputStream getOutputStreamForURL(LocalFilesystemURL inputURL) throws IOException;
abstract void readFileAtURL(LocalFilesystemURL inputURL, long start, long end,
ReadFileCallback readFileCallback) throws IOException;
abstract long writeToFileAtURL(LocalFilesystemURL inputURL, String data, int offset,
boolean isBinary) throws NoModificationAllowedException, IOException;
abstract long truncateFileAtURL(LocalFilesystemURL inputURL, long size)
throws IOException, NoModificationAllowedException;
// This method should return null if filesystem urls cannot be mapped to paths
abstract String filesystemPathForURL(LocalFilesystemURL url);
abstract LocalFilesystemURL URLforFilesystemPath(String path);
abstract boolean canRemoveFileAtLocalURL(LocalFilesystemURL inputURL);
protected class LimitedInputStream extends FilterInputStream {
long numBytesToRead;
public LimitedInputStream(InputStream in, long numBytesToRead) {
super(in);
this.numBytesToRead = numBytesToRead;
}
@Override
public int read() throws IOException {
if (numBytesToRead <= 0) {
return -1;
}
numBytesToRead--;
return in.read();
}
@Override
public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
if (numBytesToRead <= 0) {
return -1;
}
int bytesToRead = byteCount;
if (byteCount > numBytesToRead) {
bytesToRead = (int)numBytesToRead; // Cast okay; long is less than int here.
}
int numBytesRead = in.read(buffer, byteOffset, bytesToRead);
numBytesToRead -= numBytesRead;
return numBytesRead;
}
}
/* Create a FileEntry or DirectoryEntry given an actual file on device.
* Return null if the file does not exist within this filesystem.
*/
public JSONObject makeEntryForFile(File file) throws JSONException {
return null;
}
}