openEventsPlatformApp/plugins/cordova-plugin-file/src/android/AssetFilesystem.java
plugins/cordova-plugin-file/src/android/AssetFilesystem.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.content.res.AssetManager;
22import android.net.Uri;
23
24import org.apache.cordova.CordovaResourceApi;
25import org.apache.cordova.LOG;
26import org.json.JSONArray;
27import org.json.JSONException;
28import org.json.JSONObject;
29
30import java.io.File;
31import java.io.FileNotFoundException;
32import java.io.IOException;
33import java.io.ObjectInputStream;
34import java.util.HashMap;
35import java.util.Map;
36
37public class AssetFilesystem extends Filesystem {
38
39 private final AssetManager assetManager;
40
41 // A custom gradle hook creates the cdvasset.manifest file, which speeds up asset listing a tonne.
42 // See: http://stackoverflow.com/questions/16911558/android-assetmanager-list-incredibly-slow
43 private static Object listCacheLock = new Object();
44 private static boolean listCacheFromFile;
45 private static Map<String, String[]> listCache;
46 private static Map<String, Long> lengthCache;
47
48 private static final String LOG_TAG = "AssetFilesystem";
49
50 private void lazyInitCaches() {
51 synchronized (listCacheLock) {
52 if (listCache == null) {
53 ObjectInputStream ois = null;
54 try {
55 ois = new ObjectInputStream(assetManager.open("cdvasset.manifest"));
56 listCache = (Map<String, String[]>) ois.readObject();
57 lengthCache = (Map<String, Long>) ois.readObject();
58 listCacheFromFile = true;
59 } catch (ClassNotFoundException e) {
60 e.printStackTrace();
61 } catch (IOException e) {
62 // Asset manifest won't exist if the gradle hook isn't set up correctly.
63 } finally {
64 if (ois != null) {
65 try {
66 ois.close();
67 } catch (IOException e) {
68 LOG.d(LOG_TAG, e.getLocalizedMessage());
69 }
70 }
71 }
72 if (listCache == null) {
73 LOG.w("AssetFilesystem", "Asset manifest not found. Recursive copies and directory listing will be slow.");
74 listCache = new HashMap<String, String[]>();
75 }
76 }
77 }
78 }
79
80 private String[] listAssets(String assetPath) throws IOException {
81 if (assetPath.startsWith("/")) {
82 assetPath = assetPath.substring(1);
83 }
84 if (assetPath.endsWith("/")) {
85 assetPath = assetPath.substring(0, assetPath.length() - 1);
86 }
87 lazyInitCaches();
88 String[] ret = listCache.get(assetPath);
89 if (ret == null) {
90 if (listCacheFromFile) {
91 ret = new String[0];
92 } else {
93 ret = assetManager.list(assetPath);
94 listCache.put(assetPath, ret);
95 }
96 }
97 return ret;
98 }
99
100 private long getAssetSize(String assetPath) throws FileNotFoundException {
101 if (assetPath.startsWith("/")) {
102 assetPath = assetPath.substring(1);
103 }
104 lazyInitCaches();
105 if (lengthCache != null) {
106 Long ret = lengthCache.get(assetPath);
107 if (ret == null) {
108 throw new FileNotFoundException("Asset not found: " + assetPath);
109 }
110 return ret;
111 }
112 CordovaResourceApi.OpenForReadResult offr = null;
113 try {
114 offr = resourceApi.openForRead(nativeUriForFullPath(assetPath));
115 long length = offr.length;
116 if (length < 0) {
117 // available() doesn't always yield the file size, but for assets it does.
118 length = offr.inputStream.available();
119 }
120 return length;
121 } catch (IOException e) {
122 FileNotFoundException fnfe = new FileNotFoundException("File not found: " + assetPath);
123 fnfe.initCause(e);
124 throw fnfe;
125 } finally {
126 if (offr != null) {
127 try {
128 offr.inputStream.close();
129 } catch (IOException e) {
130 LOG.d(LOG_TAG, e.getLocalizedMessage());
131 }
132 }
133 }
134 }
135
136 public AssetFilesystem(AssetManager assetManager, CordovaResourceApi resourceApi) {
137 super(Uri.parse("file:///android_asset/"), "assets", resourceApi);
138 this.assetManager = assetManager;
139 }
140
141 @Override
142 public Uri toNativeUri(LocalFilesystemURL inputURL) {
143 return nativeUriForFullPath(inputURL.path);
144 }
145
146 @Override
147 public LocalFilesystemURL toLocalUri(Uri inputURL) {
148 if (!"file".equals(inputURL.getScheme())) {
149 return null;
150 }
151 File f = new File(inputURL.getPath());
152 // Removes and duplicate /s (e.g. file:///a//b/c)
153 Uri resolvedUri = Uri.fromFile(f);
154 String rootUriNoTrailingSlash = rootUri.getEncodedPath();
155 rootUriNoTrailingSlash = rootUriNoTrailingSlash.substring(0, rootUriNoTrailingSlash.length() - 1);
156 if (!resolvedUri.getEncodedPath().startsWith(rootUriNoTrailingSlash)) {
157 return null;
158 }
159 String subPath = resolvedUri.getEncodedPath().substring(rootUriNoTrailingSlash.length());
160 // Strip leading slash
161 if (!subPath.isEmpty()) {
162 subPath = subPath.substring(1);
163 }
164 Uri.Builder b = new Uri.Builder()
165 .scheme(LocalFilesystemURL.FILESYSTEM_PROTOCOL)
166 .authority("localhost")
167 .path(name);
168 if (!subPath.isEmpty()) {
169 b.appendEncodedPath(subPath);
170 }
171 if (isDirectory(subPath) || inputURL.getPath().endsWith("/")) {
172 // Add trailing / for directories.
173 b.appendEncodedPath("");
174 }
175 return LocalFilesystemURL.parse(b.build());
176 }
177
178 private boolean isDirectory(String assetPath) {
179 try {
180 return listAssets(assetPath).length != 0;
181 } catch (IOException e) {
182 return false;
183 }
184 }
185
186 @Override
187 public LocalFilesystemURL[] listChildren(LocalFilesystemURL inputURL) throws FileNotFoundException {
188 String pathNoSlashes = inputURL.path.substring(1);
189 if (pathNoSlashes.endsWith("/")) {
190 pathNoSlashes = pathNoSlashes.substring(0, pathNoSlashes.length() - 1);
191 }
192
193 String[] files;
194 try {
195 files = listAssets(pathNoSlashes);
196 } catch (IOException e) {
197 FileNotFoundException fnfe = new FileNotFoundException();
198 fnfe.initCause(e);
199 throw fnfe;
200 }
201
202 LocalFilesystemURL[] entries = new LocalFilesystemURL[files.length];
203 for (int i = 0; i < files.length; ++i) {
204 entries[i] = localUrlforFullPath(new File(inputURL.path, files[i]).getPath());
205 }
206 return entries;
207 }
208
209 @Override
210 public JSONObject getFileForLocalURL(LocalFilesystemURL inputURL,
211 String path, JSONObject options, boolean directory)
212 throws FileExistsException, IOException, TypeMismatchException, EncodingException, JSONException {
213 if (options != null && options.optBoolean("create")) {
214 throw new UnsupportedOperationException("Assets are read-only");
215 }
216
217 // Check whether the supplied path is absolute or relative
218 if (directory && !path.endsWith("/")) {
219 path += "/";
220 }
221
222 LocalFilesystemURL requestedURL;
223 if (path.startsWith("/")) {
224 requestedURL = localUrlforFullPath(normalizePath(path));
225 } else {
226 requestedURL = localUrlforFullPath(normalizePath(inputURL.path + "/" + path));
227 }
228
229 // Throws a FileNotFoundException if it doesn't exist.
230 getFileMetadataForLocalURL(requestedURL);
231
232 boolean isDir = isDirectory(requestedURL.path);
233 if (directory && !isDir) {
234 throw new TypeMismatchException("path doesn't exist or is file");
235 } else if (!directory && isDir) {
236 throw new TypeMismatchException("path doesn't exist or is directory");
237 }
238
239 // Return the directory
240 return makeEntryForURL(requestedURL);
241 }
242
243 @Override
244 public JSONObject getFileMetadataForLocalURL(LocalFilesystemURL inputURL) throws FileNotFoundException {
245 JSONObject metadata = new JSONObject();
246 long size = inputURL.isDirectory ? 0 : getAssetSize(inputURL.path);
247 try {
248 metadata.put("size", size);
249 metadata.put("type", inputURL.isDirectory ? "text/directory" : resourceApi.getMimeType(toNativeUri(inputURL)));
250 metadata.put("name", new File(inputURL.path).getName());
251 metadata.put("fullPath", inputURL.path);
252 metadata.put("lastModifiedDate", 0);
253 } catch (JSONException e) {
254 return null;
255 }
256 return metadata;
257 }
258
259 @Override
260 public boolean canRemoveFileAtLocalURL(LocalFilesystemURL inputURL) {
261 return false;
262 }
263
264 @Override
265 long writeToFileAtURL(LocalFilesystemURL inputURL, String data, int offset, boolean isBinary) throws NoModificationAllowedException, IOException {
266 throw new NoModificationAllowedException("Assets are read-only");
267 }
268
269 @Override
270 long truncateFileAtURL(LocalFilesystemURL inputURL, long size) throws IOException, NoModificationAllowedException {
271 throw new NoModificationAllowedException("Assets are read-only");
272 }
273
274 @Override
275 String filesystemPathForURL(LocalFilesystemURL url) {
276 return new File(rootUri.getPath(), url.path).toString();
277 }
278
279 @Override
280 LocalFilesystemURL URLforFilesystemPath(String path) {
281 return null;
282 }
283
284 @Override
285 boolean removeFileAtLocalURL(LocalFilesystemURL inputURL) throws InvalidModificationException, NoModificationAllowedException {
286 throw new NoModificationAllowedException("Assets are read-only");
287 }
288
289 @Override
290 boolean recursiveRemoveFileAtLocalURL(LocalFilesystemURL inputURL) throws NoModificationAllowedException {
291 throw new NoModificationAllowedException("Assets are read-only");
292 }
293
294}