From 3a97bb08f4ee75635b9932f4d603932aff67054a Mon Sep 17 00:00:00 2001 From: Frederik Seiffert Date: Tue, 7 May 2019 17:38:54 +0200 Subject: [PATCH 01/11] Added basic spinlock implementation using builtins. This will be used on platforms without pthread_spin_lock(), e.g. Android targeting API level < 24. Implementation lifted from spinlock.h in libobjc2. --- Source/NSThread.m | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/Source/NSThread.m b/Source/NSThread.m index 9419e28b3..c7b35c7ff 100644 --- a/Source/NSThread.m +++ b/Source/NSThread.m @@ -38,21 +38,40 @@ // Dummy implementatation // cleaner than IFDEF'ing the code everywhere #if !(HAVE_PTHREAD_SPIN_LOCK) -#warning no spin_locks, using dummy versions -typedef int pthread_spinlock_t; +typedef volatile int pthread_spinlock_t; int pthread_spin_init(pthread_spinlock_t *lock, int pshared) { -#if DEBUG +#if DEBUG && !__has_builtin(__sync_bool_compare_and_swap) fprintf(stderr,"NSThread.m: Warning this platform does not support spin locks - init.\n"); #endif return 0; } int pthread_spin_lock(pthread_spinlock_t *lock) { +#if __has_builtin(__sync_bool_compare_and_swap) + int count = 0; + // Set the spin lock value to 1 if it is 0. + while(!__sync_bool_compare_and_swap(lock, 0, 1)) + { + count++; + if (0 == count % 10) + { + // If it is already 1, let another thread play with the CPU for a + // bit then try again. + sleep(0); + } + } +#else + #warning no spin_locks, using dummy versions +#endif return 0; } int pthread_spin_unlock(pthread_spinlock_t *lock) { +#if __has_builtin(__sync_bool_compare_and_swap) + __sync_synchronize(); + *lock = 0; +#endif return 0; } int pthread_spin_destroy(pthread_spinlock_t *lock) From cecb41958b11eb9f619dc9122a50c293432b43c4 Mon Sep 17 00:00:00 2001 From: Frederik Seiffert Date: Wed, 8 May 2019 10:52:06 +0200 Subject: [PATCH 02/11] Always use syslog for NSLog on Android. As there is no way to access stdout/stderr on Android, and syslog is available and outputs to the system log (accesible using "adb logcat"), this improves the developer experience when developing for Android by enabling NSLog output without having to set the "GSLogSyslog" flag. --- Source/NSLog.m | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/NSLog.m b/Source/NSLog.m index 35e6fc4f3..f2bc50718 100644 --- a/Source/NSLog.m +++ b/Source/NSLog.m @@ -181,8 +181,10 @@ _NSLog_standard_printf_handler(NSString* message) #else #if defined(HAVE_SYSLOG) +#ifndef __ANDROID__ // always use syslog on Android (no stdout/stderr) if (GSPrivateDefaultsFlag(GSLogSyslog) == YES || write(_NSLogDescriptor, buf, len) != (int)len) +#endif { null_terminated_buf = malloc(sizeof (char) * (len + 1)); strncpy (null_terminated_buf, buf, len); From 43c5ccaef6ff0e018308ebb22f68bfb5b7ff1858 Mon Sep 17 00:00:00 2001 From: Frederik Seiffert Date: Tue, 7 May 2019 16:33:20 +0200 Subject: [PATCH 03/11] Add compatibility for Swift corelibs libdispatch release This updates the libdispatch runloop integration to be compatible with the Swift corelibs libdispatch release: https://github.com/apple/swift-corelibs-libdispatch In that release, the main queue handle and drain functions have been renamed with a "_4CF" (for CoreFoundation) suffix and have moved to private.h, so we now check for the existance of this header and function names. Note that libdispatch must be compiled with INSTALL_PRIVATE_HEADERS=YES. Also fixes the checks for the HAVE_LIBDISPATCH_RUNLOOP define (was inverted) and ensures that both the handle and drain functions are available. --- Headers/GNUstepBase/config.h.in | 11 +++++++++++ Source/NSRunLoop.m | 16 +++++++++++++++- configure | 32 ++++++++++++++++++++++++++++++-- configure.ac | 10 ++++++++-- 4 files changed, 64 insertions(+), 5 deletions(-) diff --git a/Headers/GNUstepBase/config.h.in b/Headers/GNUstepBase/config.h.in index 390b53603..79896e758 100644 --- a/Headers/GNUstepBase/config.h.in +++ b/Headers/GNUstepBase/config.h.in @@ -216,6 +216,9 @@ /* Define to 1 if you have the `dispatch_main_queue_drain_np' function. */ #undef HAVE_DISPATCH_MAIN_QUEUE_DRAIN_NP +/* Define to 1 if you have the header file. */ +#undef HAVE_DISPATCH_PRIVATE_H + /* Define to 1 if you have the `dladdr' function. */ #undef HAVE_DLADDR @@ -767,6 +770,14 @@ /* Define to 1 if you have the `_Block_copy' function. */ #undef HAVE__BLOCK_COPY +/* Define to 1 if you have the `_dispatch_get_main_queue_handle_4CF' function. + */ +#undef HAVE__DISPATCH_GET_MAIN_QUEUE_HANDLE_4CF + +/* Define to 1 if you have the `_dispatch_main_queue_callback_4CF' function. + */ +#undef HAVE__DISPATCH_MAIN_QUEUE_CALLBACK_4CF + /* Define to 1 if you have the `__builtin_extract_return_address' function. */ #undef HAVE___BUILTIN_EXTRACT_RETURN_ADDRESS diff --git a/Source/NSRunLoop.m b/Source/NSRunLoop.m index 1746fa3d0..962f04c55 100644 --- a/Source/NSRunLoop.m +++ b/Source/NSRunLoop.m @@ -62,10 +62,12 @@ #include #include -#if HAVE_DISPATCH_GET_MAIN_QUEUE_HANDLE_NP && HAVE_DISPATCH_MAIN_QUEUE_DRAIN_NP +#if HAVE_LIBDISPATCH_RUNLOOP # define RL_INTEGRATE_DISPATCH 1 # ifdef HAVE_DISPATCH_H # include +# elif HAVE_DISPATCH_PRIVATE_H +# include # elif HAVE_DISPATCH_DISPATCH_H # include # endif @@ -398,7 +400,13 @@ static inline BOOL timerInvalidated(NSTimer *t) @implementation GSMainQueueDrainer + (void*) mainQueueFileDescriptor { +#if HAVE_DISPATCH_GET_MAIN_QUEUE_HANDLE_NP return (void*)(uintptr_t)dispatch_get_main_queue_handle_np(); +#elif HAVE__DISPATCH_GET_MAIN_QUEUE_HANDLE_4CF + return (void*)_dispatch_get_main_queue_handle_4CF(); +#else +#error libdispatch missing main queue handle function +#endif } - (void) receivedEvent: (void*)data @@ -406,7 +414,13 @@ static inline BOOL timerInvalidated(NSTimer *t) extra: (void*)extra forMode: (NSString*)mode { +#if HAVE_DISPATCH_MAIN_QUEUE_DRAIN_NP dispatch_main_queue_drain_np(); +#elif HAVE__DISPATCH_MAIN_QUEUE_CALLBACK_4CF + _dispatch_main_queue_callback_4CF(NULL) +#else +#error libdispatch missing main queue callback function +#endif } @end #endif diff --git a/configure b/configure index 57a45f06a..6ed306078 100755 --- a/configure +++ b/configure @@ -12410,6 +12410,20 @@ else have_dispatch=no fi +done + + # check for private header which includes runloop integration functions in + # the Swift corelibs libdispatch release + for ac_header in dispatch/private.h +do : + ac_fn_c_check_header_mongrel "$LINENO" "dispatch/private.h" "ac_cv_header_dispatch_private_h" "$ac_includes_default" +if test "x$ac_cv_header_dispatch_private_h" = xyes; then : + cat >>confdefs.h <<_ACEOF +#define HAVE_DISPATCH_PRIVATE_H 1 +_ACEOF + +fi + done fi @@ -12515,10 +12529,24 @@ _ACEOF fi done - if test "$ac_cv_func_dispatch_main_queue_drain_np" = "no"; then + if test "$ac_cv_func_dispatch_main_queue_drain_np" = "yes" && test "$ac_cv_func_dispatch_get_main_queue_handle_np" = "yes"; then HAVE_LIBDISPATCH_RUNLOOP=1 fi - if test "$ac_cv_func_dispatch_get_main_queue_handle_np" = "no"; then + # Check for "_4CF" variants of runloop integration functions provided by the + # Swift corelibs libdispatch release + for ac_func in _dispatch_main_queue_callback_4CF _dispatch_get_main_queue_handle_4CF +do : + as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh` +ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var" +if eval test \"x\$"$as_ac_var"\" = x"yes"; then : + cat >>confdefs.h <<_ACEOF +#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1 +_ACEOF + +fi +done + + if test "$ac_cv_func__dispatch_main_queue_callback_4CF" = "yes" && test "$ac_cv_func__dispatch_get_main_queue_handle_4CF" = "yes"; then HAVE_LIBDISPATCH_RUNLOOP=1 fi fi diff --git a/configure.ac b/configure.ac index 6231f8282..2d2ece164 100644 --- a/configure.ac +++ b/configure.ac @@ -3357,6 +3357,9 @@ if test $enable_libdispatch = yes; then AC_CHECK_HEADERS(dispatch.h, have_dispatch=yes, have_dispatch=no) if test "$have_dispatch" = "no"; then AC_CHECK_HEADERS(dispatch/dispatch.h, have_dispatch=yes, have_dispatch=no) + # check for private header which includes runloop integration functions in + # the Swift corelibs libdispatch release + AC_CHECK_HEADERS(dispatch/private.h) fi if test "$have_dispatch" = "yes"; then AC_CHECK_LIB(dispatch, dispatch_queue_create, have_dispatch=yes, have_dispatch=no) @@ -3388,10 +3391,13 @@ if test $HAVE_LIBDISPATCH = 1; then # We check whether we have a variant of libdispatch that allows runloop # integration AC_CHECK_FUNCS(dispatch_main_queue_drain_np dispatch_get_main_queue_handle_np) - if test "$ac_cv_func_dispatch_main_queue_drain_np" = "no"; then + if test "$ac_cv_func_dispatch_main_queue_drain_np" = "yes" && test "$ac_cv_func_dispatch_get_main_queue_handle_np" = "yes"; then HAVE_LIBDISPATCH_RUNLOOP=1 fi - if test "$ac_cv_func_dispatch_get_main_queue_handle_np" = "no"; then + # Check for "_4CF" variants of runloop integration functions provided by the + # Swift corelibs libdispatch release + AC_CHECK_FUNCS(_dispatch_main_queue_callback_4CF _dispatch_get_main_queue_handle_4CF) + if test "$ac_cv_func__dispatch_main_queue_callback_4CF" = "yes" && test "$ac_cv_func__dispatch_get_main_queue_handle_4CF" = "yes"; then HAVE_LIBDISPATCH_RUNLOOP=1 fi fi From ecbecbeabd1d37cecdef818c7e1447e2e7697c85 Mon Sep 17 00:00:00 2001 From: Richard Frith-Macdonald Date: Mon, 20 May 2019 14:57:44 +0100 Subject: [PATCH 04/11] Merged patches by Frederik Seiffert --- ChangeLog | 24 ++++++++++++++++++++++++ configure | 24 ++++++------------------ 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/ChangeLog b/ChangeLog index 9f9f2e3bc..4a854b93e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,27 @@ +2019-05-20 Frederik Seiffert + + * Source/NSLog.m: Have all logs go to syslog on android. + * Source/NSThread.m: Spinlock implementation using builtins as + implemented by David in libobjc2 + * Source/NSRunLoop.m + * Headers/GNUstepBase/config.h.in: + * configure.ac: + This updates the libdispatch runloop integration to be compatible with + the Swift corelibs libdispatch release at + (https://github.com/apple/swift-corelibs-libdispatch). + + In that release, the main queue handle and drain functions have been + renamed with a "_4CF" (for CoreFoundation) suffix and have moved to + private.h, so we now check for the existance of this header and + function names. + + Note that libdispatch must be compiled with + INSTALL_PRIVATE_HEADERS=YES. + + Also fixes the checks for the HAVE_LIBDISPATCH_RUNLOOP define (was + inverted) and ensures that both the handle and drain functions are + available. + 2019-02-16 Richard Frith-Macdonald * Source/NSString.m: Removed public functions which could conflict diff --git a/configure b/configure index f0bb99a17..f5fc4c9d8 100755 --- a/configure +++ b/configure @@ -764,7 +764,6 @@ infodir docdir oldincludedir includedir -runstatedir localstatedir sharedstatedir sysconfdir @@ -879,7 +878,6 @@ datadir='${datarootdir}' sysconfdir='${prefix}/etc' sharedstatedir='${prefix}/com' localstatedir='${prefix}/var' -runstatedir='${localstatedir}/run' includedir='${prefix}/include' oldincludedir='/usr/include' docdir='${datarootdir}/doc/${PACKAGE}' @@ -1132,15 +1130,6 @@ do | -silent | --silent | --silen | --sile | --sil) silent=yes ;; - -runstatedir | --runstatedir | --runstatedi | --runstated \ - | --runstate | --runstat | --runsta | --runst | --runs \ - | --run | --ru | --r) - ac_prev=runstatedir ;; - -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \ - | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \ - | --run=* | --ru=* | --r=*) - runstatedir=$ac_optarg ;; - -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb) ac_prev=sbindir ;; -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \ @@ -1278,7 +1267,7 @@ fi for ac_var in exec_prefix prefix bindir sbindir libexecdir datarootdir \ datadir sysconfdir sharedstatedir localstatedir includedir \ oldincludedir docdir infodir htmldir dvidir pdfdir psdir \ - libdir localedir mandir runstatedir + libdir localedir mandir do eval ac_val=\$$ac_var # Remove trailing slashes. @@ -1431,7 +1420,6 @@ Fine tuning of the installation directories: --sysconfdir=DIR read-only single-machine data [PREFIX/etc] --sharedstatedir=DIR modifiable architecture-independent data [PREFIX/com] --localstatedir=DIR modifiable single-machine data [PREFIX/var] - --runstatedir=DIR modifiable per-process data [LOCALSTATEDIR/run] --libdir=DIR object code libraries [EPREFIX/lib] --includedir=DIR C header files [PREFIX/include] --oldincludedir=DIR C header files for non-gcc [/usr/include] @@ -5464,7 +5452,7 @@ else We can't simply define LARGE_OFF_T to be 9223372036854775807, since some C++ compilers masquerading as C compilers incorrectly reject 9223372036854775807. */ -#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31)) +#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62)) int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 && LARGE_OFF_T % 2147483647 == 1) ? 1 : -1]; @@ -5510,7 +5498,7 @@ else We can't simply define LARGE_OFF_T to be 9223372036854775807, since some C++ compilers masquerading as C compilers incorrectly reject 9223372036854775807. */ -#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31)) +#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62)) int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 && LARGE_OFF_T % 2147483647 == 1) ? 1 : -1]; @@ -5534,7 +5522,7 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext We can't simply define LARGE_OFF_T to be 9223372036854775807, since some C++ compilers masquerading as C compilers incorrectly reject 9223372036854775807. */ -#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31)) +#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62)) int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 && LARGE_OFF_T % 2147483647 == 1) ? 1 : -1]; @@ -5579,7 +5567,7 @@ else We can't simply define LARGE_OFF_T to be 9223372036854775807, since some C++ compilers masquerading as C compilers incorrectly reject 9223372036854775807. */ -#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31)) +#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62)) int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 && LARGE_OFF_T % 2147483647 == 1) ? 1 : -1]; @@ -5603,7 +5591,7 @@ rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext We can't simply define LARGE_OFF_T to be 9223372036854775807, since some C++ compilers masquerading as C compilers incorrectly reject 9223372036854775807. */ -#define LARGE_OFF_T ((((off_t) 1 << 31) << 31) - 1 + (((off_t) 1 << 31) << 31)) +#define LARGE_OFF_T (((off_t) 1 << 62) - 1 + ((off_t) 1 << 62)) int off_t_is_large[(LARGE_OFF_T % 2147483629 == 721 && LARGE_OFF_T % 2147483647 == 1) ? 1 : -1]; From 3b60b1a8be2c9843f538051c11d257ea9bfa0431 Mon Sep 17 00:00:00 2001 From: Frederik Seiffert Date: Thu, 9 May 2019 20:16:18 +0200 Subject: [PATCH 05/11] Added support for asset loading on Android. Requires passing the activity's AssetManager object from Java to GNUstep by calling +[NSBundle setJavaAssetManager:withJNIEnv:], which then enables the following features: - NSBundle main bundle resource paths support for Android assets, e.g. for pathForResource:ofType:, URLForResource:ofType: and related methods. - NSBundle main bundle info dictionary support if Info.plist exists in Android assets. - -initWithContentsOfFile: and related methods support for reading Android assets from main bundle in various classes (e.g. NSData, NSDictionary, NSArray, etc.). - NSFileManager fileExistsAtPath:(isDirectory:) and isReadableFileAtPath: return YES for main bundle asset / asset directory paths. - NSFileHandle support for reading Android assets from main bundle. - NSDirectoryEnumerator support for enumerating Android assets from main bundle. Note that recursion into subdirectories is currently not supported by the native Android asset manager API (see https://issuetracker.google.com/issues/37002833). Also adds support for automatic NSProcessInfo initialization on Android with a fake executable path "/data/data//exe" (as Android apps don't have a real executable path), and tweaks main bundle initialization to allow that path. Main bundle resource paths are prefixed by "/data/data//Resources". --- ChangeLog | 29 +++++ Headers/Foundation/NSBundle.h | 36 ++++++ Source/GSFileHandle.h | 7 ++ Source/GSFileHandle.m | 53 +++++++++ Source/NSBundle.m | 210 ++++++++++++++++++++++++++++------ Source/NSData.m | 29 +++++ Source/NSFileManager.m | 91 +++++++++++++-- Source/NSProcessInfo.m | 25 ++++ configure | 3 + configure.ac | 3 + 10 files changed, 440 insertions(+), 46 deletions(-) diff --git a/ChangeLog b/ChangeLog index 4a854b93e..acef40349 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,32 @@ +2019-05-23 Frederik Seiffert + + * configure: + * configure.ac: + Link against libandroid on Android. + * Headers/Foundation/NSBundle.h: + * Source/NSBundle.m: + Added methods for passing Android asset manager from Java to GNUstep + and for getting AAsset/AAssetDir for given path in main bundle. Skip + app bundle suffix check on Android. Extended bundle resource paths + backbone to check for known paths directly on Android as we can't + enumerate directories. Extracted path cache cleaning into separate + method. + * Source/GSFileHandle.h: + * Source/GSFileHandle.m: + Added file handle support for reading Android assets from main bundle. + * Source/NSData.m: + Added support for reading Android assets from main bundle in + readContentsOfFile(). This is also used by all other + -initWithContentsOfFile: and related methods from other classes. + * Source/NSFileManager.m: + Added support for Android assets from main bundle in + fileExistsAtPath:isDirectory:, isReadableFileAtPath:, and + NSDirectoryEnumerator. + * Source/NSProcessInfo.m: + Added +initialize method to auto-initialize NSProcessInfo on Android + using fake executable path "/data/data//exe" (Android + apps don't have a real executable path). + 2019-05-20 Frederik Seiffert * Source/NSLog.m: Have all logs go to syslog on android. diff --git a/Headers/Foundation/NSBundle.h b/Headers/Foundation/NSBundle.h index ef97f923e..249dedd9e 100644 --- a/Headers/Foundation/NSBundle.h +++ b/Headers/Foundation/NSBundle.h @@ -36,6 +36,10 @@ extern "C" { #import #import +#ifdef __ANDROID__ +#include +#endif + @class NSString; @class NSArray; @class NSDictionary; @@ -540,6 +544,38 @@ GS_EXPORT NSString* const NSLoadedClasses; ofType: (NSString*)extension inDirectory: (NSString*)bundlePath; +/** Cleans up the path cache for the bundle. */ +- (void) cleanPathCache; + +#ifdef __ANDROID__ + +/** + * Sets the Java Android asset manager. + * The developer can call this method to enable asset loading via NSBundle. + */ ++ (void) setJavaAssetManager:(jobject)jassetManager withJNIEnv:(JNIEnv *)env; + +/** + * Returns the native Android asset manager. + */ ++ (AAssetManager *) assetManager; + +/** + * Returns the Android asset for the given path if path is in main bundle + * resources and asset exists. + * The returned object must be released using AAsset_close(). + */ ++ (AAsset *)assetForPath:(NSString *)path; + +/** + * Returns the Android asset dir for the given path if path is in main bundle + * resources and the asset directory exists. + * The returned object must be released using AAssetDir_close(). + */ ++ (AAssetDir *)assetDirForPath:(NSString *)path; + +#endif /* __ANDROID__ */ + @end #endif /* GNUSTEP */ diff --git a/Source/GSFileHandle.h b/Source/GSFileHandle.h index 0db80e679..e4e507180 100644 --- a/Source/GSFileHandle.h +++ b/Source/GSFileHandle.h @@ -35,6 +35,10 @@ #include #endif +#ifdef __ANDROID__ +#include +#endif + struct sockaddr_in; /** @@ -69,6 +73,9 @@ struct sockaddr_in; #if defined(_WIN32) WSAEVENT event; #endif +#ifdef __ANDROID__ + AAsset *asset; +#endif #endif } diff --git a/Source/GSFileHandle.m b/Source/GSFileHandle.m index 5b91f04ae..61180045c 100644 --- a/Source/GSFileHandle.m +++ b/Source/GSFileHandle.m @@ -285,6 +285,13 @@ static GSTcpTune *tune = nil; do { +#ifdef __ANDROID__ + if (asset) + { + result = AAsset_read(asset, buf, len); + } + else +#endif #if USE_ZLIB if (gzDescriptor != 0) { @@ -379,6 +386,14 @@ static GSTcpTune *tune = nil; [self ignoreReadDescriptor]; [self ignoreWriteDescriptor]; +#ifdef __ANDROID__ + if (asset) + { + AAsset_close(asset); + asset = NULL; + } + else +#endif if (closeOnDealloc == YES && descriptor != -1) { [self closeFile]; @@ -1075,6 +1090,14 @@ NSString * const GSSOCKSRecvAddr = @"GSSOCKSRecvAddr"; if (d < 0) { +#ifdef __ANDROID__ + asset = [NSBundle assetForPath:path]; + if (asset) { + readOK = YES; + return self; + } +#endif + DESTROY(self); return nil; } @@ -1645,6 +1668,13 @@ NSString * const GSSOCKSRecvAddr = @"GSSOCKSRecvAddr"; { off_t result = -1; +#ifdef __ANDROID__ + if (asset) + { + result = AAsset_seek(asset, 0, SEEK_CUR); + } + else +#endif if (isStandardFile && descriptor >= 0) { #if USE_ZLIB @@ -1669,6 +1699,13 @@ NSString * const GSSOCKSRecvAddr = @"GSSOCKSRecvAddr"; { off_t result = -1; +#ifdef __ANDROID__ + if (asset) + { + result = AAsset_seek(asset, 0, SEEK_END); + } + else +#endif if (isStandardFile && descriptor >= 0) { #if USE_ZLIB @@ -1693,6 +1730,13 @@ NSString * const GSSOCKSRecvAddr = @"GSSOCKSRecvAddr"; { off_t result = -1; +#ifdef __ANDROID__ + if (asset) + { + result = AAsset_seek(asset, (off_t)pos, SEEK_SET); + } + else +#endif if (isStandardFile && descriptor >= 0) { #if USE_ZLIB @@ -1726,6 +1770,15 @@ NSString * const GSSOCKSRecvAddr = @"GSSOCKSRecvAddr"; [self ignoreWriteDescriptor]; [self setNonBlocking: NO]; + +#ifdef __ANDROID__ + if (asset) + { + AAsset_close(asset); + asset = NULL; + } + else +#endif #if USE_ZLIB if (gzDescriptor != 0) { diff --git a/Source/NSBundle.m b/Source/NSBundle.m index 75528649e..73e6cbf08 100644 --- a/Source/NSBundle.m +++ b/Source/NSBundle.m @@ -241,6 +241,11 @@ static NSString *library_combo = nil; #endif +#ifdef __ANDROID__ +static jobject _jassetManager = NULL; +static AAssetManager *_assetManager = NULL; +#endif + /* * Try to find the absolute path of an executable. @@ -1382,6 +1387,7 @@ _bundle_load_callback(Class theClass, struct objc_category *theCategory) isNonInstalledTool = YES; } +#ifndef __ANDROID__ /* don't check suffix on Android's fake executable path */ if (isApplication == YES) { s = [path lastPathComponent]; @@ -1422,6 +1428,7 @@ _bundle_load_callback(Class theClass, struct objc_category *theCategory) } } } +#endif /* !__ANDROID__ */ if (isApplication == NO) { @@ -1805,9 +1812,6 @@ IF_NO_GC( { NSString *identifier = [self bundleIdentifier]; NSUInteger count; - NSUInteger plen = [_path length]; - NSEnumerator *enumerator; - NSString *path; [load_lock lock]; if (_bundles != nil) @@ -1835,38 +1839,7 @@ IF_NO_GC( /* Clean up path cache for this bundle. */ - [pathCacheLock lock]; - enumerator = [pathCache keyEnumerator]; - while (nil != (path = [enumerator nextObject])) - { - if (YES == [path hasPrefix: _path]) - { - if ([path length] == plen) - { - /* Remove the bundle directory path from the cache. - */ - [pathCache removeObjectForKey: path]; - } - else - { - unichar c = [path characterAtIndex: plen]; - - /* if the directory is inside the bundle, remove from cache. - */ - if ('/' == c) - { - [pathCache removeObjectForKey: path]; - } -#if defined(_WIN32) - else if ('\\' == c) - { - [pathCache removeObjectForKey: path]; - } -#endif - } - } - } - [pathCacheLock unlock]; + [self cleanPathCache]; RELEASE(_path); } TEST_RELEASE(_frameworkVersion); @@ -2144,6 +2117,48 @@ IF_NO_GC( addBundlePath(array, contents, primary, subPath, language); } } + +#ifdef __ANDROID__ + // Android: check subdir and localization directly, as AAssetDir and thereby + // NSDirectoryEnumerator doesn't list directories + NSString *originalPrimary = primary; + if (subPath) { + primary = [originalPrimary stringByAppendingPathComponent: subPath]; + contents = bundle_directory_readable(primary); + addBundlePath(array, contents, primary, nil, nil); + + if (localization) { + primary = [primary stringByAppendingPathComponent: + [localization stringByAppendingPathExtension:@"lproj"]]; + contents = bundle_directory_readable(primary); + addBundlePath(array, contents, primary, nil, nil); + } else { + NSString *subPathPrimary = primary; + enumerate = [languages objectEnumerator]; + while ((language = [enumerate nextObject])) { + primary = [subPathPrimary stringByAppendingPathComponent: + [localization stringByAppendingPathExtension:@"lproj"]]; + contents = bundle_directory_readable(primary); + addBundlePath(array, contents, primary, nil, nil); + } + } + } + if (localization) { + primary = [originalPrimary stringByAppendingPathComponent: + [localization stringByAppendingPathExtension:@"lproj"]]; + contents = bundle_directory_readable(primary); + addBundlePath(array, contents, primary, nil, nil); + } else { + enumerate = [languages objectEnumerator]; + while ((language = [enumerate nextObject])) { + primary = [originalPrimary stringByAppendingPathComponent: + [localization stringByAppendingPathExtension:@"lproj"]]; + contents = bundle_directory_readable(primary); + addBundlePath(array, contents, primary, nil, nil); + } + } +#endif /* __ANDROID__ */ + primary = rootPath; contents = bundle_directory_readable(primary); addBundlePath(array, contents, primary, subPath, nil); @@ -2525,6 +2540,10 @@ IF_NO_GC( locale = [[locale lastPathComponent] stringByDeletingPathExtension]; [array addObject: locale]; } +#ifdef __ANDROID__ + // TODO: check known languages for existance directly, as AAssetDir and thereby + // NSDirectoryEnumerator doesn't list directories +#endif return GS_IMMUTABLE(array); } @@ -3202,5 +3221,126 @@ IF_NO_GC( return path; } +- (void)cleanPathCache +{ + NSUInteger plen = [_path length]; + NSEnumerator *enumerator; + NSString *path; + + [pathCacheLock lock]; + enumerator = [pathCache keyEnumerator]; + while (nil != (path = [enumerator nextObject])) + { + if (YES == [path hasPrefix: _path]) + { + if ([path length] == plen) + { + /* Remove the bundle directory path from the cache. + */ + [pathCache removeObjectForKey: path]; + } + else + { + unichar c = [path characterAtIndex: plen]; + + /* if the directory is inside the bundle, remove from cache. + */ + if ('/' == c) + { + [pathCache removeObjectForKey: path]; + } +#if defined(_WIN32) + else if ('\\' == c) + { + [pathCache removeObjectForKey: path]; + } +#endif + } + } + } + [pathCacheLock unlock]; + + /* also destroy cached variables depending on bundle paths */ + DESTROY(_infoDict); + DESTROY(_localizations); +} + +#ifdef __ANDROID__ + ++ (AAssetManager *)assetManager +{ + return _assetManager; +} + ++ (void)setJavaAssetManager:(jobject)jassetManager withJNIEnv:(JNIEnv *)env +{ + // create global reference to Java asset manager to prevent garbage + // collection + _jassetManager = (*env)->NewGlobalRef(env, jassetManager); + + // get native asset manager (may be shared across multiple threads) + _assetManager = AAssetManager_fromJava(env, _jassetManager); + + // clean main bundle path cache in case it was accessed before + [_mainBundle cleanPathCache]; +} + ++ (AAsset *)assetForPath:(NSString *)path +{ + AAsset *asset = NULL; + + if (_assetManager && _mainBundle) + { + NSString *resourcePath = [_mainBundle resourcePath]; + + if ([path hasPrefix:resourcePath] && [path length] > [resourcePath length]) + { + NSString *assetPath = [path substringFromIndex:[resourcePath length]+1]; + + asset = AAssetManager_open(_assetManager, + [assetPath fileSystemRepresentation], AASSET_MODE_BUFFER); + } + } + + return asset; +} + ++ (AAssetDir *)assetDirForPath:(NSString *)path +{ + AAssetDir *assetDir = NULL; + + if (_assetManager && _mainBundle) + { + NSString *resourcePath = [_mainBundle resourcePath]; + + if ([path hasPrefix:resourcePath]) + { + NSString *assetPath = @""; + if ([path length] > [resourcePath length]) { + assetPath = [path substringFromIndex:[resourcePath length] + 1]; + } + + assetDir = AAssetManager_openDir(_assetManager, + [assetPath fileSystemRepresentation]); + + if (assetDir) { + // AAssetManager_openDir() always returns an object, so we check if + // the directory exists by ensuring it contains a file + BOOL exists = AAssetDir_getNextFileName(assetDir) != NULL; + if (exists) { + AAssetDir_rewind(assetDir); + } else { + AAssetDir_close(assetDir); + assetDir = NULL; + } + } + } + } + + return assetDir; +} + +#endif /* __ANDROID__ */ + @end diff --git a/Source/NSData.m b/Source/NSData.m index 1a3f507a0..0140a8c8a 100644 --- a/Source/NSData.m +++ b/Source/NSData.m @@ -253,6 +253,35 @@ readContentsOfFile(NSString *path, void **buf, off_t *len, NSZone *zone) NSWarnFLog(@"Open (%@) attempt failed - bad path", path); return NO; } + +#ifdef __ANDROID__ + // Android: try using asset manager if path is in main bundle resources + AAsset *asset = [NSBundle assetForPath:path]; + if (asset) { + fileLength = AAsset_getLength(asset); + + tmp = NSZoneMalloc(zone, fileLength); + if (tmp == 0) { + NSLog(@"Malloc failed for file (%@) of length %jd - %@", path, + (intmax_t)fileLength, [NSError _last]); + AAsset_close(asset); + goto failure; + } + + int result = AAsset_read(asset, tmp, fileLength); + AAsset_close(asset); + + if (result < 0) { + NSWarnFLog(@"read of file (%@) contents failed - %@", path, + [NSError errorWithDomain:NSPOSIXErrorDomain code:result userInfo:nil]); + goto failure; + } + + *buf = tmp; + *len = fileLength; + return YES; + } +#endif /* __ANDROID__ */ att = [mgr fileAttributesAtPath: path traverseLink: YES]; if (nil == att) diff --git a/Source/NSFileManager.m b/Source/NSFileManager.m index c741184ca..5e4a8512e 100644 --- a/Source/NSFileManager.m +++ b/Source/NSFileManager.m @@ -1652,6 +1652,24 @@ static NSStringEncoding defaultEncoding; if (_STAT(lpath, &statbuf) != 0) { +#ifdef __ANDROID__ + // Android: try using asset manager if path is in main bundle resources + AAsset *asset = [NSBundle assetForPath:path]; + if (asset) { + AAsset_close(asset); + return YES; + } + + AAssetDir *assetDir = [NSBundle assetDirForPath:path]; + if (assetDir) { + AAssetDir_close(assetDir); + if (isDirectory) { + *isDirectory = YES; + } + return YES; + } +#endif + return NO; } @@ -1700,6 +1718,16 @@ static NSStringEncoding defaultEncoding; { return YES; } + +#ifdef __ANDROID__ + // Android: try using asset manager if path is in main bundle resources + AAsset *asset = [NSBundle assetForPath:path]; + if (asset) { + AAsset_close(asset); + return YES; + } +#endif + return NO; } #endif @@ -2379,6 +2407,9 @@ static NSStringEncoding defaultEncoding; typedef struct _GSEnumeratedDirectory { NSString *path; _DIR *pointer; +#ifdef __ANDROID__ + AAssetDir *assetDir; +#endif } GSEnumeratedDirectory; @@ -2386,6 +2417,11 @@ static inline void gsedRelease(GSEnumeratedDirectory X) { DESTROY(X.path); _CLOSEDIR(X.pointer); +#ifdef __ANDROID__ + if (X.assetDir) { + AAssetDir_close(X.assetDir); + } +#endif } #define GSI_ARRAY_TYPES 0 @@ -2443,12 +2479,26 @@ static inline void gsedRelease(GSEnumeratedDirectory X) localPath = [_mgr fileSystemRepresentationWithPath: path]; dir_pointer = _OPENDIR(localPath); + +#ifdef __ANDROID__ + AAssetDir *assetDir = NULL; + if (!dir_pointer) { + // Android: try using asset manager if path is in main bundle resources + assetDir = [NSBundle assetDirForPath:path]; + } + + if (dir_pointer || assetDir) +#else if (dir_pointer) +#endif { GSIArrayItem item; item.ext.path = @""; item.ext.pointer = dir_pointer; +#ifdef __ANDROID__ + item.ext.assetDir = assetDir; +#endif GSIArrayAddItem(_stack, item); } @@ -2535,35 +2585,54 @@ static inline void gsedRelease(GSEnumeratedDirectory X) while (GSIArrayCount(_stack) > 0) { GSEnumeratedDirectory dir = GSIArrayLastItem(_stack).ext; - struct _DIRENT *dirbuf; struct _STATB statbuf; +#if defined(_WIN32) + const wchar_t *dirname = NULL; +#else + const char *dirname = NULL; +#endif - dirbuf = _READDIR(dir.pointer); +#ifdef __ANDROID__ + if (dir.assetDir) + { + // This will only return files and not directories, which means that + // recursion is not supported. + // See https://issuetracker.google.com/issues/37002833 + dirname = AAssetDir_getNextFileName(dir.assetDir); + } + else if (dir.pointer) +#endif + { + struct _DIRENT *dirbuf = _READDIR(dir.pointer); + if (dirbuf) { + dirname = dirbuf->d_name; + } + } - if (dirbuf) + if (dirname) { #if defined(_WIN32) /* Skip "." and ".." directory entries */ - if (wcscmp(dirbuf->d_name, L".") == 0 - || wcscmp(dirbuf->d_name, L"..") == 0) + if (wcscmp(dirname, L".") == 0 + || wcscmp(dirname, L"..") == 0) { continue; } /* Name of file to return */ returnFileName = [_mgr - stringWithFileSystemRepresentation: dirbuf->d_name - length: wcslen(dirbuf->d_name)]; + stringWithFileSystemRepresentation: dirname + length: wcslen(dirname)]; #else /* Skip "." and ".." directory entries */ - if (strcmp(dirbuf->d_name, ".") == 0 - || strcmp(dirbuf->d_name, "..") == 0) + if (strcmp(dirname, ".") == 0 + || strcmp(dirname, "..") == 0) { continue; } /* Name of file to return */ returnFileName = [_mgr - stringWithFileSystemRepresentation: dirbuf->d_name - length: strlen(dirbuf->d_name)]; + stringWithFileSystemRepresentation: dirname + length: strlen(dirname)]; #endif /* if we have a null FileName something went wrong (charset?) and we skip it */ if (returnFileName == nil) diff --git a/Source/NSProcessInfo.m b/Source/NSProcessInfo.m index da94da15c..40ae03896 100644 --- a/Source/NSProcessInfo.m +++ b/Source/NSProcessInfo.m @@ -943,6 +943,31 @@ extern char **__libc_argv; } } +#elif defined(__ANDROID__) + ++ (void) initialize +{ + if (nil == procLock) procLock = [NSRecursiveLock new]; + if (self == [NSProcessInfo class] + && !_gnu_processName && !_gnu_arguments && !_gnu_environment) + { + FILE *f = fopen("/proc/self/cmdline", "r"); + if (f) { + char identifier[BUFSIZ]; + fgets(identifier, sizeof(identifier), f); + fclose(f); + + // construct fake executable path + char *arg0; + asprintf(&arg0, "/data/data/%s/exe", identifier); + + char *argv[] = { arg0 }; + _gnu_process_args(sizeof(argv)/sizeof(char *), argv, NULL); + } else { + fprintf(stderr, "Failed to read cmdline\n"); + } + } +} #else + (void) initialize diff --git a/configure b/configure index f5fc4c9d8..431d93732 100755 --- a/configure +++ b/configure @@ -5996,6 +5996,9 @@ case "$target_os" in LDFLAGS="$LDFLAGS -L/usr/local/lib";; netbsd*) CPPFLAGS="$CPPFLAGS -I/usr/pkg/include" LDFLAGS="$LDFLAGS -Wl,-R/usr/pkg/lib -L/usr/pkg/lib";; + linux-android* ) + # link against libandroid for native application APIs + LIBS="$LIBS -landroid";; esac #-------------------------------------------------------------------- diff --git a/configure.ac b/configure.ac index f7633aad8..0636c0350 100644 --- a/configure.ac +++ b/configure.ac @@ -1246,6 +1246,9 @@ case "$target_os" in LDFLAGS="$LDFLAGS -L/usr/local/lib";; netbsd*) CPPFLAGS="$CPPFLAGS -I/usr/pkg/include" LDFLAGS="$LDFLAGS -Wl,-R/usr/pkg/lib -L/usr/pkg/lib";; + linux-android* ) + # link against libandroid for native application APIs + LIBS="$LIBS -landroid";; esac #-------------------------------------------------------------------- From ba4948fd97341c0fbd0575bb156405f72ed97c77 Mon Sep 17 00:00:00 2001 From: Frederik Seiffert Date: Fri, 24 May 2019 09:28:10 +0200 Subject: [PATCH 06/11] Moved Android asset reading code path in NSData. No reason to get path file system representation first. --- Source/NSData.m | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Source/NSData.m b/Source/NSData.m index 0140a8c8a..666b89c95 100644 --- a/Source/NSData.m +++ b/Source/NSData.m @@ -242,17 +242,6 @@ readContentsOfFile(NSString *path, void **buf, off_t *len, NSZone *zone) void *tmp = 0; int c; off_t fileLength; - -#if defined(_WIN32) - thePath = (const unichar*)[path fileSystemRepresentation]; -#else - thePath = [path fileSystemRepresentation]; -#endif - if (thePath == 0) - { - NSWarnFLog(@"Open (%@) attempt failed - bad path", path); - return NO; - } #ifdef __ANDROID__ // Android: try using asset manager if path is in main bundle resources @@ -283,6 +272,17 @@ readContentsOfFile(NSString *path, void **buf, off_t *len, NSZone *zone) } #endif /* __ANDROID__ */ +#if defined(_WIN32) + thePath = (const unichar*)[path fileSystemRepresentation]; +#else + thePath = [path fileSystemRepresentation]; +#endif + if (thePath == 0) + { + NSWarnFLog(@"Open (%@) attempt failed - bad path", path); + return NO; + } + att = [mgr fileAttributesAtPath: path traverseLink: YES]; if (nil == att) { From 9f0187610288d9484b0ea4dc6805ad77a5c7ad70 Mon Sep 17 00:00:00 2001 From: Frederik Seiffert Date: Fri, 24 May 2019 09:29:24 +0200 Subject: [PATCH 07/11] Added option to specify Android asset reading mode. --- Headers/Foundation/NSBundle.h | 9 +++++++++ Source/GSFileHandle.m | 2 +- Source/NSBundle.m | 7 ++++++- Source/NSData.m | 2 +- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/Headers/Foundation/NSBundle.h b/Headers/Foundation/NSBundle.h index 249dedd9e..1f1b067b3 100644 --- a/Headers/Foundation/NSBundle.h +++ b/Headers/Foundation/NSBundle.h @@ -563,10 +563,19 @@ GS_EXPORT NSString* const NSLoadedClasses; /** * Returns the Android asset for the given path if path is in main bundle * resources and asset exists. + * Uses `AASSET_MODE_UNKNOWN` to open the asset if it exists. * The returned object must be released using AAsset_close(). */ + (AAsset *)assetForPath:(NSString *)path; +/** + * Returns the Android asset for the given path if path is in main bundle + * resources and asset exists. + * Uses the given mode to open the AAsset if it exists. + * The returned object must be released using AAsset_close(). + */ ++ (AAsset *)assetForPath:(NSString *)path withMode:(int)mode; + /** * Returns the Android asset dir for the given path if path is in main bundle * resources and the asset directory exists. diff --git a/Source/GSFileHandle.m b/Source/GSFileHandle.m index 61180045c..7823799b3 100644 --- a/Source/GSFileHandle.m +++ b/Source/GSFileHandle.m @@ -1091,7 +1091,7 @@ NSString * const GSSOCKSRecvAddr = @"GSSOCKSRecvAddr"; if (d < 0) { #ifdef __ANDROID__ - asset = [NSBundle assetForPath:path]; + asset = [NSBundle assetForPath:path withMode:AASSET_MODE_RANDOM]; if (asset) { readOK = YES; return self; diff --git a/Source/NSBundle.m b/Source/NSBundle.m index 73e6cbf08..63087d23e 100644 --- a/Source/NSBundle.m +++ b/Source/NSBundle.m @@ -3286,6 +3286,11 @@ IF_NO_GC( } + (AAsset *)assetForPath:(NSString *)path +{ + return [self assetForPath:path withMode:AASSET_MODE_UNKNOWN]; +} + ++ (AAsset *)assetForPath:(NSString *)path withMode:(int)mode { AAsset *asset = NULL; @@ -3298,7 +3303,7 @@ IF_NO_GC( NSString *assetPath = [path substringFromIndex:[resourcePath length]+1]; asset = AAssetManager_open(_assetManager, - [assetPath fileSystemRepresentation], AASSET_MODE_BUFFER); + [assetPath fileSystemRepresentation], mode); } } diff --git a/Source/NSData.m b/Source/NSData.m index 666b89c95..1e711aa29 100644 --- a/Source/NSData.m +++ b/Source/NSData.m @@ -245,7 +245,7 @@ readContentsOfFile(NSString *path, void **buf, off_t *len, NSZone *zone) #ifdef __ANDROID__ // Android: try using asset manager if path is in main bundle resources - AAsset *asset = [NSBundle assetForPath:path]; + AAsset *asset = [NSBundle assetForPath:path withMode:AASSET_MODE_BUFFER]; if (asset) { fileLength = AAsset_getLength(asset); From ca76053c8e4ee4b2bbb1d86d7c0dc793dbd568a3 Mon Sep 17 00:00:00 2001 From: Frederik Seiffert Date: Fri, 24 May 2019 09:53:01 +0200 Subject: [PATCH 08/11] Fixed Android asset language logic in NSBundle. --- Source/NSBundle.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/NSBundle.m b/Source/NSBundle.m index 63087d23e..f8c9946fb 100644 --- a/Source/NSBundle.m +++ b/Source/NSBundle.m @@ -2137,7 +2137,7 @@ IF_NO_GC( enumerate = [languages objectEnumerator]; while ((language = [enumerate nextObject])) { primary = [subPathPrimary stringByAppendingPathComponent: - [localization stringByAppendingPathExtension:@"lproj"]]; + [language stringByAppendingPathExtension:@"lproj"]]; contents = bundle_directory_readable(primary); addBundlePath(array, contents, primary, nil, nil); } @@ -2152,7 +2152,7 @@ IF_NO_GC( enumerate = [languages objectEnumerator]; while ((language = [enumerate nextObject])) { primary = [originalPrimary stringByAppendingPathComponent: - [localization stringByAppendingPathExtension:@"lproj"]]; + [language stringByAppendingPathExtension:@"lproj"]]; contents = bundle_directory_readable(primary); addBundlePath(array, contents, primary, nil, nil); } From 409030a3679ef44cc73483de6ce20a2ec6ed88ab Mon Sep 17 00:00:00 2001 From: Frederik Seiffert Date: Fri, 24 May 2019 10:56:34 +0200 Subject: [PATCH 09/11] Extended NSBundle localizations method for Android. --- ChangeLog | 12 +++++++----- Source/NSBundle.m | 19 ++++++++++++++++--- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/ChangeLog b/ChangeLog index acef40349..89da2480a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -6,11 +6,13 @@ * Headers/Foundation/NSBundle.h: * Source/NSBundle.m: Added methods for passing Android asset manager from Java to GNUstep - and for getting AAsset/AAssetDir for given path in main bundle. Skip - app bundle suffix check on Android. Extended bundle resource paths - backbone to check for known paths directly on Android as we can't - enumerate directories. Extracted path cache cleaning into separate - method. + and for getting AAsset/AAssetDir for given path in main bundle. + Skip app bundle suffix check on Android. Extended bundle resource + paths backbone to check for known paths directly on Android as we + can't enumerate directories. + Extended -localizations method to check for known localizations + directly (requires setting userLanguages in NSUserDefaults). + Extracted path cache cleaning into separate method. * Source/GSFileHandle.h: * Source/GSFileHandle.m: Added file handle support for reading Android assets from main bundle. diff --git a/Source/NSBundle.m b/Source/NSBundle.m index f8c9946fb..5e5dd84a0 100644 --- a/Source/NSBundle.m +++ b/Source/NSBundle.m @@ -2540,10 +2540,23 @@ IF_NO_GC( locale = [[locale lastPathComponent] stringByDeletingPathExtension]; [array addObject: locale]; } + #ifdef __ANDROID__ - // TODO: check known languages for existance directly, as AAssetDir and thereby - // NSDirectoryEnumerator doesn't list directories -#endif + // Android: Check known languages for localizations directly, as AAssetDir + // and thereby NSDirectoryEnumerator doesn't list directories and the above + // call to list lproj resources will therefore come up empty. + NSArray *languages = [[NSUserDefaults standardUserDefaults] + stringArrayForKey: @"NSLanguages"]; + + for (locale in languages) { + NSString *path = [self pathForResource:@"Localizable" ofType:@"strings" + inDirectory:nil forLocalization:locale]; + if (path) { + [array addObject: locale]; + } + } +#endif /* __ANDROID__ */ + return GS_IMMUTABLE(array); } From 75850de0ffba303d2791e8023ecc5d6ddb91954e Mon Sep 17 00:00:00 2001 From: Frederik Seiffert Date: Sat, 1 Jun 2019 21:15:27 +0200 Subject: [PATCH 10/11] Extended Android asset support Added NSFileManager support for copying assets and reading their attributes. --- ChangeLog | 5 +-- Source/NSFileManager.m | 74 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 71 insertions(+), 8 deletions(-) diff --git a/ChangeLog b/ChangeLog index 89da2480a..f04641796 100644 --- a/ChangeLog +++ b/ChangeLog @@ -22,8 +22,9 @@ -initWithContentsOfFile: and related methods from other classes. * Source/NSFileManager.m: Added support for Android assets from main bundle in - fileExistsAtPath:isDirectory:, isReadableFileAtPath:, and - NSDirectoryEnumerator. + fileExistsAtPath:isDirectory:, isReadableFileAtPath:, + NSDirectoryEnumerator, and copying from assets. Extended + GSAttrDictionary with basic support for Android assets. * Source/NSProcessInfo.m: Added +initialize method to auto-initialize NSProcessInfo on Android using fake executable path "/data/data//exe" (Android diff --git a/Source/NSFileManager.m b/Source/NSFileManager.m index 5e4a8512e..98a88b992 100644 --- a/Source/NSFileManager.m +++ b/Source/NSFileManager.m @@ -241,7 +241,7 @@ struct _STATB statbuf; _CHAR _path[0]; } -+ (NSDictionary*) attributesAt: (const _CHAR*)lpath ++ (NSDictionary*) attributesAt: (NSString *)path traverseLink: (BOOL)traverse; @end @@ -1957,8 +1957,7 @@ static NSStringEncoding defaultEncoding; { NSDictionary *d; - d = [GSAttrDictionaryClass attributesAt: - [self fileSystemRepresentationWithPath: path] traverseLink: flag]; + d = [GSAttrDictionaryClass attributesAt: path traverseLink: flag]; return d; } @@ -2040,8 +2039,7 @@ static NSStringEncoding defaultEncoding; NSDictionary *d; DESTROY(_lastError); - d = [GSAttrDictionaryClass attributesAt: - [self fileSystemRepresentationWithPath: path] traverseLink: NO]; + d = [GSAttrDictionaryClass attributesAt: path traverseLink: NO]; if (error != NULL) { @@ -2910,6 +2908,9 @@ static inline void gsedRelease(GSEnumeratedDirectory X) int rbytes; int wbytes; char buffer[bufsize]; +#ifdef __ANDROID__ + AAsset *asset = NULL; +#endif attributes = [self fileAttributesAtPath: source traverseLink: NO]; if (nil == attributes) @@ -2928,7 +2929,15 @@ static inline void gsedRelease(GSEnumeratedDirectory X) /* Open the source file. In case of error call the handler. */ sourceFd = open([self fileSystemRepresentationWithPath: source], GSBINIO|O_RDONLY); +#ifdef __ANDROID__ + if (sourceFd < 0) { + // Android: try using asset manager if path is in main bundle resources + asset = [NSBundle assetForPath:source withMode:AASSET_MODE_STREAMING]; + } + if (sourceFd < 0 && asset == NULL) +#else if (sourceFd < 0) +#endif { return [self _proceedAccordingToHandler: handler forError: @"cannot open file for reading" @@ -2942,6 +2951,12 @@ static inline void gsedRelease(GSEnumeratedDirectory X) GSBINIO|O_WRONLY|O_CREAT|O_TRUNC, fileMode); if (destFd < 0) { +#ifdef __ANDROID__ + if (asset) { + AAsset_close(asset); + } + else +#endif close (sourceFd); return [self _proceedAccordingToHandler: handler @@ -2955,6 +2970,12 @@ static inline void gsedRelease(GSEnumeratedDirectory X) file. In case of errors call the handler and abort the operation. */ for (i = 0; i < fileSize; i += rbytes) { +#ifdef __ANDROID__ + if (asset) { + rbytes = AAsset_read(asset, buffer, bufsize); + } + else +#endif rbytes = read (sourceFd, buffer, bufsize); if (rbytes <= 0) { @@ -2962,6 +2983,12 @@ static inline void gsedRelease(GSEnumeratedDirectory X) { break; // End of input file } +#ifdef __ANDROID__ + if (asset) { + AAsset_close(asset); + } + else +#endif close (sourceFd); close (destFd); @@ -2975,6 +3002,12 @@ static inline void gsedRelease(GSEnumeratedDirectory X) wbytes = write (destFd, buffer, rbytes); if (wbytes != rbytes) { +#ifdef __ANDROID__ + if (asset) { + AAsset_close(asset); + } + else +#endif close (sourceFd); close (destFd); @@ -2985,6 +3018,12 @@ static inline void gsedRelease(GSEnumeratedDirectory X) toPath: destination]; } } +#ifdef __ANDROID__ + if (asset) { + AAsset_close(asset); + } + else +#endif close (sourceFd); close (destFd); @@ -3321,12 +3360,16 @@ static inline void gsedRelease(GSEnumeratedDirectory X) static NSSet *fileKeys = nil; -+ (NSDictionary*) attributesAt: (const _CHAR*)lpath ++ (NSDictionary*) attributesAt: (NSString *)path traverseLink: (BOOL)traverse { GSAttrDictionary *d; unsigned l = 0; unsigned i; + const _CHAR *lpath = [defaultManager fileSystemRepresentationWithPath: path]; +#ifdef __ANDROID__ + AAsset *asset = NULL; +#endif if (lpath == 0 || *lpath == 0) { @@ -3344,6 +3387,11 @@ static NSSet *fileKeys = nil; { if (lstat(lpath, &d->statbuf) != 0) { +#ifdef __ANDROID__ + // Android: try using asset manager if path is in main bundle resources + asset = [NSBundle assetForPath:path]; + if (asset == NULL) +#endif DESTROY(d); } } @@ -3351,6 +3399,11 @@ static NSSet *fileKeys = nil; #endif if (_STAT(lpath, &d->statbuf) != 0) { +#ifdef __ANDROID__ + // Android: try using asset manager if path is in main bundle resources + asset = [NSBundle assetForPath:path]; + if (asset == NULL) +#endif DESTROY(d); } if (d != nil) @@ -3359,6 +3412,15 @@ static NSSet *fileKeys = nil; { d->_path[i] = lpath[i]; } +#ifdef __ANDROID__ + if (asset) { + // set some basic stat values for Android assets + memset(&d->statbuf, 0, sizeof(d->statbuf)); + d->statbuf.st_mode = S_IRUSR; + d->statbuf.st_size = AAsset_getLength(asset); + AAsset_close(asset); + } +#endif } return AUTORELEASE(d); } From 2425c42acecaac84ae79098f8db208aefee57553 Mon Sep 17 00:00:00 2001 From: Richard Frith-Macdonald Date: Thu, 6 Jun 2019 14:16:30 +0100 Subject: [PATCH 11/11] Cosmetic tweaks to match coding style --- Headers/Foundation/NSBundle.h | 14 +-- Source/GSFileHandle.m | 49 ++++---- Source/GSTimSort.m | 14 +-- Source/NSBundle.m | 207 +++++++++++++++++++--------------- Source/NSData.m | 49 ++++---- Source/NSFileManager.m | 157 +++++++++++++++----------- Source/NSObject.m | 46 +++++++- Source/NSProcessInfo.m | 30 ++--- 8 files changed, 332 insertions(+), 234 deletions(-) diff --git a/Headers/Foundation/NSBundle.h b/Headers/Foundation/NSBundle.h index 1f1b067b3..76b8bbb4f 100644 --- a/Headers/Foundation/NSBundle.h +++ b/Headers/Foundation/NSBundle.h @@ -553,7 +553,7 @@ GS_EXPORT NSString* const NSLoadedClasses; * Sets the Java Android asset manager. * The developer can call this method to enable asset loading via NSBundle. */ -+ (void) setJavaAssetManager:(jobject)jassetManager withJNIEnv:(JNIEnv *)env; ++ (void) setJavaAssetManager: (jobject)jassetManager withJNIEnv: (JNIEnv *)env; /** * Returns the native Android asset manager. @@ -566,7 +566,7 @@ GS_EXPORT NSString* const NSLoadedClasses; * Uses `AASSET_MODE_UNKNOWN` to open the asset if it exists. * The returned object must be released using AAsset_close(). */ -+ (AAsset *)assetForPath:(NSString *)path; ++ (AAsset *) assetForPath: (NSString *)path; /** * Returns the Android asset for the given path if path is in main bundle @@ -574,14 +574,14 @@ GS_EXPORT NSString* const NSLoadedClasses; * Uses the given mode to open the AAsset if it exists. * The returned object must be released using AAsset_close(). */ -+ (AAsset *)assetForPath:(NSString *)path withMode:(int)mode; ++ (AAsset *) assetForPath: (NSString *)path withMode: (int)mode; /** * Returns the Android asset dir for the given path if path is in main bundle * resources and the asset directory exists. * The returned object must be released using AAssetDir_close(). */ -+ (AAssetDir *)assetDirForPath:(NSString *)path; ++ (AAssetDir *) assetDirForPath: (NSString *)path; #endif /* __ANDROID__ */ @@ -648,7 +648,7 @@ GS_EXPORT NSString* const NSLoadedClasses; *

