Panda3D
virtualFileMountAndroidAsset.cxx
Go to the documentation of this file.
1 /**
2  * PANDA 3D SOFTWARE
3  * Copyright (c) Carnegie Mellon University. All rights reserved.
4  *
5  * All use of this software is subject to the terms of the revised BSD
6  * license. You should have received a copy of this license along
7  * with this source code in a file named "LICENSE."
8  *
9  * @file virtualFileMountAndroidAsset.cxx
10  * @author rdb
11  * @date 2013-01-21
12  */
13 
14 #ifdef ANDROID
15 
17 #include "virtualFileSystem.h"
18 
19 #ifndef NDEBUG
20 #include <sys/stat.h>
21 #endif
22 
23 using std::streamoff;
24 using std::streampos;
25 using std::streamsize;
26 
27 TypeHandle VirtualFileMountAndroidAsset::_type_handle;
28 
29 /**
30  *
31  */
32 VirtualFileMountAndroidAsset::
33 ~VirtualFileMountAndroidAsset() {
34 }
35 
36 /**
37  * Returns a file descriptor that can be used to read the asset if it was
38  * stored uncompressed and unencrypted. Returns a valid fd or -1.
39  */
40 int VirtualFileMountAndroidAsset::
41 get_fd(const Filename &file, off_t *start, off_t *length) const {
42  AAsset* asset;
43  asset = AAssetManager_open(_asset_mgr, file.c_str(), AASSET_MODE_UNKNOWN);
44 
45  int fd = AAsset_openFileDescriptor(asset, start, length);
46  AAsset_close(asset);
47  return fd;
48 }
49 
50 /**
51  * Returns true if the indicated file exists within the mount system.
52  */
53 bool VirtualFileMountAndroidAsset::
54 has_file(const Filename &file) const {
55  return (file.empty() || /*is_directory(file) ||*/ is_regular_file(file));
56 }
57 
58 /**
59  * Returns true if the indicated file exists within the mount system and is a
60  * directory.
61  */
62 bool VirtualFileMountAndroidAsset::
63 is_directory(const Filename &file) const {
64  // This is the only way - AAssetManager_openDir also works for files.
65  // AAssetDir *dir = AAssetManager_openDir(_asset_mgr, file.c_str());
66 
67  // express_cat.error() << "is_directory " << file << " - " << dir << "\n";
68 
69  // if (dir == NULL) { return false; } AAssetDir_close(dir);
70 
71  // openDir doesn't return NULL for ordinary files!
72  return !is_regular_file(file);
73 }
74 
75 /**
76  * Returns true if the indicated file exists within the mount system and is a
77  * regular file.
78  */
79 bool VirtualFileMountAndroidAsset::
80 is_regular_file(const Filename &file) const {
81  // I'm afraid the only way to see if it exists is to try and open it.
82  AAsset* asset;
83  asset = AAssetManager_open(_asset_mgr, file.c_str(), AASSET_MODE_UNKNOWN);
84 
85  //express_cat.error() << "is_regular_file " << file << " - " << asset << "\n";
86 
87  if (asset == nullptr) {
88  return false;
89  }
90  AAsset_close(asset);
91  return true;
92 }
93 
94 /**
95  * Fills up the indicated pvector with the contents of the file, if it is a
96  * regular file. Returns true on success, false otherwise.
97  */
98 bool VirtualFileMountAndroidAsset::
99 read_file(const Filename &file, bool do_uncompress,
100  vector_uchar &result) const {
101  if (do_uncompress) {
102  // If the file is to be decompressed, we'd better just use the higher-
103  // level implementation, which includes support for on-the-fly
104  // decompression.
105  return VirtualFileMount::read_file(file, do_uncompress, result);
106  }
107 
108  // But if we're just reading a straight file, let's just read it here to
109  // avoid all of the streambuf nonsense.
110  result.clear();
111 
112  AAsset* asset;
113  asset = AAssetManager_open(_asset_mgr, file.c_str(), AASSET_MODE_STREAMING);
114  if (asset == nullptr) {
115  express_cat.info()
116  << "Unable to read " << file << "\n";
117  return false;
118  }
119 
120  // Reserve enough space to hold the entire file.
121  off_t file_size = AAsset_getLength(asset);
122  if (file_size == 0) {
123  return true;
124  } else if (file_size > 0) {
125  result.reserve((size_t)file_size);
126  }
127 
128  static const size_t buffer_size = 4096;
129  char buffer[buffer_size];
130 
131  int count = AAsset_read(asset, buffer, buffer_size);
132  while (count > 0) {
133  thread_consider_yield();
134  result.insert(result.end(), buffer, buffer + count);
135  count = AAsset_read(asset, buffer, buffer_size);
136  }
137 
138  AAsset_close(asset);
139  return (count == 0);
140 }
141 
142 /**
143  * Opens the file for reading, if it exists. Returns a newly allocated
144  * istream on success (which you should eventually delete when you are done
145  * reading). Returns NULL on failure.
146  */
147 std::istream *VirtualFileMountAndroidAsset::
148 open_read_file(const Filename &file) const {
149  AAsset* asset;
150  asset = AAssetManager_open(_asset_mgr, file.c_str(), AASSET_MODE_UNKNOWN);
151  if (asset == nullptr) {
152  return nullptr;
153  }
154 
155  AssetStream *stream = new AssetStream(asset);
156  return (std::istream *) stream;
157 }
158 
159 /**
160  * Returns the current size on disk (or wherever it is) of the already-open
161  * file. Pass in the stream that was returned by open_read_file(); some
162  * implementations may require this stream to determine the size.
163  */
164 streamsize VirtualFileMountAndroidAsset::
165 get_file_size(const Filename &file, std::istream *in) const {
166  // If it's already open, get the AAsset pointer from the streambuf.
167  const AssetStreamBuf *buf = (const AssetStreamBuf *) in->rdbuf();
168  off_t length = AAsset_getLength(buf->_asset);
169  return length;
170 }
171 
172 /**
173  * Returns the current size on disk (or wherever it is) of the file before it
174  * has been opened.
175  */
176 streamsize VirtualFileMountAndroidAsset::
177 get_file_size(const Filename &file) const {
178  AAsset* asset = AAssetManager_open(_asset_mgr, file.c_str(), AASSET_MODE_UNKNOWN);
179  off_t length = AAsset_getLength(asset);
180  AAsset_close(asset);
181  return length;
182 }
183 
184 /**
185  * Returns a time_t value that represents the time the file was last modified,
186  * to within whatever precision the operating system records this information
187  * (on a Windows95 system, for instance, this may only be accurate to within 2
188  * seconds).
189  *
190  * If the timestamp cannot be determined, either because it is not supported
191  * by the operating system or because there is some error (such as file not
192  * found), returns 0.
193  */
194 time_t VirtualFileMountAndroidAsset::
195 get_timestamp(const Filename &file) const {
196  // There's no obvious way to get a timestamp from an Android asset.
197  return 0;
198 }
199 
200 /**
201  * Populates the SubfileInfo structure with the data representing where the
202  * file actually resides on disk, if this is knowable. Returns true if the
203  * file might reside on disk, and the info is populated, or false if it might
204  * not (or it is not known where the file resides), in which case the info is
205  * meaningless.
206  */
207 bool VirtualFileMountAndroidAsset::
208 get_system_info(const Filename &file, SubfileInfo &info) {
209  off_t start, length;
210  int fd = get_fd(file, &start, &length);
211 
212 #ifndef NDEBUG
213  // Double-check that this fd actually points to the apk.
214  struct stat st1, st2;
215  nassertr(fstat(fd, &st1) == 0, false);
216  nassertr(stat(_apk_path.c_str(), &st2) == 0, false);
217  nassertr(st1.st_dev == st2.st_dev, false);
218  nassertr(st1.st_ino == st2.st_ino, false);
219 #endif
220 
221  // We don't actually need the file descriptor, so close it.
222  close(fd);
223 
224  info = SubfileInfo(_apk_path, start, length);
225  return true;
226 }
227 
228 /**
229  * Fills the given vector up with the list of filenames that are local to this
230  * directory, if the filename is a directory. Returns true if successful, or
231  * false if the file is not a directory or cannot be read.
232  */
233 bool VirtualFileMountAndroidAsset::
234 scan_directory(vector_string &contents, const Filename &dir) const {
235  AAssetDir *asset_dir = AAssetManager_openDir(_asset_mgr, dir.c_str());
236  if (asset_dir == nullptr) {
237  return false;
238  }
239 
240  // Note: this returns the full path.
241  const char *fullpath = AAssetDir_getNextFileName(asset_dir);
242 
243  while (fullpath != nullptr) {
244  express_cat.error() << fullpath << "\n"; // DEBUG
245  // Determine the basename and add it to the vector.
246  Filename fname (fullpath);
247  contents.push_back(fname.get_basename());
248  fullpath = AAssetDir_getNextFileName(asset_dir);
249  }
250 
251  return true;
252 }
253 
254 /**
255  *
256  */
257 VirtualFileMountAndroidAsset::AssetStream::
258 ~AssetStream() {
259  delete rdbuf();
260 }
261 
262 /**
263  *
264  */
265 VirtualFileMountAndroidAsset::AssetStreamBuf::
266 AssetStreamBuf(AAsset *asset) :
267  _asset(asset) {
268 
269 #ifdef PHAVE_IOSTREAM
270  char *buf = new char[4096];
271  char *ebuf = buf + 4096;
272  setg(buf, ebuf, ebuf);
273 
274 #else
275  allocate();
276  setg(base(), ebuf(), ebuf());
277 #endif
278 }
279 
280 /**
281  *
282  */
283 VirtualFileMountAndroidAsset::AssetStreamBuf::
284 ~AssetStreamBuf() {
285  AAsset_close(_asset);
286 }
287 
288 /**
289  * Implements seeking within the stream.
290  */
291 streampos VirtualFileMountAndroidAsset::AssetStreamBuf::
292 seekoff(streamoff off, ios_seekdir dir, ios_openmode which) {
293  size_t n = egptr() - gptr();
294 
295  int whence;
296  switch (dir) {
297  case std::ios_base::beg:
298  whence = SEEK_SET;
299  break;
300  case std::ios_base::cur:
301  if (off == 0) {
302  // Just requesting the current position, no need to void the buffer.
303  return AAsset_seek(_asset, 0, SEEK_CUR) - n;
304 
305  } else if (gptr() + off >= eback() && gptr() + off < egptr()) {
306  // We can seek around within the buffer.
307  gbump(off);
308  return AAsset_seek(_asset, 0, SEEK_CUR) - n + off;
309  }
310  whence = SEEK_CUR;
311  break;
312  case std::ios_base::end:
313  whence = SEEK_END;
314  break;
315  default:
316  return pos_type(-1);
317  }
318  gbump(n);
319  return AAsset_seek(_asset, off, whence);
320 }
321 
322 /**
323  * A variant on seekoff() to implement seeking within a stream.
324  *
325  * The MSDN Library claims that it is only necessary to redefine seekoff(),
326  * and not seekpos() as well, as the default implementation of seekpos() is
327  * supposed to map to seekoff() exactly as I am doing here; but in fact it
328  * must do something else, because seeking didn't work on Windows until I
329  * redefined this function as well.
330  */
331 streampos VirtualFileMountAndroidAsset::AssetStreamBuf::
332 seekpos(streampos pos, ios_openmode which) {
333  size_t n = egptr() - gptr();
334  gbump(n);
335  return AAsset_seek(_asset, pos, SEEK_SET);
336 }
337 
338 /**
339  * Called by the system istream implementation when its internal buffer needs
340  * more characters.
341  */
342 int VirtualFileMountAndroidAsset::AssetStreamBuf::
343 underflow() {
344  // Sometimes underflow() is called even if the buffer is not empty.
345  if (gptr() >= egptr()) {
346  // Mark the buffer filled (with buffer_size bytes).
347  size_t buffer_size = egptr() - eback();
348  gbump(-(int)buffer_size);
349 
350  streamsize read_count;
351  read_count = AAsset_read(_asset, gptr(), buffer_size);
352 
353  if (read_count != buffer_size) {
354  // Oops, we didn't read what we thought we would.
355  if (read_count == 0) {
356  gbump(buffer_size);
357  return EOF;
358  }
359 
360  // Slide what we did read to the top of the buffer.
361  nassertr(read_count < buffer_size, EOF);
362  size_t delta = buffer_size - read_count;
363  memmove(gptr() + delta, gptr(), read_count);
364  gbump(delta);
365  }
366  }
367 
368  return (unsigned char)*gptr();
369 }
370 
371 #endif // ANDROID
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:39
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
bool is_regular_file(const Filename &filename) const
Convenience function; returns true if the named file exists and is a regular file.
PANDA 3D SOFTWARE Copyright (c) Carnegie Mellon University.
This class records a particular byte sub-range within an existing file on disk.
Definition: subfileInfo.h:26
TypeHandle is the identifier used to differentiate C++ class types.
Definition: typeHandle.h:81
virtual bool read_file(const Filename &file, bool do_uncompress, vector_uchar &result) const
Fills up the indicated pvector with the contents of the file, if it is a regular file.