openEventsPlatformApp/plugins/cordova-plugin-file/src/android/Filesystem.java
plugins/cordova-plugin-file/src/android/Filesystem.javaRaw
1/*
2 Licensed to the Apache Software Foundation (ASF) under one
3 or more contributor license agreements. See the NOTICE file
4 distributed with this work for additional information
5 regarding copyright ownership. The ASF licenses this file
6 to you under the Apache License, Version 2.0 (the
7 "License"); you may not use this file except in compliance
8 with the License. You may obtain a copy of the License at
9
10 http://www.apache.org/licenses/LICENSE-2.0
11
12 Unless required by applicable law or agreed to in writing,
13 software distributed under the License is distributed on an
14 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 KIND, either express or implied. See the License for the
16 specific language governing permissions and limitations
17 under the License.
18 */
19package org.apache.cordova.file;
20
21import android.net.Uri;
22
23import java.io.File;
24import java.io.FileNotFoundException;
25import java.io.FilterInputStream;
26import java.io.IOException;
27import java.io.InputStream;
28import java.io.OutputStream;
29import java.util.ArrayList;
30import java.util.Arrays;
31
32import org.apache.cordova.CordovaResourceApi;
33import org.json.JSONArray;
34import org.json.JSONException;
35import org.json.JSONObject;
36
37public abstract class Filesystem {
38
39 protected final Uri rootUri;
40 protected final CordovaResourceApi resourceApi;
41 public final String name;
42 private JSONObject rootEntry;
43
44 public Filesystem(Uri rootUri, String name, CordovaResourceApi resourceApi) {
45 this.rootUri = rootUri;
46 this.name = name;
47 this.resourceApi = resourceApi;
48 }
49
50 public interface ReadFileCallback {
51 public void handleData(InputStream inputStream, String contentType) throws IOException;
52 }
53
54 public static JSONObject makeEntryForURL(LocalFilesystemURL inputURL, Uri nativeURL) {
55 try {
56 String path = inputURL.path;
57 int end = path.endsWith("/") ? 1 : 0;
58 String[] parts = path.substring(0, path.length() - end).split("/+");
59 String fileName = parts[parts.length - 1];
60
61 JSONObject entry = new JSONObject();
62 entry.put("isFile", !inputURL.isDirectory);
63 entry.put("isDirectory", inputURL.isDirectory);
64 entry.put("name", fileName);
65 entry.put("fullPath", path);
66 // The file system can't be specified, as it would lead to an infinite loop,
67 // but the filesystem name can be.
68 entry.put("filesystemName", inputURL.fsName);
69 // Backwards compatibility
70 entry.put("filesystem", "temporary".equals(inputURL.fsName) ? 0 : 1);
71
72 String nativeUrlStr = nativeURL.toString();
73 if (inputURL.isDirectory && !nativeUrlStr.endsWith("/")) {
74 nativeUrlStr += "/";
75 }
76 entry.put("nativeURL", nativeUrlStr);
77 return entry;
78 } catch (JSONException e) {
79 e.printStackTrace();
80 throw new RuntimeException(e);
81 }
82 }
83
84 public JSONObject makeEntryForURL(LocalFilesystemURL inputURL) {
85 Uri nativeUri = toNativeUri(inputURL);
86 return nativeUri == null ? null : makeEntryForURL(inputURL, nativeUri);
87 }
88
89 public JSONObject makeEntryForNativeUri(Uri nativeUri) {
90 LocalFilesystemURL inputUrl = toLocalUri(nativeUri);
91 return inputUrl == null ? null : makeEntryForURL(inputUrl, nativeUri);
92 }
93
94 public JSONObject getEntryForLocalURL(LocalFilesystemURL inputURL) throws IOException {
95 return makeEntryForURL(inputURL);
96 }
97
98 public JSONObject makeEntryForFile(File file) {
99 return makeEntryForNativeUri(Uri.fromFile(file));
100 }
101
102 abstract JSONObject getFileForLocalURL(LocalFilesystemURL inputURL, String path,
103 JSONObject options, boolean directory) throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException;
104
105 abstract boolean removeFileAtLocalURL(LocalFilesystemURL inputURL) throws InvalidModificationException, NoModificationAllowedException;
106
107 abstract boolean recursiveRemoveFileAtLocalURL(LocalFilesystemURL inputURL) throws FileExistsException, NoModificationAllowedException;
108
109 abstract LocalFilesystemURL[] listChildren(LocalFilesystemURL inputURL) throws FileNotFoundException;
110
111 public final JSONArray readEntriesAtLocalURL(LocalFilesystemURL inputURL) throws FileNotFoundException {
112 LocalFilesystemURL[] children = listChildren(inputURL);
113 JSONArray entries = new JSONArray();
114 if (children != null) {
115 for (LocalFilesystemURL url : children) {
116 entries.put(makeEntryForURL(url));
117 }
118 }
119 return entries;
120 }
121
122 abstract JSONObject getFileMetadataForLocalURL(LocalFilesystemURL inputURL) throws FileNotFoundException;
123
124 public Uri getRootUri() {
125 return rootUri;
126 }
127
128 public boolean exists(LocalFilesystemURL inputURL) {
129 try {
130 getFileMetadataForLocalURL(inputURL);
131 } catch (FileNotFoundException e) {
132 return false;
133 }
134 return true;
135 }
136
137 public Uri nativeUriForFullPath(String fullPath) {
138 Uri ret = null;
139 if (fullPath != null) {
140 String encodedPath = Uri.fromFile(new File(fullPath)).getEncodedPath();
141 if (encodedPath.startsWith("/")) {
142 encodedPath = encodedPath.substring(1);
143 }
144 ret = rootUri.buildUpon().appendEncodedPath(encodedPath).build();
145 }
146 return ret;
147 }
148
149 public LocalFilesystemURL localUrlforFullPath(String fullPath) {
150 Uri nativeUri = nativeUriForFullPath(fullPath);
151 if (nativeUri != null) {
152 return toLocalUri(nativeUri);
153 }
154 return null;
155 }
156
157 /**
158 * Removes multiple repeated //s, and collapses processes ../s.
159 */
160 protected static String normalizePath(String rawPath) {
161 // If this is an absolute path, trim the leading "/" and replace it later
162 boolean isAbsolutePath = rawPath.startsWith("/");
163 if (isAbsolutePath) {
164 rawPath = rawPath.replaceFirst("/+", "");
165 }
166 ArrayList<String> components = new ArrayList<String>(Arrays.asList(rawPath.split("/+")));
167 for (int index = 0; index < components.size(); ++index) {
168 if (components.get(index).equals("..")) {
169 components.remove(index);
170 if (index > 0) {
171 components.remove(index-1);
172 --index;
173 }
174 }
175 }
176 StringBuilder normalizedPath = new StringBuilder();
177 for(String component: components) {
178 normalizedPath.append("/");
179 normalizedPath.append(component);
180 }
181 if (isAbsolutePath) {
182 return normalizedPath.toString();
183 } else {
184 return normalizedPath.toString().substring(1);
185 }
186 }
187
188 /**
189 * Gets the free space in bytes available on this filesystem.
190 * Subclasses may override this method to return nonzero free space.
191 */
192 public long getFreeSpaceInBytes() {
193 return 0;
194 }
195
196 public abstract Uri toNativeUri(LocalFilesystemURL inputURL);
197 public abstract LocalFilesystemURL toLocalUri(Uri inputURL);
198
199 public JSONObject getRootEntry() {
200 if (rootEntry == null) {
201 rootEntry = makeEntryForNativeUri(rootUri);
202 }
203 return rootEntry;
204 }
205
206 public JSONObject getParentForLocalURL(LocalFilesystemURL inputURL) throws IOException {
207 Uri parentUri = inputURL.uri;
208 String parentPath = new File(inputURL.uri.getPath()).getParent();
209 if (!"/".equals(parentPath)) {
210 parentUri = inputURL.uri.buildUpon().path(parentPath + '/').build();
211 }
212 return getEntryForLocalURL(LocalFilesystemURL.parse(parentUri));
213 }
214
215 protected LocalFilesystemURL makeDestinationURL(String newName, LocalFilesystemURL srcURL, LocalFilesystemURL destURL, boolean isDirectory) {
216 // I know this looks weird but it is to work around a JSON bug.
217 if ("null".equals(newName) || "".equals(newName)) {
218 newName = srcURL.uri.getLastPathSegment();;
219 }
220
221 String newDest = destURL.uri.toString();
222 if (newDest.endsWith("/")) {
223 newDest = newDest + newName;
224 } else {
225 newDest = newDest + "/" + newName;
226 }
227 if (isDirectory) {
228 newDest += '/';
229 }
230 return LocalFilesystemURL.parse(newDest);
231 }
232
233 /* Read a source URL (possibly from a different filesystem, srcFs,) and copy it to
234 * the destination URL on this filesystem, optionally with a new filename.
235 * If move is true, then this method should either perform an atomic move operation
236 * or remove the source file when finished.
237 */
238 public JSONObject copyFileToURL(LocalFilesystemURL destURL, String newName,
239 Filesystem srcFs, LocalFilesystemURL srcURL, boolean move) throws IOException, InvalidModificationException, JSONException, NoModificationAllowedException, FileExistsException {
240 // First, check to see that we can do it
241 if (move && !srcFs.canRemoveFileAtLocalURL(srcURL)) {
242 throw new NoModificationAllowedException("Cannot move file at source URL");
243 }
244 final LocalFilesystemURL destination = makeDestinationURL(newName, srcURL, destURL, srcURL.isDirectory);
245
246 Uri srcNativeUri = srcFs.toNativeUri(srcURL);
247
248 CordovaResourceApi.OpenForReadResult ofrr = resourceApi.openForRead(srcNativeUri);
249 OutputStream os = null;
250 try {
251 os = getOutputStreamForURL(destination);
252 } catch (IOException e) {
253 ofrr.inputStream.close();
254 throw e;
255 }
256 // Closes streams.
257 resourceApi.copyResource(ofrr, os);
258
259 if (move) {
260 srcFs.removeFileAtLocalURL(srcURL);
261 }
262 return getEntryForLocalURL(destination);
263 }
264
265 public OutputStream getOutputStreamForURL(LocalFilesystemURL inputURL) throws IOException {
266 return resourceApi.openOutputStream(toNativeUri(inputURL));
267 }
268
269 public void readFileAtURL(LocalFilesystemURL inputURL, long start, long end,
270 ReadFileCallback readFileCallback) throws IOException {
271 CordovaResourceApi.OpenForReadResult ofrr = resourceApi.openForRead(toNativeUri(inputURL));
272 if (end < 0) {
273 end = ofrr.length;
274 }
275 long numBytesToRead = end - start;
276 try {
277 if (start > 0) {
278 ofrr.inputStream.skip(start);
279 }
280 InputStream inputStream = ofrr.inputStream;
281 if (end < ofrr.length) {
282 inputStream = new LimitedInputStream(inputStream, numBytesToRead);
283 }
284 readFileCallback.handleData(inputStream, ofrr.mimeType);
285 } finally {
286 ofrr.inputStream.close();
287 }
288 }
289
290 abstract long writeToFileAtURL(LocalFilesystemURL inputURL, String data, int offset,
291 boolean isBinary) throws NoModificationAllowedException, IOException;
292
293 abstract long truncateFileAtURL(LocalFilesystemURL inputURL, long size)
294 throws IOException, NoModificationAllowedException;
295
296 // This method should return null if filesystem urls cannot be mapped to paths
297 abstract String filesystemPathForURL(LocalFilesystemURL url);
298
299 abstract LocalFilesystemURL URLforFilesystemPath(String path);
300
301 abstract boolean canRemoveFileAtLocalURL(LocalFilesystemURL inputURL);
302
303 protected class LimitedInputStream extends FilterInputStream {
304 long numBytesToRead;
305 public LimitedInputStream(InputStream in, long numBytesToRead) {
306 super(in);
307 this.numBytesToRead = numBytesToRead;
308 }
309 @Override
310 public int read() throws IOException {
311 if (numBytesToRead <= 0) {
312 return -1;
313 }
314 numBytesToRead--;
315 return in.read();
316 }
317 @Override
318 public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
319 if (numBytesToRead <= 0) {
320 return -1;
321 }
322 int bytesToRead = byteCount;
323 if (byteCount > numBytesToRead) {
324 bytesToRead = (int)numBytesToRead; // Cast okay; long is less than int here.
325 }
326 int numBytesRead = in.read(buffer, byteOffset, bytesToRead);
327 numBytesToRead -= numBytesRead;
328 return numBytesRead;
329 }
330 }
331}