├── jsconfig.json ├── android ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── rnfm │ │ ├── DownloadResult.java │ │ ├── DownloadParams.java │ │ ├── RNFileManagerPackage.java │ │ ├── Downloader.java │ │ └── RNFileManager.java ├── local.properties ├── build.gradle ├── gradlew.bat └── gradlew ├── NSArray+Map.h ├── RNFileManager.h ├── .gitignore ├── NSArray+Map.m ├── Downloader.h ├── LICENSE ├── Downloader.m ├── index.js ├── package.json ├── README.md ├── RNFileManger.xcodeproj └── project.pbxproj └── RNFileManager.m /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "module": "commonjs" 5 | } 6 | } -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thethreeonee/react-native-file-manager/HEAD/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /android/src/main/java/com/rnfm/DownloadResult.java: -------------------------------------------------------------------------------- 1 | package com.rnfm; 2 | 3 | public class DownloadResult { 4 | public int statusCode; 5 | public int bytesWritten; 6 | public Exception exception; 7 | } 8 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Oct 21 11:34:03 PDT 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.8-all.zip 7 | -------------------------------------------------------------------------------- /NSArray+Map.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSArray+Map.h 3 | // RNFS 4 | // 5 | // taken from http://stackoverflow.com/questions/6127638/nsarray-equivalent-of-map 6 | 7 | #import 8 | 9 | @interface NSArray (Map) 10 | 11 | - (NSArray *)rnfs_mapObjectsUsingBlock:(id (^)(id obj, NSUInteger idx))block; 12 | 13 | @end -------------------------------------------------------------------------------- /RNFileManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // RNFSManager.h 3 | // RNFSManager 4 | // 5 | // Created by Johannes Lumpe on 08/05/15. 6 | // Copyright (c) 2015 Johannes Lumpe. All rights reserved. 7 | // 8 | 9 | #import "RCTBridgeModule.h" 10 | #import "RCTLog.h" 11 | 12 | @interface RNFileManager : NSObject 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | workbench 3 | *.log 4 | # Xcode 5 | .DS_Store 6 | build/ 7 | *.pbxuser 8 | !default.pbxuser 9 | *.mode1v3 10 | !default.mode1v3 11 | *.mode2v3 12 | !default.mode2v3 13 | *.perspectivev3 14 | !default.perspectivev3 15 | *.xcworkspace 16 | !default.xcworkspace 17 | xcuserdata 18 | profile 19 | *.moved-aside 20 | DerivedData 21 | .idea/ 22 | # Pods - for those of you who use CocoaPods 23 | Pods 24 | update-test.sh 25 | # AndroidStudio 26 | *.iml 27 | -------------------------------------------------------------------------------- /NSArray+Map.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSArray+Map.m 3 | // RNFS 4 | 5 | #import "NSArray+Map.h" 6 | 7 | @implementation NSArray (Map) 8 | 9 | - (NSArray *)rnfs_mapObjectsUsingBlock:(id (^)(id obj, NSUInteger idx))block 10 | { 11 | NSMutableArray *result = [NSMutableArray arrayWithCapacity:[self count]]; 12 | 13 | [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 14 | [result addObject:block(obj, idx)]; 15 | }]; 16 | 17 | return result; 18 | } 19 | 20 | @end -------------------------------------------------------------------------------- /android/local.properties: -------------------------------------------------------------------------------- 1 | ## This file is automatically generated by Android Studio. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must *NOT* be checked into Version Control Systems, 5 | # as it contains information specific to your local configuration. 6 | # 7 | # Location of the SDK. This is only used by Gradle. 8 | # For customization when using a Version Control System, please read the 9 | # header note. 10 | #Tue Dec 08 10:57:21 CST 2015 11 | sdk.dir=/Users/MiincGu/Library/Android/sdk 12 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | jcenter() 4 | } 5 | 6 | dependencies { 7 | classpath 'com.android.tools.build:gradle:1.1.3' 8 | } 9 | } 10 | 11 | apply plugin: 'com.android.library' 12 | 13 | android { 14 | compileSdkVersion 23 15 | buildToolsVersion "23.0.1" 16 | 17 | defaultConfig { 18 | minSdkVersion 16 19 | targetSdkVersion 22 20 | versionCode 1 21 | versionName "1.0" 22 | } 23 | lintOptions { 24 | abortOnError false 25 | } 26 | } 27 | 28 | repositories { 29 | mavenCentral() 30 | } 31 | 32 | dependencies { 33 | compile 'com.facebook.react:react-native:0.12.+' 34 | } 35 | -------------------------------------------------------------------------------- /android/src/main/java/com/rnfm/DownloadParams.java: -------------------------------------------------------------------------------- 1 | package com.rnfm; 2 | 3 | import java.io.File; 4 | import java.net.URL; 5 | import java.util.*; 6 | 7 | public class DownloadParams { 8 | public interface OnTaskCompleted { 9 | void onTaskCompleted(DownloadResult res); 10 | } 11 | 12 | public interface OnDownloadBegin { 13 | void onDownloadBegin(int statusCode, int contentLength, Map headers); 14 | } 15 | 16 | public interface OnDownloadProgress { 17 | void onDownloadProgress(int contentLength, int bytesWritten); 18 | } 19 | 20 | public URL src; 21 | public File dest; 22 | public OnTaskCompleted onTaskCompleted; 23 | public OnDownloadBegin onDownloadBegin; 24 | public OnDownloadProgress onDownloadProgress; 25 | } -------------------------------------------------------------------------------- /Downloader.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | typedef void (^DownloaderCallback)(NSNumber*, NSNumber*); 4 | typedef void (^ErrorCallback)(NSError*); 5 | typedef void (^BeginCallback)(NSNumber*, NSNumber*, NSDictionary*); 6 | typedef void (^ProgressCallback)(NSNumber*, NSNumber*); 7 | 8 | @interface DownloadParams : NSObject 9 | 10 | @property (copy) NSString* fromUrl; 11 | @property (copy) NSString* toFile; 12 | @property (copy) DownloaderCallback callback; // Download has finished (data written) 13 | @property (copy) ErrorCallback errorCallback; // Something went wrong 14 | @property (copy) BeginCallback beginCallback; // Download has started (headers received) 15 | @property (copy) ProgressCallback progressCallback; // Download is progressing 16 | 17 | @end 18 | 19 | @interface Downloader : NSObject 20 | 21 | - (void)downloadFile:(DownloadParams*)params; 22 | - (void)stopDownload; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /android/src/main/java/com/rnfm/RNFileManagerPackage.java: -------------------------------------------------------------------------------- 1 | package com.rnfm; 2 | 3 | import java.util.*; 4 | 5 | import com.facebook.react.ReactPackage; 6 | import com.facebook.react.bridge.NativeModule; 7 | import com.facebook.react.bridge.JavaScriptModule; 8 | import com.facebook.react.bridge.ReactApplicationContext; 9 | import com.facebook.react.uimanager.ViewManager; 10 | 11 | public class RNFileManagerPackage implements ReactPackage { 12 | 13 | @Override 14 | public List createNativeModules(ReactApplicationContext reactContext) { 15 | List modules = new ArrayList<>(); 16 | modules.add(new RNFileManager(reactContext)); 17 | return modules; 18 | } 19 | 20 | @Override 21 | public List> createJSModules() { 22 | return Collections.emptyList(); 23 | } 24 | 25 | @Override 26 | public List createViewManagers(ReactApplicationContext reactContext) { 27 | return Arrays.asList(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Johannes Lumpe 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /android/src/main/java/com/rnfm/Downloader.java: -------------------------------------------------------------------------------- 1 | package com.rnfm; 2 | 3 | import java.io.File; 4 | import java.io.FileOutputStream; 5 | import java.io.FileInputStream; 6 | import java.io.BufferedInputStream; 7 | import java.io.InputStream; 8 | import java.io.OutputStream; 9 | import java.io.IOException; 10 | import java.net.URL; 11 | import java.net.URLConnection; 12 | import java.net.HttpURLConnection; 13 | import java.util.*; 14 | import java.util.concurrent.atomic.AtomicBoolean; 15 | 16 | import android.os.AsyncTask; 17 | 18 | public class Downloader extends AsyncTask { 19 | private DownloadParams mParam; 20 | private AtomicBoolean mAbort = new AtomicBoolean(false); 21 | 22 | protected DownloadResult doInBackground(DownloadParams... params) { 23 | mParam = params[0]; 24 | 25 | DownloadResult res = new DownloadResult(); 26 | 27 | try { 28 | this.download(mParam, res); 29 | mParam.onTaskCompleted.onTaskCompleted(res); 30 | } catch (Exception ex) { 31 | res.exception = ex; 32 | mParam.onTaskCompleted.onTaskCompleted(res); 33 | return res; 34 | } 35 | 36 | return res; 37 | } 38 | 39 | private void download(DownloadParams param, DownloadResult res) throws IOException { 40 | InputStream input = null; 41 | OutputStream output = null; 42 | 43 | try { 44 | HttpURLConnection connection = (HttpURLConnection)param.src.openConnection(); 45 | 46 | connection.setConnectTimeout(5000); 47 | connection.connect(); 48 | 49 | int statusCode = connection.getResponseCode(); 50 | int lengthOfFile = connection.getContentLength(); 51 | 52 | Map> headers = connection.getHeaderFields(); 53 | 54 | Map headersFlat = new HashMap(); 55 | 56 | for (Map.Entry> entry : headers.entrySet()) { 57 | String headerKey = entry.getKey(); 58 | String valueKey = entry.getValue().get(0); 59 | 60 | if (headerKey != null && valueKey != null) { 61 | headersFlat.put(headerKey, valueKey); 62 | } 63 | } 64 | 65 | mParam.onDownloadBegin.onDownloadBegin(statusCode, lengthOfFile, headersFlat); 66 | 67 | input = new BufferedInputStream(param.src.openStream(), 8 * 1024); 68 | output = new FileOutputStream(param.dest); 69 | 70 | byte data[] = new byte[8 * 1024]; 71 | int total = 0; 72 | int count; 73 | 74 | while ((count = input.read(data)) != -1) { 75 | if (mAbort.get()) { 76 | break; 77 | } 78 | 79 | total += count; 80 | publishProgress(new int[] { lengthOfFile, total }); 81 | output.write(data, 0, count); 82 | } 83 | 84 | output.flush(); 85 | 86 | res.statusCode = statusCode; 87 | res.bytesWritten = total; 88 | } finally { 89 | if (output != null) output.close(); 90 | if (input != null) input.close(); 91 | } 92 | } 93 | 94 | protected void stop() { 95 | mAbort.set(true); 96 | } 97 | 98 | @Override 99 | protected void onProgressUpdate(int[]... values) { 100 | super.onProgressUpdate(values); 101 | mParam.onDownloadProgress.onDownloadProgress(values[0][0], values[0][1]); 102 | } 103 | 104 | protected void onPostExecute(Exception ex) { 105 | 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /Downloader.m: -------------------------------------------------------------------------------- 1 | #import "Downloader.h" 2 | 3 | @implementation DownloadParams 4 | 5 | @end 6 | 7 | @interface Downloader() 8 | 9 | @property (copy) DownloadParams* params; 10 | 11 | @property (retain) NSURLConnection* connection; 12 | @property (retain) NSNumber* statusCode; 13 | @property (retain) NSNumber* contentLength; 14 | @property (retain) NSNumber* bytesWritten; 15 | 16 | @property (retain) NSFileHandle* fileHandle; 17 | 18 | @end 19 | 20 | @implementation Downloader 21 | 22 | - (void)downloadFile:(DownloadParams*)params 23 | { 24 | _params = params; 25 | 26 | _bytesWritten = 0; 27 | 28 | NSURL* url = [NSURL URLWithString:_params.fromUrl]; 29 | 30 | NSMutableURLRequest* downloadRequest = [NSMutableURLRequest requestWithURL:url 31 | cachePolicy:NSURLRequestUseProtocolCachePolicy 32 | timeoutInterval:30]; 33 | 34 | _connection = [[NSURLConnection alloc] initWithRequest:downloadRequest delegate:self startImmediately:NO]; 35 | 36 | [_connection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; 37 | 38 | [_connection start]; 39 | } 40 | 41 | - (void)connection:(NSURLConnection*)connection didFailWithError:(NSError*)error 42 | { 43 | [_fileHandle closeFile]; 44 | 45 | NSString *tempPath = [_params.toFile stringByAppendingPathExtension:@"tmp"]; 46 | 47 | NSError *err = nil; 48 | if ([[NSFileManager defaultManager] fileExistsAtPath:tempPath isDirectory:false]) { 49 | [[NSFileManager defaultManager] removeItemAtPath:tempPath error:&err]; 50 | } 51 | 52 | return _params.errorCallback(error); 53 | } 54 | 55 | - (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse*)response 56 | { 57 | NSString *tempPath = [_params.toFile stringByAppendingPathExtension:@"tmp"]; 58 | 59 | [[NSFileManager defaultManager] createFileAtPath:tempPath contents:nil attributes:nil]; 60 | [[NSURL fileURLWithPath:tempPath] setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil]; 61 | 62 | _fileHandle = [NSFileHandle fileHandleForWritingAtPath:tempPath]; 63 | 64 | if (!_fileHandle) { 65 | NSError* error = [NSError errorWithDomain:@"Downloader" code:NSURLErrorFileDoesNotExist userInfo:@{NSLocalizedDescriptionKey: [NSString stringWithFormat: @"Failed to create target file at path: %@", tempPath]}]; 66 | 67 | if (_params.errorCallback) { 68 | return _params.errorCallback(error); 69 | } 70 | } 71 | 72 | NSHTTPURLResponse* httpUrlResponse = (NSHTTPURLResponse*)response; 73 | 74 | _statusCode = [NSNumber numberWithLong:httpUrlResponse.statusCode]; 75 | _contentLength = [NSNumber numberWithLongLong: httpUrlResponse.expectedContentLength]; 76 | 77 | return _params.beginCallback(_statusCode, _contentLength, httpUrlResponse.allHeaderFields); 78 | } 79 | 80 | - (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data 81 | { 82 | if ([_statusCode isEqualToNumber:[NSNumber numberWithInt:200]]) { 83 | [_fileHandle writeData:data]; 84 | 85 | _bytesWritten = [NSNumber numberWithUnsignedInteger:[_bytesWritten unsignedIntegerValue] + data.length]; 86 | 87 | return _params.progressCallback(_contentLength, _bytesWritten); 88 | } 89 | } 90 | 91 | - (void)connectionDidFinishLoading:(NSURLConnection*)connection 92 | { 93 | [_fileHandle closeFile]; 94 | 95 | NSString *tempPath = [_params.toFile stringByAppendingPathExtension:@"tmp"]; 96 | 97 | NSError *error = nil; 98 | if ([[NSFileManager defaultManager] fileExistsAtPath:_params.toFile isDirectory:false]) { 99 | [[NSFileManager defaultManager] removeItemAtPath:_params.toFile error:&error]; 100 | } 101 | [[NSFileManager defaultManager] moveItemAtPath:tempPath toPath:_params.toFile error:&error]; 102 | [[NSURL fileURLWithPath:_params.toFile] setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil]; 103 | 104 | NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:_params.toFile error:&error]; 105 | 106 | return _params.callback(_statusCode, [fileAttributes valueForKey:@"NSFileSize"]); 107 | } 108 | 109 | - (void)stopDownload 110 | { 111 | [_connection cancel]; 112 | } 113 | 114 | @end 115 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // This file supports both iOS and Android 4 | 5 | // Stop bluebird going nuts because it can't find "self" 6 | if (typeof self === 'undefined') { 7 | global.self = global; 8 | } 9 | 10 | var RNFSManager = require('react-native').NativeModules.RNFileManager; 11 | var NativeAppEventEmitter = require('react-native').NativeAppEventEmitter; // iOS 12 | var DeviceEventEmitter = require('react-native').DeviceEventEmitter; // Android 13 | var Promise = require('bluebird'); 14 | var base64 = require('base-64'); 15 | var utf8 = require('utf8'); 16 | 17 | var _readDir = Promise.promisify(RNFSManager.readDir); 18 | var _stat = Promise.promisify(RNFSManager.stat); 19 | var _readFile = Promise.promisify(RNFSManager.readFile); 20 | var _writeFile = Promise.promisify(RNFSManager.writeFile); 21 | var _unlink = Promise.promisify(RNFSManager.unlink); 22 | var _mkdir = Promise.promisify(RNFSManager.mkdir); 23 | var _downloadFile = Promise.promisify(RNFSManager.downloadFile); 24 | var _pathForBundle = Promise.promisify(RNFSManager.pathForBundle); 25 | var _exists = Promise.promisify(RNFSManager.exists); 26 | var _folderExists = Promise.promisify(RNFSManager.folderExists); 27 | var _rename = Promise.promisify(RNFSManager.rename); 28 | var _moveFile = Promise.promisify(RNFSManager.moveFile); 29 | 30 | var convertError = (err) => { 31 | if (err.isOperational && err.cause) { 32 | err = err.cause; 33 | } 34 | 35 | var error = new Error(err.description || err.message); 36 | error.code = err.code; 37 | throw error; 38 | }; 39 | 40 | var NSFileTypeRegular = RNFSManager.NSFileTypeRegular; 41 | var NSFileTypeDirectory = RNFSManager.NSFileTypeDirectory; 42 | 43 | var jobId = 0; 44 | 45 | var getJobId = () => { 46 | jobId += 1; 47 | return jobId; 48 | }; 49 | 50 | var RNFS = { 51 | 52 | readDir(dirpath) { 53 | return _readDir(dirpath) 54 | .then(files => { 55 | return files.map(file => ({ 56 | name: file.name, 57 | path: file.path, 58 | size: file.size, 59 | isFile: () => file.type === NSFileTypeRegular, 60 | isDirectory: () => file.type === NSFileTypeDirectory, 61 | })); 62 | }) 63 | .catch(convertError); 64 | }, 65 | 66 | // Node style version (lowercase d). Returns just the names 67 | readdir(dirpath) { 68 | return RNFS.readDir(dirpath) 69 | .then(files => { 70 | return files.map(file => file.name); 71 | }); 72 | }, 73 | 74 | stat(filepath) { 75 | return _stat(filepath) 76 | .then((result) => { 77 | return { 78 | 'ctime': new Date(result.ctime*1000), 79 | 'mtime': new Date(result.mtime*1000), 80 | 'size': result.size, 81 | 'mode': result.mode, 82 | isFile: () => result.type === NSFileTypeRegular, 83 | isDirectory: () => result.type === NSFileTypeDirectory, 84 | }; 85 | }) 86 | .catch(convertError); 87 | }, 88 | 89 | readFile(filepath, encoding) { 90 | if (!encoding) encoding = 'utf8'; 91 | 92 | return _readFile(filepath) 93 | .then((b64) => { 94 | var contents; 95 | 96 | if (encoding === 'utf8') { 97 | contents = utf8.decode(base64.decode(b64)); 98 | } else if (encoding === 'ascii') { 99 | contents = base64.decode(b64); 100 | } else if (encoding === 'base64') { 101 | contents = b64; 102 | } else { 103 | throw new Error('Invalid encoding type "' + encoding + '"'); 104 | } 105 | 106 | return contents; 107 | }) 108 | .catch(convertError); 109 | }, 110 | 111 | writeFile(filepath, contents, encoding, options) { 112 | var b64; 113 | 114 | if (!encoding) encoding = 'utf8'; 115 | 116 | if (encoding === 'utf8') { 117 | b64 = base64.encode(utf8.encode(contents)); 118 | } else if (encoding === 'ascii') { 119 | b64 = base64.encode(contents); 120 | } else if (encoding === 'base64') { 121 | b64 = contents; 122 | } else { 123 | throw new Error('Invalid encoding type "' + encoding + '"'); 124 | } 125 | 126 | return _writeFile(filepath, b64, options) 127 | .catch(convertError); 128 | }, 129 | 130 | pathForBundle(bundleName) { 131 | return _pathForBundle(bundleName); 132 | }, 133 | 134 | unlink(filepath) { 135 | return _unlink(filepath) 136 | .catch(convertError); 137 | }, 138 | 139 | mkdir(filepath, excludeFromBackup) { 140 | excludeFromBackup = !!excludeFromBackup; 141 | return _mkdir(filepath, excludeFromBackup) 142 | .catch(convertError); 143 | }, 144 | 145 | downloadFile(fromUrl, toFile, begin, progress) { 146 | var jobId = getJobId(); 147 | var subscriptionIos, subscriptionAndroid; 148 | 149 | if (!begin) begin = (info) => { 150 | // console.log('Download begun:', info); 151 | }; 152 | 153 | if (begin) { 154 | // Two different styles of subscribing to events for different platforms, hmmm.... 155 | if (NativeAppEventEmitter.addListener) 156 | subscriptionIos = NativeAppEventEmitter.addListener('DownloadBegin-' + jobId, begin); 157 | if (DeviceEventEmitter.addListener) 158 | subscriptionAndroid = DeviceEventEmitter.addListener('DownloadBegin-' + jobId, begin); 159 | } 160 | 161 | if (progress) { 162 | if (NativeAppEventEmitter.addListener) 163 | subscriptionIos = NativeAppEventEmitter.addListener('DownloadProgress-' + jobId, progress); 164 | if (DeviceEventEmitter.addListener) 165 | subscriptionAndroid = DeviceEventEmitter.addListener('DownloadProgress-' + jobId, progress); 166 | } 167 | 168 | return _downloadFile(fromUrl, toFile, jobId) 169 | .then(res => { 170 | if (subscriptionIos) subscriptionIos.remove(); 171 | if (subscriptionAndroid) subscriptionAndroid.remove(); 172 | return res; 173 | }) 174 | .catch(convertError); 175 | }, 176 | 177 | stopDownload(jobId) { 178 | RNFSManager.stopDownload(jobId); 179 | }, 180 | 181 | fileExistsAtPath(filepath) { 182 | return _exists(filepath) 183 | .catch(convertError); 184 | }, 185 | 186 | folderExistsAtPath(path) { 187 | return _folderExists(path) 188 | .catch(convertError); 189 | }, 190 | 191 | renameFile(filepath, newName) { 192 | return _rename(filepath, newName) 193 | .catch(convertError); 194 | }, 195 | 196 | moveFile(filepath, newPath) { 197 | return _moveFile(filepath, newPath) 198 | .catch(convertError); 199 | }, 200 | 201 | MainBundlePath: RNFSManager.MainBundlePath, 202 | CachesDirectoryPath: RNFSManager.NSCachesDirectoryPath, 203 | DocumentDirectoryPath: RNFSManager.NSDocumentDirectoryPath, 204 | ApplicationSupportDirectoryPath: RNFSManager.NSApplicationSupportDirectoryPath 205 | }; 206 | 207 | module.exports = RNFS; 208 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-file-manager", 3 | "version": "1.0.1", 4 | "description": "A file manger for react native", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://MiincGu@github.com/MiincGu/react-native-file-manager.git" 12 | }, 13 | "keywords": [ 14 | "react-native" 15 | ], 16 | "author": { 17 | "name": "MiincGu" 18 | }, 19 | "license": "ISC", 20 | "bugs": { 21 | "url": "https://github.com/MiincGu/react-native-file-manager/issues" 22 | }, 23 | "homepage": "https://github.com/MiincGu/react-native-file-manager#readme", 24 | "dependencies": { 25 | "base-64": "^0.1.0", 26 | "bluebird": "^2.9.25", 27 | "utf8": "^2.1.1" 28 | }, 29 | "gitHead": "2714088d4daf0c72a7971c306dd01afcfacd1a27", 30 | "readme": "## react-native-file-manager\n\nNative file system manager for react-native\n\n## Usage (iOS)\n\nFirst you need to install react-native-file-manager:\n\n```javascript\nnpm install react-native-file-manager --save\n```\n\nIn XCode, in the project navigator, right click Libraries ➜ Add Files to [your project's name] Go to node_modules ➜ react-native-file-manager and add the .xcodeproj file\n\nIn XCode, in the project navigator, select your project. Add the lib*.a from the RNFileManager project to your project's Build Phases ➜ Link Binary With Libraries Click .xcodeproj file you added before in the project navigator and go the Build Settings tab. Make sure 'All' is toggled on (instead of 'Basic'). Look for Header Search Paths and make sure it contains both $(SRCROOT)/../react-native/React and $(SRCROOT)/../../React - mark both as recursive.\n\nRun your project (Cmd+R)\n\n## Usage (Android)\n\nAndroid support is currently limited to only the `DocumentDirectory`. This maps to the app's `files` directory.\n\nMake alterations to the following files:\n\n* `android/settings.gradle`\n\n```gradle\n...\ninclude ':react-native-file-manager'\nproject(':react-native-file-manager').projectDir = new File(settingsDir, '../node_modules/react-native-file-manager/android')\n```\n\n* `android/app/build.gradle`\n\n```gradle\n...\ndependencies {\n ...\n compile project(':react-native-file-manager')\n}\n```\n\n* register module (in MainActivity.java)\n\n```java\nimport com.rnfm.RNFileManagerPackage; // <--- import\n\npublic class MainActivity extends Activity implements DefaultHardwareBackBtnHandler {\n\n ......\n\n @Override\n protected void onCreate(Bundle savedInstanceState) {\n super.onCreate(savedInstanceState);\n mReactRootView = new ReactRootView(this);\n\n mReactInstanceManager = ReactInstanceManager.builder()\n .setApplication(getApplication())\n .setBundleAssetName(\"index.android.bundle\")\n .setJSMainModuleName(\"index.android\")\n .addPackage(new MainReactPackage())\n .addPackage(new RNFileMangerPackage()) // <------- add package\n .setUseDeveloperSupport(BuildConfig.DEBUG)\n .setInitialLifecycleState(LifecycleState.RESUMED)\n .build();\n\n mReactRootView.startReactApplication(mReactInstanceManager, \"ExampleRN\", null);\n\n setContentView(mReactRootView);\n }\n\n ......\n\n}\n```\n\n## Examples\n\n### Basic\n\n```javascript\n// require the module\nvar RNFileManager = require('react-native-file-manager');\n\n// get a list of files and directories in the main bundle\nRNFileManager.readDir(RNFileManager.MainBundlePath)\n .then((result) => {\n console.log('GOT RESULT', result);\n\n // stat the first file\n return Promise.all([RNFileManager.stat(result[0].path), result[0].path]);\n })\n .then((statResult) => {\n if (statResult[0].isFile()) {\n // if we have a file, read it\n return RNFileManager.readFile(statResult[1], 'utf8');\n }\n\n return 'no file';\n })\n .then((contents) => {\n // log the file contents\n console.log(contents);\n })\n .catch((err) => {\n console.log(err.message, err.code);\n });\n```\n\n### File creation\n\n```javascript\n// require the module\nvar RNFileManager = require('react-native-file-manager');\n\n// create a path you want to write to\nvar path = RNFileManager.DocumentDirectoryPath + '/test.txt';\n\n// write the file\nRNFileManager.writeFile(path, 'Lorem ipsum dolor sit amet', 'utf8')\n .then((success) => {\n console.log('FILE WRITTEN!');\n })\n .catch((err) => {\n console.log(err.message);\n });\n\n```\n\n### File deletion\n```javascript\n// create a path you want to delete\nvar path = RNFileManager.DocumentDirectoryPath + '/test.txt';\n\nreturn RNFileManager.unlink(path)\n // spread is a method offered by bluebird to allow for more than a\n // single return value of a promise. If you use `then`, you will receive\n // the values inside of an array\n .spread((success, path) => {\n console.log('FILE DELETED', success, path);\n })\n // `unlink` will throw an error, if the item to unlink does not exist\n .catch((err) => {\n console.log(err.message);\n });\n```\n\n## API\n\n### Constants\n\nThe following constants are available on the `RNFileManager` export:\n\n`MainBundlePath` (`String`) The absolute path to the main bundle directory \n`CachesDirectoryPath` (`String`) The absolute path to the caches directory \n`DocumentDirectoryPath` (`String`) The absolute path to the document directory\n\n### `promise readDir(path)`\n\nReads the contents of `path`. This must be an absolute path. Use the above path constants to form a usable file path.\n\nThe returned promise resolves with an array of objects with the following properties:\n\n`name` (`String`) - The name of the item \n`path` (`String`) - The absolute path to the item \n`size` (`Number`) - Size in bytes\n\n### `promise readdir(path)`\n\nNode.js style version of `readDir` that returns only the names. Note the lowercase `d`.\n\n### `promise stat(path)`\n\nStats an item at `path`. \nThe promise resolves with an object with the following properties:\n\n`ctime` (`Date`) - The creation date of the item \n`mtime` (`Date`) - The modification date of the item \n`size` (`Number`) - The size of the item in bytes \n`isFile` (`Function`) - Returns true when the item is a file \n`isDirectory` (`Function`) - Returns true when the item is a directory\n\n### `promise readFile(path [, encoding])`\n\nReads the file at `path` and return contents. `encoding` can be one of `utf8` (default), `ascii`, `base64`. Use `base64` for reading binary files.\n\nNote: you will take quite a performance hit if you are reading big files\n\n### `promise writeFile(filepath, contents [, encoding, options])`\n\nWrite the `contents` to `filepath`. `encoding` can be one of `utf8` (default), `ascii`, `base64`. `options` optionally takes an object specifying the file's properties, like mode etc.\n\nThe promise resolves with a boolean.\n\n### `promise unlink(filepath)`\n\nUnlinks the item at `filepath`. If the item does not exist, an error will be thrown.\n\nThe promise resolves with an array, which contains a boolean and the path that has been unlinked. Tip: use `spread` to receive the two arguments instead of a single array in your handler.\n\nAlso recursively deletes directories (works like Linux `rm -rf`).\n\n### `promise mkdir(filepath [, excludeFromBackup])`\n\nCreate a directory at `filepath`. Automatically creates parents and does not throw if already exists (works like Linux `mkdir -p`).\n\nIOS only: If `excludeFromBackup` is true, then `NSURLIsExcludedFromBackupKey` attribute will be set. Apple will *reject* apps for storing offline cache data that does not have this attribute.\n\n### `promise downloadFile(url, filepath [, beginCallback, progressCallback])`\n\nDownload file from `url` to `filepath`. Will overwrite any previously existing file.\n\nIf `beginCallback` is provided, it will be invoked once upon download starting when headers have been received and passed a single argument with the following properties:\n\n`jobId` (`Number`) - The download job ID, required if one wishes to cancel the download. See `stopDownload`. \n`statusCode` (`Number`) - The HTTP status code \n`contentLength` (`Number`) - The total size in bytes of the download resource \n`headers` (`Map`) - The HTTP response headers from the server \n\nIf `progressCallback` is provided, it will be invoked continuously and passed a single argument with the following properties:\n\n`contentLength` (`Number`) - The total size in bytes of the download resource \n`bytesWritten` (`Number`) - The number of bytes written to the file so far \n\nPercentage can be computed easily by dividing `bytesWritten` by `contentLength`.\n\n### `promise stopDownload(jobId)`\n\nAbort the current download job with this ID. The partial file will remain on the filesystem.\n", 31 | "readmeFilename": "README.md", 32 | "_id": "react-native-file-manager@1.0.1", 33 | "_shasum": "25b4a4b389680b022812d636463d70e3a815f47f", 34 | "_from": "miincgu/react-native-file-manager", 35 | "_resolved": "git://github.com/miincgu/react-native-file-manager.git#2714088d4daf0c72a7971c306dd01afcfacd1a27" 36 | } 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NOT MAINTAINED ANY MORE 2 | 3 | Change name from react-native-fs for personal use. Use on your own risk. 4 | 5 | ## react-native-file-manager 6 | 7 | Native file system manager for react-native 8 | 9 | ## Usage (iOS) 10 | 11 | First you need to install react-native-file-manager: 12 | 13 | ```javascript 14 | npm install react-native-file-manager --save 15 | ``` 16 | 17 | In XCode, in the project navigator, right click Libraries ➜ Add Files to [your project's name] Go to node_modules ➜ react-native-file-manager and add the .xcodeproj file 18 | 19 | In XCode, in the project navigator, select your project. Add the lib*.a from the RNFileManager project to your project's Build Phases ➜ Link Binary With Libraries Click .xcodeproj file you added before in the project navigator and go the Build Settings tab. Make sure 'All' is toggled on (instead of 'Basic'). Look for Header Search Paths and make sure it contains both $(SRCROOT)/../react-native/React and $(SRCROOT)/../../React - mark both as recursive. 20 | 21 | Run your project (Cmd+R) 22 | 23 | ## Usage (Android) 24 | 25 | Android support is currently limited to only the `DocumentDirectory`. This maps to the app's `files` directory. 26 | 27 | Make alterations to the following files: 28 | 29 | * `android/settings.gradle` 30 | 31 | ```gradle 32 | ... 33 | include ':react-native-file-manager' 34 | project(':react-native-file-manager').projectDir = new File(settingsDir, '../node_modules/react-native-file-manager/android') 35 | ``` 36 | 37 | * `android/app/build.gradle` 38 | 39 | ```gradle 40 | ... 41 | dependencies { 42 | ... 43 | compile project(':react-native-file-manager') 44 | } 45 | ``` 46 | 47 | * register module (in MainActivity.java) 48 | 49 | ```java 50 | import com.rnfm.RNFileManagerPackage; // <--- import 51 | 52 | public class MainActivity extends Activity implements DefaultHardwareBackBtnHandler { 53 | 54 | ...... 55 | 56 | @Override 57 | protected void onCreate(Bundle savedInstanceState) { 58 | super.onCreate(savedInstanceState); 59 | mReactRootView = new ReactRootView(this); 60 | 61 | mReactInstanceManager = ReactInstanceManager.builder() 62 | .setApplication(getApplication()) 63 | .setBundleAssetName("index.android.bundle") 64 | .setJSMainModuleName("index.android") 65 | .addPackage(new MainReactPackage()) 66 | .addPackage(new RNFileMangerPackage()) // <------- add package 67 | .setUseDeveloperSupport(BuildConfig.DEBUG) 68 | .setInitialLifecycleState(LifecycleState.RESUMED) 69 | .build(); 70 | 71 | mReactRootView.startReactApplication(mReactInstanceManager, "ExampleRN", null); 72 | 73 | setContentView(mReactRootView); 74 | } 75 | 76 | ...... 77 | 78 | } 79 | ``` 80 | 81 | ## Examples 82 | 83 | ### Basic 84 | 85 | ```javascript 86 | // require the module 87 | var RNFileManager = require('react-native-file-manager'); 88 | 89 | // get a list of files and directories in the main bundle 90 | RNFileManager.readDir(RNFileManager.MainBundlePath) 91 | .then((result) => { 92 | console.log('GOT RESULT', result); 93 | 94 | // stat the first file 95 | return Promise.all([RNFileManager.stat(result[0].path), result[0].path]); 96 | }) 97 | .then((statResult) => { 98 | if (statResult[0].isFile()) { 99 | // if we have a file, read it 100 | return RNFileManager.readFile(statResult[1], 'utf8'); 101 | } 102 | 103 | return 'no file'; 104 | }) 105 | .then((contents) => { 106 | // log the file contents 107 | console.log(contents); 108 | }) 109 | .catch((err) => { 110 | console.log(err.message, err.code); 111 | }); 112 | ``` 113 | 114 | ### File creation 115 | 116 | ```javascript 117 | // require the module 118 | var RNFileManager = require('react-native-file-manager'); 119 | 120 | // create a path you want to write to 121 | var path = RNFileManager.DocumentDirectoryPath + '/test.txt'; 122 | 123 | // write the file 124 | RNFileManager.writeFile(path, 'Lorem ipsum dolor sit amet', 'utf8') 125 | .then((success) => { 126 | console.log('FILE WRITTEN!'); 127 | }) 128 | .catch((err) => { 129 | console.log(err.message); 130 | }); 131 | 132 | ``` 133 | 134 | ### File deletion 135 | ```javascript 136 | // create a path you want to delete 137 | var path = RNFileManager.DocumentDirectoryPath + '/test.txt'; 138 | 139 | return RNFileManager.unlink(path) 140 | // spread is a method offered by bluebird to allow for more than a 141 | // single return value of a promise. If you use `then`, you will receive 142 | // the values inside of an array 143 | .spread((success, path) => { 144 | console.log('FILE DELETED', success, path); 145 | }) 146 | // `unlink` will throw an error, if the item to unlink does not exist 147 | .catch((err) => { 148 | console.log(err.message); 149 | }); 150 | ``` 151 | 152 | ## API 153 | 154 | ### Constants 155 | 156 | The following constants are available on the `RNFileManager` export: 157 | 158 | `MainBundlePath` (`String`) The absolute path to the main bundle directory 159 | `CachesDirectoryPath` (`String`) The absolute path to the caches directory 160 | `DocumentDirectoryPath` (`String`) The absolute path to the document directory 161 | 162 | ### `promise readDir(path)` 163 | 164 | Reads the contents of `path`. This must be an absolute path. Use the above path constants to form a usable file path. 165 | 166 | The returned promise resolves with an array of objects with the following properties: 167 | 168 | `name` (`String`) - The name of the item 169 | `path` (`String`) - The absolute path to the item 170 | `size` (`Number`) - Size in bytes 171 | 172 | ### `promise readdir(path)` 173 | 174 | Node.js style version of `readDir` that returns only the names. Note the lowercase `d`. 175 | 176 | ### `promise stat(path)` 177 | 178 | Stats an item at `path`. 179 | The promise resolves with an object with the following properties: 180 | 181 | `ctime` (`Date`) - The creation date of the item 182 | `mtime` (`Date`) - The modification date of the item 183 | `size` (`Number`) - The size of the item in bytes 184 | `isFile` (`Function`) - Returns true when the item is a file 185 | `isDirectory` (`Function`) - Returns true when the item is a directory 186 | 187 | ### `promise readFile(path [, encoding])` 188 | 189 | Reads the file at `path` and return contents. `encoding` can be one of `utf8` (default), `ascii`, `base64`. Use `base64` for reading binary files. 190 | 191 | Note: you will take quite a performance hit if you are reading big files 192 | 193 | ### `promise writeFile(filepath, contents [, encoding, options])` 194 | 195 | Write the `contents` to `filepath`. `encoding` can be one of `utf8` (default), `ascii`, `base64`. `options` optionally takes an object specifying the file's properties, like mode etc. 196 | 197 | The promise resolves with a boolean. 198 | 199 | ### `promise unlink(filepath)` 200 | 201 | Unlinks the item at `filepath`. If the item does not exist, an error will be thrown. 202 | 203 | The promise resolves with an array, which contains a boolean and the path that has been unlinked. Tip: use `spread` to receive the two arguments instead of a single array in your handler. 204 | 205 | Also recursively deletes directories (works like Linux `rm -rf`). 206 | 207 | ### `promise mkdir(filepath [, excludeFromBackup])` 208 | 209 | Create a directory at `filepath`. Automatically creates parents and does not throw if already exists (works like Linux `mkdir -p`). 210 | 211 | IOS only: If `excludeFromBackup` is true, then `NSURLIsExcludedFromBackupKey` attribute will be set. Apple will *reject* apps for storing offline cache data that does not have this attribute. 212 | 213 | ### `promise downloadFile(url, filepath [, beginCallback, progressCallback])` 214 | 215 | Download file from `url` to `filepath`. Will overwrite any previously existing file. 216 | 217 | If `beginCallback` is provided, it will be invoked once upon download starting when headers have been received and passed a single argument with the following properties: 218 | 219 | `jobId` (`Number`) - The download job ID, required if one wishes to cancel the download. See `stopDownload`. 220 | `statusCode` (`Number`) - The HTTP status code 221 | `contentLength` (`Number`) - The total size in bytes of the download resource 222 | `headers` (`Map`) - The HTTP response headers from the server 223 | 224 | If `progressCallback` is provided, it will be invoked continuously and passed a single argument with the following properties: 225 | 226 | `contentLength` (`Number`) - The total size in bytes of the download resource 227 | `bytesWritten` (`Number`) - The number of bytes written to the file so far 228 | 229 | Percentage can be computed easily by dividing `bytesWritten` by `contentLength`. 230 | 231 | ### `promise stopDownload(jobId)` 232 | 233 | Abort the current download job with this ID. The partial file will remain on the filesystem. 234 | -------------------------------------------------------------------------------- /android/src/main/java/com/rnfm/RNFileManager.java: -------------------------------------------------------------------------------- 1 | package com.rnfm; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | import java.util.HashMap; 6 | import java.util.ArrayList; 7 | 8 | import android.os.Environment; 9 | import android.os.AsyncTask; 10 | import android.util.Base64; 11 | import android.content.Context; 12 | import android.support.annotation.Nullable; 13 | import android.util.SparseArray; 14 | 15 | import java.io.File; 16 | import java.io.FileOutputStream; 17 | import java.io.FileInputStream; 18 | import java.io.BufferedInputStream; 19 | import java.io.InputStream; 20 | import java.io.OutputStream; 21 | import java.io.IOException; 22 | import java.net.URL; 23 | import java.net.URLConnection; 24 | import java.net.HttpURLConnection; 25 | 26 | import com.facebook.react.bridge.NativeModule; 27 | import com.facebook.react.bridge.ReactApplicationContext; 28 | import com.facebook.react.bridge.ReactContext; 29 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 30 | import com.facebook.react.bridge.ReactMethod; 31 | import com.facebook.react.bridge.Callback; 32 | import com.facebook.react.bridge.ReadableMap; 33 | import com.facebook.react.bridge.WritableMap; 34 | import com.facebook.react.bridge.Arguments; 35 | import com.facebook.react.bridge.WritableArray; 36 | import com.facebook.react.modules.core.DeviceEventManagerModule; 37 | 38 | public class RNFileManager extends ReactContextBaseJavaModule { 39 | 40 | private static final String NSDocumentDirectoryPath = "NSDocumentDirectoryPath"; 41 | private static final String NSDocumentDirectory = "NSDocumentDirectory"; 42 | 43 | private static final String NSFileTypeRegular = "NSFileTypeRegular"; 44 | private static final String NSFileTypeDirectory = "NSFileTypeDirectory"; 45 | 46 | private SparseArray downloaders = new SparseArray(); 47 | 48 | public RNFileManager(ReactApplicationContext reactContext) { 49 | super(reactContext); 50 | } 51 | 52 | @Override 53 | public String getName() { 54 | return "RNFSManager"; 55 | } 56 | 57 | @ReactMethod 58 | public void writeFile(String filepath, String base64Content, ReadableMap options, Callback callback) { 59 | try { 60 | byte[] bytes = Base64.decode(base64Content, Base64.DEFAULT); 61 | 62 | FileOutputStream outputStream = new FileOutputStream(filepath); 63 | outputStream.write(bytes); 64 | outputStream.close(); 65 | 66 | callback.invoke(null, true, filepath); 67 | } catch (Exception ex) { 68 | ex.printStackTrace(); 69 | callback.invoke(makeErrorPayload(ex)); 70 | } 71 | } 72 | 73 | @ReactMethod 74 | public void readFile(String filepath, Callback callback) { 75 | try { 76 | File file = new File(filepath); 77 | 78 | if (!file.exists()) throw new Exception("File does not exist"); 79 | 80 | FileInputStream inputStream = new FileInputStream(filepath); 81 | byte[] buffer = new byte[(int)file.length()]; 82 | inputStream.read(buffer); 83 | 84 | String base64Content = Base64.encodeToString(buffer, Base64.NO_WRAP); 85 | 86 | callback.invoke(null, base64Content); 87 | } catch (Exception ex) { 88 | ex.printStackTrace(); 89 | callback.invoke(makeErrorPayload(ex)); 90 | } 91 | } 92 | 93 | @ReactMethod 94 | public void readDir(String directory, Callback callback) { 95 | try { 96 | File file = new File(directory); 97 | 98 | if (!file.exists()) throw new Exception("Folder does not exist"); 99 | 100 | File[] files = file.listFiles(); 101 | 102 | WritableArray fileMaps = Arguments.createArray(); 103 | 104 | for (File childFile : files) { 105 | WritableMap fileMap = Arguments.createMap(); 106 | 107 | fileMap.putString("name", childFile.getName()); 108 | fileMap.putString("path", childFile.getAbsolutePath()); 109 | fileMap.putInt("size", (int)childFile.length()); 110 | fileMap.putInt("type", childFile.isDirectory() ? 1 : 0); 111 | 112 | fileMaps.pushMap(fileMap); 113 | } 114 | 115 | callback.invoke(null, fileMaps); 116 | 117 | } catch (Exception ex) { 118 | ex.printStackTrace(); 119 | callback.invoke(makeErrorPayload(ex)); 120 | } 121 | } 122 | 123 | @ReactMethod 124 | public void stat(String filepath, Callback callback) { 125 | try { 126 | File file = new File(filepath); 127 | 128 | if (!file.exists()) throw new Exception("File does not exist"); 129 | 130 | WritableMap statMap = Arguments.createMap(); 131 | 132 | statMap.putInt("ctime", (int)(file.lastModified() / 1000)); 133 | statMap.putInt("mtime", (int)(file.lastModified() / 1000)); 134 | statMap.putInt("size", (int)file.length()); 135 | statMap.putInt("type", file.isDirectory() ? 1 : 0); 136 | 137 | callback.invoke(null, statMap); 138 | } catch (Exception ex) { 139 | ex.printStackTrace(); 140 | callback.invoke(makeErrorPayload(ex)); 141 | } 142 | } 143 | 144 | @ReactMethod 145 | public void unlink(String filepath, Callback callback) { 146 | try { 147 | File file = new File(filepath); 148 | 149 | if (!file.exists()) throw new Exception("File does not exist"); 150 | 151 | boolean success = DeleteRecursive(file); 152 | 153 | callback.invoke(null, success, filepath); 154 | } catch (Exception ex) { 155 | ex.printStackTrace(); 156 | callback.invoke(makeErrorPayload(ex)); 157 | } 158 | } 159 | 160 | private boolean DeleteRecursive(File fileOrDirectory) { 161 | if (fileOrDirectory.isDirectory()) { 162 | for (File child : fileOrDirectory.listFiles()) { 163 | DeleteRecursive(child); 164 | } 165 | } 166 | 167 | return fileOrDirectory.delete(); 168 | } 169 | 170 | @ReactMethod 171 | public void mkdir(String filepath, Boolean excludeFromBackup, Callback callback) { 172 | try { 173 | File file = new File(filepath); 174 | 175 | file.mkdirs(); 176 | 177 | boolean success = file.exists(); 178 | 179 | callback.invoke(null, success, filepath); 180 | } catch (Exception ex) { 181 | ex.printStackTrace(); 182 | callback.invoke(makeErrorPayload(ex)); 183 | } 184 | } 185 | 186 | private void sendEvent(ReactContext reactContext, String eventName, @Nullable WritableMap params) { 187 | reactContext 188 | .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) 189 | .emit(eventName, params); 190 | } 191 | 192 | @ReactMethod 193 | public void downloadFile(String urlStr, final String filepath, final int jobId, final Callback callback) { 194 | try { 195 | File file = new File(filepath); 196 | URL url = new URL(urlStr); 197 | 198 | DownloadParams params = new DownloadParams(); 199 | 200 | params.src = url; 201 | params.dest = file; 202 | 203 | params.onTaskCompleted = new DownloadParams.OnTaskCompleted() { 204 | public void onTaskCompleted(DownloadResult res) { 205 | if (res.exception == null) { 206 | WritableMap infoMap = Arguments.createMap(); 207 | 208 | infoMap.putInt("jobId", jobId); 209 | infoMap.putInt("statusCode", res.statusCode); 210 | infoMap.putInt("bytesWritten", res.bytesWritten); 211 | 212 | callback.invoke(null, infoMap); 213 | } else { 214 | callback.invoke(makeErrorPayload(res.exception)); 215 | } 216 | } 217 | }; 218 | 219 | params.onDownloadBegin = new DownloadParams.OnDownloadBegin() { 220 | public void onDownloadBegin(int statusCode, int contentLength, Map headers) { 221 | WritableMap headersMap = Arguments.createMap(); 222 | 223 | for (Map.Entry entry : headers.entrySet()) { 224 | headersMap.putString(entry.getKey(), entry.getValue()); 225 | } 226 | 227 | WritableMap data = Arguments.createMap(); 228 | 229 | data.putInt("jobId", jobId); 230 | data.putInt("statusCode", statusCode); 231 | data.putInt("contentLength", contentLength); 232 | data.putMap("headers", headersMap); 233 | 234 | sendEvent(getReactApplicationContext(), "DownloadBegin-" + jobId, data); 235 | } 236 | }; 237 | 238 | params.onDownloadProgress = new DownloadParams.OnDownloadProgress() { 239 | public void onDownloadProgress(int contentLength, int bytesWritten) { 240 | WritableMap data = Arguments.createMap(); 241 | data.putInt("contentLength", contentLength); 242 | data.putInt("bytesWritten", bytesWritten); 243 | 244 | sendEvent(getReactApplicationContext(), "DownloadProgress-" + jobId, data); 245 | } 246 | }; 247 | 248 | Downloader downloader = new Downloader(); 249 | 250 | downloader.execute(params); 251 | 252 | this.downloaders.put(jobId, downloader); 253 | } catch (Exception ex) { 254 | ex.printStackTrace(); 255 | callback.invoke(makeErrorPayload(ex)); 256 | } 257 | } 258 | 259 | @ReactMethod 260 | public void stopDownload(int jobId) { 261 | Downloader downloader = this.downloaders.get(jobId); 262 | 263 | if (downloader != null) { 264 | downloader.stop(); 265 | } 266 | } 267 | 268 | @ReactMethod 269 | public void pathForBundle(String bundleNamed, Callback callback) { 270 | // TODO: Not sure what equilivent would be? 271 | } 272 | 273 | private WritableMap makeErrorPayload(Exception ex) { 274 | WritableMap error = Arguments.createMap(); 275 | error.putString("message", ex.getMessage()); 276 | return error; 277 | } 278 | 279 | @Override 280 | public Map getConstants() { 281 | final Map constants = new HashMap<>(); 282 | constants.put(NSDocumentDirectory, 0); 283 | constants.put(NSDocumentDirectoryPath, this.getReactApplicationContext().getFilesDir().getAbsolutePath()); 284 | constants.put(NSFileTypeRegular, 0); 285 | constants.put(NSFileTypeDirectory, 1); 286 | return constants; 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /RNFileManger.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 8BF740771C033A2E0057A1E7 /* Downloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 8BF740761C033A2E0057A1E7 /* Downloader.m */; }; 11 | F1E59BDF1ADD662800ACA28A /* RNFileManager.m in Sources */ = {isa = PBXBuildFile; fileRef = F1E59BDE1ADD662800ACA28A /* RNFileManager.m */; }; 12 | F1EB08BB1AFD0E6A008F8F2B /* NSArray+Map.m in Sources */ = {isa = PBXBuildFile; fileRef = F1EB08BA1AFD0E6A008F8F2B /* NSArray+Map.m */; }; 13 | /* End PBXBuildFile section */ 14 | 15 | /* Begin PBXCopyFilesBuildPhase section */ 16 | F12AFB991ADAF8F800E0535D /* CopyFiles */ = { 17 | isa = PBXCopyFilesBuildPhase; 18 | buildActionMask = 2147483647; 19 | dstPath = "include/$(PRODUCT_NAME)"; 20 | dstSubfolderSpec = 16; 21 | files = ( 22 | ); 23 | runOnlyForDeploymentPostprocessing = 0; 24 | }; 25 | /* End PBXCopyFilesBuildPhase section */ 26 | 27 | /* Begin PBXFileReference section */ 28 | 8BF740751C033A2E0057A1E7 /* Downloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Downloader.h; sourceTree = ""; }; 29 | 8BF740761C033A2E0057A1E7 /* Downloader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Downloader.m; sourceTree = ""; }; 30 | F12AFB9B1ADAF8F800E0535D /* libRNFileManger.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNFileManger.a; sourceTree = BUILT_PRODUCTS_DIR; }; 31 | F1E59BDD1ADD662800ACA28A /* RNFileManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNFileManager.h; sourceTree = ""; }; 32 | F1E59BDE1ADD662800ACA28A /* RNFileManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RNFileManager.m; sourceTree = ""; }; 33 | F1EB08B91AFD0E6A008F8F2B /* NSArray+Map.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSArray+Map.h"; sourceTree = ""; }; 34 | F1EB08BA1AFD0E6A008F8F2B /* NSArray+Map.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Map.m"; sourceTree = ""; }; 35 | /* End PBXFileReference section */ 36 | 37 | /* Begin PBXFrameworksBuildPhase section */ 38 | F12AFB981ADAF8F800E0535D /* Frameworks */ = { 39 | isa = PBXFrameworksBuildPhase; 40 | buildActionMask = 2147483647; 41 | files = ( 42 | ); 43 | runOnlyForDeploymentPostprocessing = 0; 44 | }; 45 | /* End PBXFrameworksBuildPhase section */ 46 | 47 | /* Begin PBXGroup section */ 48 | F12AFB921ADAF8F800E0535D = { 49 | isa = PBXGroup; 50 | children = ( 51 | F1EB08B91AFD0E6A008F8F2B /* NSArray+Map.h */, 52 | F1EB08BA1AFD0E6A008F8F2B /* NSArray+Map.m */, 53 | F1E59BDD1ADD662800ACA28A /* RNFileManager.h */, 54 | F1E59BDE1ADD662800ACA28A /* RNFileManager.m */, 55 | 8BF740751C033A2E0057A1E7 /* Downloader.h */, 56 | 8BF740761C033A2E0057A1E7 /* Downloader.m */, 57 | F12AFB9C1ADAF8F800E0535D /* Products */, 58 | ); 59 | sourceTree = ""; 60 | }; 61 | F12AFB9C1ADAF8F800E0535D /* Products */ = { 62 | isa = PBXGroup; 63 | children = ( 64 | F12AFB9B1ADAF8F800E0535D /* libRNFileManger.a */, 65 | ); 66 | name = Products; 67 | sourceTree = ""; 68 | }; 69 | /* End PBXGroup section */ 70 | 71 | /* Begin PBXNativeTarget section */ 72 | F12AFB9A1ADAF8F800E0535D /* RNFileManger */ = { 73 | isa = PBXNativeTarget; 74 | buildConfigurationList = F12AFBAF1ADAF8F800E0535D /* Build configuration list for PBXNativeTarget "RNFileManger" */; 75 | buildPhases = ( 76 | F12AFB971ADAF8F800E0535D /* Sources */, 77 | F12AFB981ADAF8F800E0535D /* Frameworks */, 78 | F12AFB991ADAF8F800E0535D /* CopyFiles */, 79 | ); 80 | buildRules = ( 81 | ); 82 | dependencies = ( 83 | ); 84 | name = RNFileManger; 85 | productName = RNLocalNotification; 86 | productReference = F12AFB9B1ADAF8F800E0535D /* libRNFileManger.a */; 87 | productType = "com.apple.product-type.library.static"; 88 | }; 89 | /* End PBXNativeTarget section */ 90 | 91 | /* Begin PBXProject section */ 92 | F12AFB931ADAF8F800E0535D /* Project object */ = { 93 | isa = PBXProject; 94 | attributes = { 95 | LastUpgradeCheck = 0630; 96 | ORGANIZATIONNAME = "Johannes Lumpe"; 97 | TargetAttributes = { 98 | F12AFB9A1ADAF8F800E0535D = { 99 | CreatedOnToolsVersion = 6.3; 100 | }; 101 | }; 102 | }; 103 | buildConfigurationList = F12AFB961ADAF8F800E0535D /* Build configuration list for PBXProject "RNFileManger" */; 104 | compatibilityVersion = "Xcode 3.2"; 105 | developmentRegion = English; 106 | hasScannedForEncodings = 0; 107 | knownRegions = ( 108 | en, 109 | ); 110 | mainGroup = F12AFB921ADAF8F800E0535D; 111 | productRefGroup = F12AFB9C1ADAF8F800E0535D /* Products */; 112 | projectDirPath = ""; 113 | projectRoot = ""; 114 | targets = ( 115 | F12AFB9A1ADAF8F800E0535D /* RNFileManger */, 116 | ); 117 | }; 118 | /* End PBXProject section */ 119 | 120 | /* Begin PBXSourcesBuildPhase section */ 121 | F12AFB971ADAF8F800E0535D /* Sources */ = { 122 | isa = PBXSourcesBuildPhase; 123 | buildActionMask = 2147483647; 124 | files = ( 125 | F1E59BDF1ADD662800ACA28A /* RNFileManager.m in Sources */, 126 | F1EB08BB1AFD0E6A008F8F2B /* NSArray+Map.m in Sources */, 127 | 8BF740771C033A2E0057A1E7 /* Downloader.m in Sources */, 128 | ); 129 | runOnlyForDeploymentPostprocessing = 0; 130 | }; 131 | /* End PBXSourcesBuildPhase section */ 132 | 133 | /* Begin XCBuildConfiguration section */ 134 | F12AFBAD1ADAF8F800E0535D /* Debug */ = { 135 | isa = XCBuildConfiguration; 136 | buildSettings = { 137 | ALWAYS_SEARCH_USER_PATHS = NO; 138 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 139 | CLANG_CXX_LIBRARY = "libc++"; 140 | CLANG_ENABLE_MODULES = YES; 141 | CLANG_ENABLE_OBJC_ARC = YES; 142 | CLANG_WARN_BOOL_CONVERSION = YES; 143 | CLANG_WARN_CONSTANT_CONVERSION = YES; 144 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 145 | CLANG_WARN_EMPTY_BODY = YES; 146 | CLANG_WARN_ENUM_CONVERSION = YES; 147 | CLANG_WARN_INT_CONVERSION = YES; 148 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 149 | CLANG_WARN_UNREACHABLE_CODE = YES; 150 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 151 | COPY_PHASE_STRIP = NO; 152 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 153 | ENABLE_STRICT_OBJC_MSGSEND = YES; 154 | GCC_C_LANGUAGE_STANDARD = gnu99; 155 | GCC_DYNAMIC_NO_PIC = NO; 156 | GCC_NO_COMMON_BLOCKS = YES; 157 | GCC_OPTIMIZATION_LEVEL = 0; 158 | GCC_PREPROCESSOR_DEFINITIONS = ( 159 | "DEBUG=1", 160 | "$(inherited)", 161 | ); 162 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 163 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 164 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 165 | GCC_WARN_UNDECLARED_SELECTOR = YES; 166 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 167 | GCC_WARN_UNUSED_FUNCTION = YES; 168 | GCC_WARN_UNUSED_VARIABLE = YES; 169 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 170 | MTL_ENABLE_DEBUG_INFO = YES; 171 | ONLY_ACTIVE_ARCH = YES; 172 | SDKROOT = iphoneos; 173 | }; 174 | name = Debug; 175 | }; 176 | F12AFBAE1ADAF8F800E0535D /* Release */ = { 177 | isa = XCBuildConfiguration; 178 | buildSettings = { 179 | ALWAYS_SEARCH_USER_PATHS = NO; 180 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 181 | CLANG_CXX_LIBRARY = "libc++"; 182 | CLANG_ENABLE_MODULES = YES; 183 | CLANG_ENABLE_OBJC_ARC = YES; 184 | CLANG_WARN_BOOL_CONVERSION = YES; 185 | CLANG_WARN_CONSTANT_CONVERSION = YES; 186 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 187 | CLANG_WARN_EMPTY_BODY = YES; 188 | CLANG_WARN_ENUM_CONVERSION = YES; 189 | CLANG_WARN_INT_CONVERSION = YES; 190 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 191 | CLANG_WARN_UNREACHABLE_CODE = YES; 192 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 193 | COPY_PHASE_STRIP = NO; 194 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 195 | ENABLE_NS_ASSERTIONS = NO; 196 | ENABLE_STRICT_OBJC_MSGSEND = YES; 197 | GCC_C_LANGUAGE_STANDARD = gnu99; 198 | GCC_NO_COMMON_BLOCKS = YES; 199 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 200 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 201 | GCC_WARN_UNDECLARED_SELECTOR = YES; 202 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 203 | GCC_WARN_UNUSED_FUNCTION = YES; 204 | GCC_WARN_UNUSED_VARIABLE = YES; 205 | IPHONEOS_DEPLOYMENT_TARGET = 7.0; 206 | MTL_ENABLE_DEBUG_INFO = NO; 207 | SDKROOT = iphoneos; 208 | VALIDATE_PRODUCT = YES; 209 | }; 210 | name = Release; 211 | }; 212 | F12AFBB01ADAF8F800E0535D /* Debug */ = { 213 | isa = XCBuildConfiguration; 214 | buildSettings = { 215 | HEADER_SEARCH_PATHS = ( 216 | "$(inherited)", 217 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 218 | "$(SRCROOT)/../../React/**", 219 | "$(SRCROOT)/../react-native/React/**", 220 | "$(SRCROOT)/node_modules/react-native/React/**", 221 | ); 222 | OTHER_LDFLAGS = "-ObjC"; 223 | PRODUCT_NAME = RNFileManger; 224 | SKIP_INSTALL = YES; 225 | }; 226 | name = Debug; 227 | }; 228 | F12AFBB11ADAF8F800E0535D /* Release */ = { 229 | isa = XCBuildConfiguration; 230 | buildSettings = { 231 | HEADER_SEARCH_PATHS = ( 232 | "$(inherited)", 233 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 234 | "$(SRCROOT)/../../React/**", 235 | "$(SRCROOT)/../react-native/React/**", 236 | "$(SRCROOT)/node_modules/react-native/React/**", 237 | ); 238 | OTHER_LDFLAGS = "-ObjC"; 239 | PRODUCT_NAME = RNFileManger; 240 | SKIP_INSTALL = YES; 241 | }; 242 | name = Release; 243 | }; 244 | /* End XCBuildConfiguration section */ 245 | 246 | /* Begin XCConfigurationList section */ 247 | F12AFB961ADAF8F800E0535D /* Build configuration list for PBXProject "RNFileManger" */ = { 248 | isa = XCConfigurationList; 249 | buildConfigurations = ( 250 | F12AFBAD1ADAF8F800E0535D /* Debug */, 251 | F12AFBAE1ADAF8F800E0535D /* Release */, 252 | ); 253 | defaultConfigurationIsVisible = 0; 254 | defaultConfigurationName = Release; 255 | }; 256 | F12AFBAF1ADAF8F800E0535D /* Build configuration list for PBXNativeTarget "RNFileManger" */ = { 257 | isa = XCConfigurationList; 258 | buildConfigurations = ( 259 | F12AFBB01ADAF8F800E0535D /* Debug */, 260 | F12AFBB11ADAF8F800E0535D /* Release */, 261 | ); 262 | defaultConfigurationIsVisible = 0; 263 | defaultConfigurationName = Release; 264 | }; 265 | /* End XCConfigurationList section */ 266 | }; 267 | rootObject = F12AFB931ADAF8F800E0535D /* Project object */; 268 | } 269 | -------------------------------------------------------------------------------- /RNFileManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // RNFSManager.m 3 | // RNFSManager 4 | // 5 | // Created by Johannes Lumpe on 08/05/15. 6 | // Copyright (c) 2015 Johannes Lumpe. All rights reserved. 7 | // 8 | 9 | #import "RNFileManager.h" 10 | #import "RCTBridge.h" 11 | #import "NSArray+Map.h" 12 | #import "Downloader.h" 13 | #import "RCTEventDispatcher.h" 14 | 15 | @interface RNFileManager() 16 | 17 | @property (retain) NSMutableDictionary* downloaders; 18 | 19 | @end 20 | 21 | @implementation RNFileManager 22 | 23 | @synthesize bridge = _bridge; 24 | 25 | RCT_EXPORT_MODULE(); 26 | 27 | - (dispatch_queue_t)methodQueue 28 | { 29 | return dispatch_queue_create("pe.lum.rnfs", DISPATCH_QUEUE_SERIAL); 30 | } 31 | 32 | RCT_EXPORT_METHOD(readDir:(NSString *)dirPath 33 | callback:(RCTResponseSenderBlock)callback) 34 | { 35 | NSFileManager *fileManager = [NSFileManager defaultManager]; 36 | NSError *error = nil; 37 | 38 | NSArray *contents = [fileManager contentsOfDirectoryAtPath:dirPath error:&error]; 39 | 40 | contents = [contents rnfs_mapObjectsUsingBlock:^id(NSString *obj, NSUInteger idx) { 41 | NSString *path = [dirPath stringByAppendingPathComponent:obj]; 42 | NSDictionary *attributes = [fileManager attributesOfItemAtPath:path error:nil]; 43 | 44 | return @{ 45 | @"name": obj, 46 | @"path": path, 47 | @"size": [attributes objectForKey:NSFileSize], 48 | @"type": [attributes objectForKey:NSFileType] 49 | }; 50 | }]; 51 | 52 | if (error) { 53 | return callback([self makeErrorPayload:error]); 54 | } 55 | 56 | callback(@[[NSNull null], contents]); 57 | } 58 | 59 | RCT_EXPORT_METHOD(stat:(NSString *)filepath 60 | callback:(RCTResponseSenderBlock)callback) 61 | { 62 | NSError *error = nil; 63 | NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:filepath error:&error]; 64 | 65 | if (error) { 66 | return callback([self makeErrorPayload:error]); 67 | } 68 | 69 | attributes = @{ 70 | @"ctime": [self dateToTimeIntervalNumber:(NSDate *)[attributes objectForKey:NSFileCreationDate]], 71 | @"mtime": [self dateToTimeIntervalNumber:(NSDate *)[attributes objectForKey:NSFileModificationDate]], 72 | @"size": [attributes objectForKey:NSFileSize], 73 | @"type": [attributes objectForKey:NSFileType], 74 | @"mode": @([[NSString stringWithFormat:@"%ld", (long)[(NSNumber *)[attributes objectForKey:NSFilePosixPermissions] integerValue]] integerValue]) 75 | }; 76 | 77 | callback(@[[NSNull null], attributes]); 78 | } 79 | 80 | RCT_EXPORT_METHOD(writeFile:(NSString *)filepath 81 | contents:(NSString *)base64Content 82 | attributes:(NSDictionary *)attributes 83 | callback:(RCTResponseSenderBlock)callback) 84 | { 85 | NSData *data = [[NSData alloc] initWithBase64EncodedString:base64Content options:NSDataBase64DecodingIgnoreUnknownCharacters]; 86 | BOOL success = [[NSFileManager defaultManager] createFileAtPath:filepath contents:data attributes:attributes]; 87 | 88 | if (!success) { 89 | return callback(@[[NSString stringWithFormat:@"Could not write file at path %@", filepath]]); 90 | } 91 | 92 | callback(@[[NSNull null], [NSNumber numberWithBool:success], filepath]); 93 | } 94 | 95 | RCT_EXPORT_METHOD(unlink:(NSString*)filepath 96 | callback:(RCTResponseSenderBlock)callback) 97 | { 98 | NSFileManager *manager = [NSFileManager defaultManager]; 99 | BOOL exists = [manager fileExistsAtPath:filepath isDirectory:false]; 100 | 101 | if (!exists) { 102 | return callback(@[[NSString stringWithFormat:@"File at path %@ does not exist", filepath]]); 103 | } 104 | NSError *error = nil; 105 | BOOL success = [manager removeItemAtPath:filepath error:&error]; 106 | 107 | if (!success) { 108 | return callback([self makeErrorPayload:error]); 109 | } 110 | 111 | callback(@[[NSNull null], [NSNumber numberWithBool:success], filepath]); 112 | } 113 | 114 | RCT_EXPORT_METHOD(mkdir:(NSString*)filepath 115 | excludeFromBackup:(BOOL)excludeFromBackup 116 | callback:(RCTResponseSenderBlock)callback) 117 | { 118 | NSFileManager *manager = [NSFileManager defaultManager]; 119 | 120 | NSError *error = nil; 121 | BOOL success = [manager createDirectoryAtPath:filepath withIntermediateDirectories:YES attributes:nil error:&error]; 122 | 123 | if (!success) { 124 | return callback([self makeErrorPayload:error]); 125 | } 126 | 127 | NSURL *url = [NSURL fileURLWithPath:filepath]; 128 | 129 | success = [url setResourceValue: [NSNumber numberWithBool: excludeFromBackup] forKey: NSURLIsExcludedFromBackupKey error: &error]; 130 | 131 | if (!success) { 132 | return callback([self makeErrorPayload:error]); 133 | } 134 | 135 | callback(@[[NSNull null], [NSNumber numberWithBool:success], filepath]); 136 | } 137 | 138 | RCT_EXPORT_METHOD(readFile:(NSString *)filepath 139 | callback:(RCTResponseSenderBlock)callback) 140 | { 141 | NSData *content = [[NSFileManager defaultManager] contentsAtPath:filepath]; 142 | NSString *base64Content = [content base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithLineFeed]; 143 | 144 | if (!base64Content) { 145 | return callback(@[[NSString stringWithFormat:@"Could not read file at path %@", filepath]]); 146 | } 147 | 148 | callback(@[[NSNull null], base64Content]); 149 | } 150 | 151 | RCT_EXPORT_METHOD(downloadFile:(NSString *)urlStr 152 | filepath:(NSString *)filepath 153 | jobId:(nonnull NSNumber *)jobId 154 | callback:(RCTResponseSenderBlock)callback) 155 | { 156 | 157 | DownloadParams* params = [DownloadParams alloc]; 158 | 159 | params.fromUrl = urlStr; 160 | params.toFile = filepath; 161 | 162 | params.callback = ^(NSNumber* statusCode, NSNumber* bytesWritten) { 163 | if (bytesWritten == nil) { 164 | NSError *error = [NSError errorWithDomain:@"vrrv" code:0 userInfo:nil]; 165 | return callback([self makeErrorPayload:error]); 166 | } 167 | return callback(@[[NSNull null], @{@"jobId": jobId, 168 | @"statusCode": (statusCode ? statusCode : [NSNumber numberWithInt:0]), 169 | @"bytesWritten": (bytesWritten ? bytesWritten : [NSNumber numberWithInt:0])}]); 170 | }; 171 | 172 | params.errorCallback = ^(NSError* error) { 173 | return callback([self makeErrorPayload:error]); 174 | }; 175 | 176 | params.beginCallback = ^(NSNumber* statusCode, NSNumber* contentLength, NSDictionary* headers) { 177 | [self.bridge.eventDispatcher sendAppEventWithName:[NSString stringWithFormat:@"DownloadBegin-%@", jobId] 178 | body:@{@"jobId": jobId, 179 | @"statusCode": statusCode, 180 | @"contentLength": contentLength, 181 | @"headers": headers}]; 182 | }; 183 | 184 | params.progressCallback = ^(NSNumber* contentLength, NSNumber* bytesWritten) { 185 | [self.bridge.eventDispatcher sendAppEventWithName:[NSString stringWithFormat:@"DownloadProgress-%@", jobId] 186 | body:@{@"contentLength": contentLength, 187 | @"bytesWritten": bytesWritten}]; 188 | }; 189 | 190 | if (!self.downloaders) self.downloaders = [[NSMutableDictionary alloc] init]; 191 | 192 | Downloader* downloader = [Downloader alloc]; 193 | 194 | [downloader downloadFile:params]; 195 | 196 | [self.downloaders setValue:downloader forKey:[jobId stringValue]]; 197 | } 198 | 199 | RCT_EXPORT_METHOD(stopDownload:(nonnull NSNumber *)jobId) 200 | { 201 | Downloader* downloader = [self.downloaders objectForKey:[jobId stringValue]]; 202 | 203 | if (downloader != nil) { 204 | [downloader stopDownload]; 205 | } 206 | } 207 | 208 | RCT_EXPORT_METHOD(exists:(NSString *)filepath 209 | callback:(RCTResponseSenderBlock)callback) 210 | { 211 | BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:filepath]; 212 | 213 | callback(@[[NSNull null], [NSNumber numberWithBool:exists]]); 214 | } 215 | 216 | RCT_EXPORT_METHOD(folderExists:(NSString *)filepath 217 | callback:(RCTResponseSenderBlock)callback) 218 | { 219 | BOOL isDirectory; 220 | BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:filepath isDirectory:&isDirectory]; 221 | 222 | if (exists && isDirectory) { 223 | callback(@[[NSNull null], [NSNumber numberWithBool:YES]]); 224 | } else { 225 | callback(@[[NSNull null], [NSNumber numberWithBool:NO]]); 226 | } 227 | } 228 | 229 | RCT_EXPORT_METHOD(rename:(NSString *)filepath 230 | to:(NSString *)newname 231 | callback:(RCTResponseSenderBlock)callback) 232 | { 233 | NSString *newPath = [filepath.stringByDeletingLastPathComponent stringByAppendingPathComponent:newname]; 234 | NSError *error = nil; 235 | BOOL success = [[NSFileManager defaultManager] moveItemAtPath:filepath toPath:newPath error:&error]; 236 | 237 | callback(@[[NSNull null], [NSNumber numberWithBool:success]]); 238 | } 239 | 240 | RCT_EXPORT_METHOD(moveFile:(NSString *)filepath 241 | to:(NSString *)newPath 242 | callback:(RCTResponseSenderBlock)callback) 243 | { 244 | NSError *error = nil; 245 | BOOL success = [[NSFileManager defaultManager] moveItemAtPath:filepath toPath:newPath error:&error]; 246 | 247 | callback(@[[NSNull null], [NSNumber numberWithBool:success]]); 248 | } 249 | 250 | RCT_EXPORT_METHOD(pathForBundle:(NSString *)bundleNamed 251 | callback:(RCTResponseSenderBlock)callback) 252 | { 253 | NSString *path = [[NSBundle mainBundle].bundlePath stringByAppendingFormat:@"/%@.bundle", bundleNamed]; 254 | NSBundle *bundle = [NSBundle bundleWithPath:path]; 255 | 256 | if (!bundle) { 257 | bundle = [NSBundle bundleForClass:NSClassFromString(bundleNamed)]; 258 | path = bundle.bundlePath; 259 | } 260 | 261 | if (!bundle.isLoaded) { 262 | [bundle load]; 263 | } 264 | 265 | if (path) { 266 | callback(@[[NSNull null], path]); 267 | } else { 268 | callback(@[[NSError errorWithDomain:NSPOSIXErrorDomain 269 | code:NSFileNoSuchFileError 270 | userInfo:nil].localizedDescription, 271 | [NSNull null]]); 272 | } 273 | } 274 | 275 | - (NSNumber *)dateToTimeIntervalNumber:(NSDate *)date 276 | { 277 | return @([date timeIntervalSince1970]); 278 | } 279 | 280 | - (NSArray *)makeErrorPayload:(NSError *)error 281 | { 282 | return @[@{ 283 | @"description": error.localizedDescription, 284 | @"code": @(error.code) 285 | }]; 286 | } 287 | 288 | - (NSString *)getPathForDirectory:(int)directory 289 | { 290 | NSArray *paths = NSSearchPathForDirectoriesInDomains(directory, NSUserDomainMask, YES); 291 | return [paths firstObject]; 292 | } 293 | 294 | - (NSDictionary *)constantsToExport 295 | { 296 | return @{ 297 | @"MainBundlePath": [[NSBundle mainBundle] bundlePath], 298 | @"NSCachesDirectoryPath": [self getPathForDirectory:NSCachesDirectory], 299 | @"NSDocumentDirectoryPath": [self getPathForDirectory:NSDocumentDirectory], 300 | @"NSApplicationSupportDirectoryPath": [self getPathForDirectory:NSApplicationSupportDirectory], 301 | @"NSFileTypeRegular": NSFileTypeRegular, 302 | @"NSFileTypeDirectory": NSFileTypeDirectory 303 | }; 304 | } 305 | 306 | @end 307 | --------------------------------------------------------------------------------