*/ #define NSLocalizedString(key, comment) \ - [[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:nil] + [[NSBundle mainBundle] localizedStringForKey: (key) value: @"" table: nil] /** * This function (macro) does the same as @@ -666,7 +666,7 @@ GS_EXPORT NSString* const NSLoadedClasses; * different table. */ #define NSLocalizedStringFromTable(key, tbl, comment) \ - [[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:(tbl)] + [[NSBundle mainBundle] localizedStringForKey: (key) value: @"" table: (tbl)] /** * This function is the full-blown localization function (it @@ -682,7 +682,7 @@ GS_EXPORT NSString* const NSLoadedClasses; * use when translating the string. */ #define NSLocalizedStringFromTableInBundle(key, tbl, bundle, comment) \ - [bundle localizedStringForKey:(key) value:@"" table:(tbl)] + [bundle localizedStringForKey: (key) value: @"" table: (tbl)] #if defined(__cplusplus) diff --git a/Source/GSFileHandle.m b/Source/GSFileHandle.m index 7823799b3..cb814ac4a 100644 --- a/Source/GSFileHandle.m +++ b/Source/GSFileHandle.m @@ -287,9 +287,9 @@ static GSTcpTune *tune = nil; { #ifdef __ANDROID__ if (asset) - { - result = AAsset_read(asset, buf, len); - } + { + result = AAsset_read(asset, buf, len); + } else #endif #if USE_ZLIB @@ -388,10 +388,10 @@ static GSTcpTune *tune = nil; #ifdef __ANDROID__ if (asset) - { - AAsset_close(asset); - asset = NULL; - } + { + AAsset_close(asset); + asset = NULL; + } else #endif if (closeOnDealloc == YES && descriptor != -1) @@ -1092,10 +1092,11 @@ NSString * const GSSOCKSRecvAddr = @"GSSOCKSRecvAddr"; { #ifdef __ANDROID__ asset = [NSBundle assetForPath:path withMode:AASSET_MODE_RANDOM]; - if (asset) { - readOK = YES; - return self; - } + if (asset) + { + readOK = YES; + return self; + } #endif DESTROY(self); @@ -1670,9 +1671,9 @@ NSString * const GSSOCKSRecvAddr = @"GSSOCKSRecvAddr"; #ifdef __ANDROID__ if (asset) - { - result = AAsset_seek(asset, 0, SEEK_CUR); - } + { + result = AAsset_seek(asset, 0, SEEK_CUR); + } else #endif if (isStandardFile && descriptor >= 0) @@ -1701,9 +1702,9 @@ NSString * const GSSOCKSRecvAddr = @"GSSOCKSRecvAddr"; #ifdef __ANDROID__ if (asset) - { - result = AAsset_seek(asset, 0, SEEK_END); - } + { + result = AAsset_seek(asset, 0, SEEK_END); + } else #endif if (isStandardFile && descriptor >= 0) @@ -1732,9 +1733,9 @@ NSString * const GSSOCKSRecvAddr = @"GSSOCKSRecvAddr"; #ifdef __ANDROID__ if (asset) - { - result = AAsset_seek(asset, (off_t)pos, SEEK_SET); - } + { + result = AAsset_seek(asset, (off_t)pos, SEEK_SET); + } else #endif if (isStandardFile && descriptor >= 0) @@ -1773,10 +1774,10 @@ NSString * const GSSOCKSRecvAddr = @"GSSOCKSRecvAddr"; #ifdef __ANDROID__ if (asset) - { - AAsset_close(asset); - asset = NULL; - } + { + AAsset_close(asset); + asset = NULL; + } else #endif #if USE_ZLIB diff --git a/Source/GSTimSort.m b/Source/GSTimSort.m index fe37f636d..2a9b369e1 100644 --- a/Source/GSTimSort.m +++ b/Source/GSTimSort.m @@ -627,15 +627,11 @@ descriptorOrComparator: (id)descriptorOrComparator while (stackSize > 1) { NSInteger n = stackSize -2; - if ( (n >= 1 - && runStack[n-1].length <= (runStack[n].length - + runStack[n+1].length) - ) - || (n >= 2 - && runStack[n-2].length <= (runStack[n].length - + runStack[n-1].length) - ) - ) + + if ((n >= 1 && runStack[n-1].length + <= (runStack[n].length + runStack[n+1].length)) + || (n >= 2 && runStack[n-2].length + <= (runStack[n].length + runStack[n-1].length))) { if (runStack[n-1].length < runStack[n+1].length) { diff --git a/Source/NSBundle.m b/Source/NSBundle.m index 5e5dd84a0..309b7e25a 100644 --- a/Source/NSBundle.m +++ b/Source/NSBundle.m @@ -269,17 +269,17 @@ AbsolutePathOfExecutable(NSString *path, BOOL atLaunch) NSString *result = nil; env = [[NSProcessInfo processInfo] environment]; - pathlist = [env objectForKey:@"PATH"]; + pathlist = [env objectForKey: @"PATH"]; /* Windows 2000 and perhaps others have "Path" not "PATH" */ if (pathlist == nil) { - pathlist = [env objectForKey:@"Path"]; + pathlist = [env objectForKey: @"Path"]; } #if defined(_WIN32) - patharr = [pathlist componentsSeparatedByString:@";"]; + patharr = [pathlist componentsSeparatedByString: @";"]; #else - patharr = [pathlist componentsSeparatedByString:@":"]; + patharr = [pathlist componentsSeparatedByString: @":"]; #endif /* Add . if not already in path */ if ([patharr indexOfObject: @"."] == NSNotFound) @@ -290,7 +290,7 @@ AbsolutePathOfExecutable(NSString *path, BOOL atLaunch) patharr = [patharr objectEnumerator]; while (nil != (prefix = [patharr nextObject])) { - if ([prefix isEqual:@"."]) + if ([prefix isEqual: @"."]) { if (atLaunch == YES) { @@ -548,13 +548,14 @@ _find_framework(NSString *name) { NSArray *paths; NSFileManager *file_mgr = manager(); - NSString *file_name = [name stringByAppendingPathExtension:@"framework"]; + NSString *file_name; NSString *file_path; NSString *path; NSEnumerator *enumerator; NSCParameterAssert(name != nil); + file_name = [name stringByAppendingPathExtension: @"framework"]; paths = NSSearchPathForDirectoriesInDomains(GSFrameworksDirectory, NSAllDomainsMask,YES); @@ -927,9 +928,9 @@ _find_main_bundle_for_tool(NSString *toolName) for (j = 0; j < [l count]; j++) { if ([[l objectAtIndex: j] pointerValue] - == [[b objectAtIndex:i] pointerValue]) + == [[b objectAtIndex: i] pointerValue]) { - [l removeObjectAtIndex:j]; + [l removeObjectAtIndex: j]; } } } @@ -2119,44 +2120,55 @@ IF_NO_GC( } #ifdef __ANDROID__ - // Android: check subdir and localization directly, as AAssetDir and thereby - // NSDirectoryEnumerator doesn't list directories + /* Android: check subdir and localization directly, as AAssetDir and thereby + * NSDirectoryEnumerator doesn't list directories + */ NSString *originalPrimary = primary; - if (subPath) { - primary = [originalPrimary stringByAppendingPathComponent: subPath]; - contents = bundle_directory_readable(primary); - addBundlePath(array, contents, primary, nil, nil); - - if (localization) { - primary = [primary stringByAppendingPathComponent: - [localization stringByAppendingPathExtension:@"lproj"]]; + if (subPath) + { + primary = [originalPrimary stringByAppendingPathComponent: subPath]; contents = bundle_directory_readable(primary); addBundlePath(array, contents, primary, nil, nil); - } else { - NSString *subPathPrimary = primary; - enumerate = [languages objectEnumerator]; - while ((language = [enumerate nextObject])) { - primary = [subPathPrimary stringByAppendingPathComponent: - [language stringByAppendingPathExtension:@"lproj"]]; - contents = bundle_directory_readable(primary); - addBundlePath(array, contents, primary, nil, nil); - } + + if (localization) + { + primary = [primary stringByAppendingPathComponent: + [localization stringByAppendingPathExtension: @"lproj"]]; + contents = bundle_directory_readable(primary); + addBundlePath(array, contents, primary, nil, nil); + } + else + { + NSString *subPathPrimary = primary; + + enumerate = [languages objectEnumerator]; + while ((language = [enumerate nextObject])) + { + primary = [subPathPrimary stringByAppendingPathComponent: + [language stringByAppendingPathExtension: @"lproj"]]; + contents = bundle_directory_readable(primary); + addBundlePath(array, contents, primary, nil, nil); + } + } } - } - if (localization) { - primary = [originalPrimary stringByAppendingPathComponent: - [localization stringByAppendingPathExtension:@"lproj"]]; - contents = bundle_directory_readable(primary); - addBundlePath(array, contents, primary, nil, nil); - } else { - enumerate = [languages objectEnumerator]; - while ((language = [enumerate nextObject])) { + if (localization) + { primary = [originalPrimary stringByAppendingPathComponent: - [language stringByAppendingPathExtension:@"lproj"]]; + [localization stringByAppendingPathExtension: @"lproj"]]; contents = bundle_directory_readable(primary); addBundlePath(array, contents, primary, nil, nil); } - } + else + { + enumerate = [languages objectEnumerator]; + while ((language = [enumerate nextObject])) + { + primary = [originalPrimary stringByAppendingPathComponent: + [language stringByAppendingPathExtension: @"lproj"]]; + contents = bundle_directory_readable(primary); + addBundlePath(array, contents, primary, nil, nil); + } + } #endif /* __ANDROID__ */ primary = rootPath; @@ -2269,7 +2281,7 @@ IF_NO_GC( #if !defined(_WIN32) if (_frameworkVersion) - rootPath = [NSString stringWithFormat:@"%@/Versions/%@", [self bundlePath], + rootPath = [NSString stringWithFormat: @"%@/Versions/%@", [self bundlePath], _frameworkVersion]; else #endif @@ -2548,13 +2560,17 @@ IF_NO_GC( NSArray *languages = [[NSUserDefaults standardUserDefaults] stringArrayForKey: @"NSLanguages"]; - for (locale in languages) { - NSString *path = [self pathForResource:@"Localizable" ofType:@"strings" - inDirectory:nil forLocalization:locale]; - if (path) { - [array addObject: locale]; + for (locale in languages) + { + NSString *path = [self pathForResource: @"Localizable" + ofType: @"strings" + inDirectory: nil + forLocalization: locale]; + if (path) + { + [array addObject: locale]; + } } - } #endif /* __ANDROID__ */ return GS_IMMUTABLE(array); @@ -2764,7 +2780,7 @@ IF_NO_GC( withString: @"_1"]; #if !defined(_WIN32) - path = [_path stringByAppendingPathComponent:@"Versions/Current"]; + path = [_path stringByAppendingPathComponent: @"Versions/Current"]; #else path = _path; #endif @@ -2798,7 +2814,7 @@ IF_NO_GC( { #if !defined(_WIN32) return [_path stringByAppendingPathComponent: - [NSString stringWithFormat:@"Versions/%@/%@", + [NSString stringWithFormat: @"Versions/%@/%@", version, executableName]]; #else return [_path stringByAppendingPathComponent: executableName]; @@ -2827,7 +2843,7 @@ IF_NO_GC( { #if !defined(_WIN32) return [_path stringByAppendingPathComponent: - [NSString stringWithFormat:@"Versions/%@/Resources", + [NSString stringWithFormat: @"Versions/%@/Resources", version]]; #else /* No Versions (that require symlinks) on mswindows */ @@ -2884,7 +2900,7 @@ IF_NO_GC( { #if !defined(_WIN32) return [_path stringByAppendingPathComponent: - [NSString stringWithFormat:@"Versions/%@/PlugIns", + [NSString stringWithFormat: @"Versions/%@/PlugIns", version]]; #else return [_path stringByAppendingPathComponent: @"PlugIns"]; @@ -2912,8 +2928,8 @@ IF_NO_GC( { #if !defined(_WIN32) return [_path stringByAppendingPathComponent: - [NSString stringWithFormat:@"Versions/%@/PrivateFrameworks", - version]]; + [NSString stringWithFormat: @"Versions/%@/PrivateFrameworks", + version]]; #else return [_path stringByAppendingPathComponent: @"PrivateFrameworks"]; #endif @@ -3282,13 +3298,14 @@ IF_NO_GC( + (AAssetManager *)assetManager { - return _assetManager; + return _assetManager; } -+ (void)setJavaAssetManager:(jobject)jassetManager withJNIEnv:(JNIEnv *)env ++ (void) setJavaAssetManager: (jobject)jassetManager withJNIEnv: (JNIEnv *)env { - // create global reference to Java asset manager to prevent garbage - // collection + /* create global reference to Java asset manager to prevent garbage + * collection + */ _jassetManager = (*env)->NewGlobalRef(env, jassetManager); // get native asset manager (may be shared across multiple threads) @@ -3298,62 +3315,72 @@ IF_NO_GC( [_mainBundle cleanPathCache]; } -+ (AAsset *)assetForPath:(NSString *)path ++ (AAsset *) assetForPath: (NSString *)path { - return [self assetForPath:path withMode:AASSET_MODE_UNKNOWN]; + return [self assetForPath: path withMode: AASSET_MODE_UNKNOWN]; } -+ (AAsset *)assetForPath:(NSString *)path withMode:(int)mode ++ (AAsset *) assetForPath: (NSString *)path withMode: (int)mode { AAsset *asset = NULL; if (_assetManager && _mainBundle) - { - NSString *resourcePath = [_mainBundle resourcePath]; - - if ([path hasPrefix:resourcePath] && [path length] > [resourcePath length]) { - NSString *assetPath = [path substringFromIndex:[resourcePath length]+1]; + NSString *resourcePath = [_mainBundle resourcePath]; - asset = AAssetManager_open(_assetManager, - [assetPath fileSystemRepresentation], mode); + if ([path hasPrefix: resourcePath] + && [path length] > [resourcePath length]) + { + NSString *assetPath; + + assetPath = [path substringFromIndex: [resourcePath length] + 1]; + asset = AAssetManager_open(_assetManager, + [assetPath fileSystemRepresentation], mode); + } } - } return asset; } -+ (AAssetDir *)assetDirForPath:(NSString *)path ++ (AAssetDir *) assetDirForPath: (NSString *)path { AAssetDir *assetDir = NULL; if (_assetManager && _mainBundle) - { - NSString *resourcePath = [_mainBundle resourcePath]; - - if ([path hasPrefix:resourcePath]) { - NSString *assetPath = @""; - if ([path length] > [resourcePath length]) { - assetPath = [path substringFromIndex:[resourcePath length] + 1]; - } + NSString *resourcePath = [_mainBundle resourcePath]; - assetDir = AAssetManager_openDir(_assetManager, - [assetPath fileSystemRepresentation]); - - if (assetDir) { - // AAssetManager_openDir() always returns an object, so we check if - // the directory exists by ensuring it contains a file - BOOL exists = AAssetDir_getNextFileName(assetDir) != NULL; - if (exists) { - AAssetDir_rewind(assetDir); - } else { - AAssetDir_close(assetDir); - assetDir = NULL; - } - } + if ([path hasPrefix: resourcePath]) + { + NSString *assetPath = @""; + + if ([path length] > [resourcePath length]) + { + assetPath = [path substringFromIndex: [resourcePath length] + 1]; + } + + assetDir = AAssetManager_openDir(_assetManager, + [assetPath fileSystemRepresentation]); + + if (assetDir) + { + /* AAssetManager_openDir() always returns an object, + * so we check if the directory exists by ensuring + * it contains a file + */ + BOOL exists = AAssetDir_getNextFileName(assetDir) != NULL; + if (exists) + { + AAssetDir_rewind(assetDir); + } + else + { + AAssetDir_close(assetDir); + assetDir = NULL; + } + } + } } - } return assetDir; } diff --git a/Source/NSData.m b/Source/NSData.m index 1e711aa29..d60c37f88 100644 --- a/Source/NSData.m +++ b/Source/NSData.m @@ -245,31 +245,36 @@ readContentsOfFile(NSString *path, void **buf, off_t *len, NSZone *zone) #ifdef __ANDROID__ // Android: try using asset manager if path is in main bundle resources - AAsset *asset = [NSBundle assetForPath:path withMode:AASSET_MODE_BUFFER]; - if (asset) { - fileLength = AAsset_getLength(asset); + AAsset *asset = [NSBundle assetForPath: path withMode: AASSET_MODE_BUFFER]; + if (asset) + { + fileLength = AAsset_getLength(asset); - tmp = NSZoneMalloc(zone, fileLength); - if (tmp == 0) { - NSLog(@"Malloc failed for file (%@) of length %jd - %@", path, - (intmax_t)fileLength, [NSError _last]); + tmp = NSZoneMalloc(zone, fileLength); + if (tmp == 0) + { + NSLog(@"Malloc failed for file (%@) of length %jd - %@", path, + (intmax_t)fileLength, [NSError _last]); + AAsset_close(asset); + goto failure; + } + + int result = AAsset_read(asset, tmp, fileLength); AAsset_close(asset); - goto failure; + + if (result < 0) + { + NSWarnFLog(@"read of file (%@) contents failed - %@", path, + [NSError errorWithDomain: NSPOSIXErrorDomain + code: result + userInfo: nil]); + goto failure; + } + + *buf = tmp; + *len = fileLength; + return YES; } - - int result = AAsset_read(asset, tmp, fileLength); - AAsset_close(asset); - - if (result < 0) { - NSWarnFLog(@"read of file (%@) contents failed - %@", path, - [NSError errorWithDomain:NSPOSIXErrorDomain code:result userInfo:nil]); - goto failure; - } - - *buf = tmp; - *len = fileLength; - return YES; - } #endif /* __ANDROID__ */ #if defined(_WIN32) diff --git a/Source/NSFileManager.m b/Source/NSFileManager.m index 98a88b992..2770c661c 100644 --- a/Source/NSFileManager.m +++ b/Source/NSFileManager.m @@ -738,7 +738,7 @@ static NSStringEncoding defaultEncoding; nxtImp = [direnum methodForSelector: @selector(nextObject)]; - urlArray = [NSMutableArray arrayWithCapacity:128]; + urlArray = [NSMutableArray arrayWithCapacity: 128]; while ((tempPath = (*nxtImp)(direnum, @selector(nextObject))) != nil) { NSURL *tempURL; @@ -749,7 +749,7 @@ static NSStringEncoding defaultEncoding; /* we purge files beginning with . */ if (!((mask & NSDirectoryEnumerationSkipsHiddenFiles) - && [lastComponent hasPrefix:@"."])) + && [lastComponent hasPrefix: @"."])) { [urlArray addObject: tempURL]; } @@ -1653,21 +1653,26 @@ static NSStringEncoding defaultEncoding; if (_STAT(lpath, &statbuf) != 0) { #ifdef __ANDROID__ - // Android: try using asset manager if path is in main bundle resources - AAsset *asset = [NSBundle assetForPath:path]; - if (asset) { - AAsset_close(asset); - return YES; - } + /* Android: try using asset manager if path is in + * main bundle resources + */ + AAsset *asset = [NSBundle assetForPath: path]; + if (asset) + { + AAsset_close(asset); + return YES; + } - AAssetDir *assetDir = [NSBundle assetDirForPath:path]; - if (assetDir) { - AAssetDir_close(assetDir); - if (isDirectory) { - *isDirectory = YES; - } - return YES; - } + AAssetDir *assetDir = [NSBundle assetDirForPath: path]; + if (assetDir) + { + AAssetDir_close(assetDir); + if (isDirectory) + { + *isDirectory = YES; + } + return YES; + } #endif return NO; @@ -1721,11 +1726,12 @@ static NSStringEncoding defaultEncoding; #ifdef __ANDROID__ // Android: try using asset manager if path is in main bundle resources - AAsset *asset = [NSBundle assetForPath:path]; - if (asset) { - AAsset_close(asset); - return YES; - } + AAsset *asset = [NSBundle assetForPath: path]; + if (asset) + { + AAsset_close(asset); + return YES; + } #endif return NO; @@ -2203,8 +2209,8 @@ static NSStringEncoding defaultEncoding; */ - (NSDictionary*) fileSystemAttributesAtPath: (NSString*)path { - return [self attributesOfFileSystemForPath:path - error:NULL]; + return [self attributesOfFileSystemForPath: path + error: NULL]; } /** @@ -2416,9 +2422,10 @@ static inline void gsedRelease(GSEnumeratedDirectory X) DESTROY(X.path); _CLOSEDIR(X.pointer); #ifdef __ANDROID__ - if (X.assetDir) { - AAssetDir_close(X.assetDir); - } + if (X.assetDir) + { + AAssetDir_close(X.assetDir); + } #endif } @@ -2480,10 +2487,13 @@ static inline void gsedRelease(GSEnumeratedDirectory X) #ifdef __ANDROID__ AAssetDir *assetDir = NULL; - if (!dir_pointer) { - // Android: try using asset manager if path is in main bundle resources - assetDir = [NSBundle assetDirForPath:path]; - } + if (!dir_pointer) + { + /* Android: try using asset manager if path is in + * main bundle resources + */ + assetDir = [NSBundle assetDirForPath: path]; + } if (dir_pointer || assetDir) #else @@ -2592,19 +2602,21 @@ static inline void gsedRelease(GSEnumeratedDirectory X) #ifdef __ANDROID__ if (dir.assetDir) - { - // This will only return files and not directories, which means that - // recursion is not supported. - // See https://issuetracker.google.com/issues/37002833 - dirname = AAssetDir_getNextFileName(dir.assetDir); - } + { + /* This will only return files and not directories, which means that + * recursion is not supported. + * See https://issuetracker.google.com/issues/37002833 + */ + dirname = AAssetDir_getNextFileName(dir.assetDir); + } else if (dir.pointer) #endif { struct _DIRENT *dirbuf = _READDIR(dir.pointer); - if (dirbuf) { - dirname = dirbuf->d_name; - } + if (dirbuf) + { + dirname = dirbuf->d_name; + } } if (dirname) @@ -2930,10 +2942,11 @@ static inline void gsedRelease(GSEnumeratedDirectory X) sourceFd = open([self fileSystemRepresentationWithPath: source], GSBINIO|O_RDONLY); #ifdef __ANDROID__ - if (sourceFd < 0) { - // Android: try using asset manager if path is in main bundle resources - asset = [NSBundle assetForPath:source withMode:AASSET_MODE_STREAMING]; - } + if (sourceFd < 0) + { + // Android: try using asset manager if path is in main bundle resources + asset = [NSBundle assetForPath: source withMode: AASSET_MODE_STREAMING]; + } if (sourceFd < 0 && asset == NULL) #else if (sourceFd < 0) @@ -2952,9 +2965,10 @@ static inline void gsedRelease(GSEnumeratedDirectory X) if (destFd < 0) { #ifdef __ANDROID__ - if (asset) { - AAsset_close(asset); - } + if (asset) + { + AAsset_close(asset); + } else #endif close (sourceFd); @@ -2971,9 +2985,10 @@ static inline void gsedRelease(GSEnumeratedDirectory X) for (i = 0; i < fileSize; i += rbytes) { #ifdef __ANDROID__ - if (asset) { - rbytes = AAsset_read(asset, buffer, bufsize); - } + if (asset) + { + rbytes = AAsset_read(asset, buffer, bufsize); + } else #endif rbytes = read (sourceFd, buffer, bufsize); @@ -2984,9 +2999,10 @@ static inline void gsedRelease(GSEnumeratedDirectory X) break; // End of input file } #ifdef __ANDROID__ - if (asset) { - AAsset_close(asset); - } + if (asset) + { + AAsset_close(asset); + } else #endif close (sourceFd); @@ -3003,9 +3019,10 @@ static inline void gsedRelease(GSEnumeratedDirectory X) if (wbytes != rbytes) { #ifdef __ANDROID__ - if (asset) { - AAsset_close(asset); - } + if (asset) + { + AAsset_close(asset); + } else #endif close (sourceFd); @@ -3019,9 +3036,10 @@ static inline void gsedRelease(GSEnumeratedDirectory X) } } #ifdef __ANDROID__ - if (asset) { - AAsset_close(asset); - } + if (asset) + { + AAsset_close(asset); + } else #endif close (sourceFd); @@ -3388,8 +3406,10 @@ static NSSet *fileKeys = nil; if (lstat(lpath, &d->statbuf) != 0) { #ifdef __ANDROID__ - // Android: try using asset manager if path is in main bundle resources - asset = [NSBundle assetForPath:path]; + /* Android: try using asset manager if path is in + * main bundle resources + */ + asset = [NSBundle assetForPath: path]; if (asset == NULL) #endif DESTROY(d); @@ -3401,7 +3421,7 @@ static NSSet *fileKeys = nil; { #ifdef __ANDROID__ // Android: try using asset manager if path is in main bundle resources - asset = [NSBundle assetForPath:path]; + asset = [NSBundle assetForPath: path]; if (asset == NULL) #endif DESTROY(d); @@ -3413,13 +3433,14 @@ static NSSet *fileKeys = nil; d->_path[i] = lpath[i]; } #ifdef __ANDROID__ - if (asset) { - // set some basic stat values for Android assets - memset(&d->statbuf, 0, sizeof(d->statbuf)); - d->statbuf.st_mode = S_IRUSR; - d->statbuf.st_size = AAsset_getLength(asset); - AAsset_close(asset); - } + if (asset) + { + // set some basic stat values for Android assets + memset(&d->statbuf, 0, sizeof(d->statbuf)); + d->statbuf.st_mode = S_IRUSR; + d->statbuf.st_size = AAsset_getLength(asset); + AAsset_close(asset); + } #endif } return AUTORELEASE(d); diff --git a/Source/NSObject.m b/Source/NSObject.m index eea533cd4..3c0f5569c 100644 --- a/Source/NSObject.m +++ b/Source/NSObject.m @@ -2580,12 +2580,56 @@ GSPrivateMemorySize(NSObject *self, NSHashTable *exclude) } @implementation NSObject (MemoryFootprint) ++ (NSUInteger) contentSizeInBytesOf: (NSObject*)instance + excluding: (NSHashTable*)exclude +{ + unsigned count; + Ivar *vars; + NSUInteger size = 0; + + if (0 != (vars = class_copyIvarList(self, &count))) + { + while (count-- > 0) + { + const char *type = ivar_getTypeEncoding(vars[count]); + + type = GSSkipTypeQualifierAndLayoutInfo(type); + if ('@' == *type) + { + NSObject *obj = object_getIvar(instance, vars[count]); + + if (obj != nil) + { + size += [obj sizeInBytesExcluding: exclude]; + } + } + } + free(vars); + } + return size; +} + (NSUInteger) sizeInBytesExcluding: (NSHashTable*)exclude { return 0; } - (NSUInteger) sizeInBytesExcluding: (NSHashTable*)exclude { - return GSPrivateMemorySize(self, exclude); + if (0 == NSHashGet(exclude, self)) + { + Class c = object_getClass(self); + NSUInteger size = class_getInstanceSize(c); + + NSHashInsert(exclude, self); + if (size > 0) + { + while (c != Nil) + { + size += [c contentSizeInBytesOf: self excluding: exclude]; + } + c = class_getSuperclass(c); + } + return size; + } + return 0; } @end diff --git a/Source/NSProcessInfo.m b/Source/NSProcessInfo.m index 40ae03896..6f395ee3e 100644 --- a/Source/NSProcessInfo.m +++ b/Source/NSProcessInfo.m @@ -952,20 +952,24 @@ extern char **__libc_argv; && !_gnu_processName && !_gnu_arguments && !_gnu_environment) { FILE *f = fopen("/proc/self/cmdline", "r"); - if (f) { - char identifier[BUFSIZ]; - fgets(identifier, sizeof(identifier), f); - fclose(f); - - // construct fake executable path - char *arg0; - asprintf(&arg0, "/data/data/%s/exe", identifier); - char *argv[] = { arg0 }; - _gnu_process_args(sizeof(argv)/sizeof(char *), argv, NULL); - } else { - fprintf(stderr, "Failed to read cmdline\n"); - } + if (f) + { + char identifier[BUFSIZ]; + fgets(identifier, sizeof(identifier), f); + fclose(f); + + // construct fake executable path + char *arg0; + asprintf(&arg0, "/data/data/%s/exe", identifier); + + char *argv[] = { arg0 }; + _gnu_process_args(sizeof(argv)/sizeof(char *), argv, NULL); + } + else + { + fprintf(stderr, "Failed to read cmdline\n"); + } } }