/** * React Native FS * @flow */ 'use strict'; // This file supports both iOS and Android var RNFSManager = require('react-native').NativeModules.RNFSManager; var NativeAppEventEmitter = require('react-native').NativeAppEventEmitter; // iOS var DeviceEventEmitter = require('react-native').DeviceEventEmitter; // Android var base64 = require('base-64'); var utf8 = require('utf8'); var RNFSFileTypeRegular = RNFSManager.RNFSFileTypeRegular; var RNFSFileTypeDirectory = RNFSManager.RNFSFileTypeDirectory; var jobId = 0; var getJobId = () => { jobId += 1; return jobId; }; var normalizeFilePath = (path: string) => (path.startsWith('file://') ? path.slice(7) : path); type MkdirOptions = { NSURLIsExcludedFromBackupKey?: boolean; // iOS only }; type ReadDirItem = { ctime: ?Date; // The creation date of the file (iOS only) mtime: Date; // The last modified date of the file name: string; // The name of the item path: string; // The absolute path to the item size: string; // Size in bytes isFile: () => boolean; // Is the file just a file? isDirectory: () => boolean; // Is the file a directory? }; type StatResult = { name: string; // The name of the item path: string; // The absolute path to the item size: string; // Size in bytes mode: number; // UNIX file mode ctime: number; // Created date utime: number; // Updated date isFile: () => boolean; // Is the file just a file? isDirectory: () => boolean; // Is the file a directory? }; type Headers = { [name: string]: string }; type Fields = { [name: string]: string }; type DownloadFileOptions = { fromUrl: string; // URL to download file from toFile: string; // Local filesystem path to save the file to headers?: Headers; // An object of headers to be passed to the server background?: boolean; // iOS only progressDivider?: number; readTimeout?: number; connectionTimeout?: number; begin?: (res: DownloadBeginCallbackResult) => void; progress?: (res: DownloadProgressCallbackResult) => void; }; type DownloadBeginCallbackResult = { jobId: number; // The download job ID, required if one wishes to cancel the download. See `stopDownload`. statusCode: number; // The HTTP status code contentLength: number; // The total size in bytes of the download resource headers: Headers; // The HTTP response headers from the server }; type DownloadProgressCallbackResult = { jobId: number; // The download job ID, required if one wishes to cancel the download. See `stopDownload`. contentLength: number; // The total size in bytes of the download resource bytesWritten: number; // The number of bytes written to the file so far }; type DownloadResult = { jobId: number; // The download job ID, required if one wishes to cancel the download. See `stopDownload`. statusCode: number; // The HTTP status code bytesWritten: number; // The number of bytes written to the file }; type UploadFileOptions = { toUrl: string; // URL to upload file to files: UploadFileItem[]; // An array of objects with the file information to be uploaded. headers?: Headers; // An object of headers to be passed to the server fields?: Fields; // An object of fields to be passed to the server method?: string; // Default is 'POST', supports 'POST' and 'PUT' begin?: (res: UploadBeginCallbackResult) => void; progress?: (res: UploadProgressCallbackResult) => void; }; type UploadFileItem = { name: string; // Name of the file, if not defined then filename is used filename: string; // Name of file filepath: string; // Path to file filetype: string; // The mimetype of the file to be uploaded, if not defined it will get mimetype from `filepath` extension }; type UploadBeginCallbackResult = { jobId: number; // The upload job ID, required if one wishes to cancel the upload. See `stopUpload`. }; type UploadProgressCallbackResult = { jobId: number; // The upload job ID, required if one wishes to cancel the upload. See `stopUpload`. totalBytesExpectedToSend: number; // The total number of bytes that will be sent to the server totalBytesSent: number; // The number of bytes sent to the server }; type UploadResult = { jobId: number; // The upload job ID, required if one wishes to cancel the upload. See `stopUpload`. statusCode: number; // The HTTP status code headers: Headers; // The HTTP response headers from the server body: string; // The HTTP response body }; type FSInfoResult = { totalSpace: number; // The total amount of storage space on the device (in bytes). freeSpace: number; // The amount of available storage space on the device (in bytes). }; /** * Generic function used by readFile and readFileAssets */ function readFileGeneric(filepath: string, encodingOrOptions:?string, command: Function) { var options = { encoding: 'utf8' }; if (encodingOrOptions) { if (typeof encodingOrOptions === 'string') { options.encoding = encodingOrOptions; } else if (typeof encodingOrOptions === 'object') { options = encodingOrOptions; } } return command(normalizeFilePath(filepath)).then((b64) => { var contents; if (options.encoding === 'utf8') { contents = utf8.decode(base64.decode(b64)); } else if (options.encoding === 'ascii') { contents = base64.decode(b64); } else if (options.encoding === 'base64') { contents = b64; } else { throw new Error('Invalid encoding type "' + String(options.encoding) + '"'); } return contents; }); } /** * Generic function used by readDir and readDirAssets */ function readDirGeneric(dirpath: string, command: Function) { return command(normalizeFilePath(dirpath)).then(files => { return files.map(file => ({ ctime: file.ctime && new Date(file.ctime * 1000) || null, mtime: new Date(file.mtime * 1000), name: file.name, path: file.path, size: file.size, isFile: () => file.type === RNFSFileTypeRegular, isDirectory: () => file.type === RNFSFileTypeDirectory, })); }); } var RNFS = { mkdir(filepath: string, options: MkdirOptions = {}): Promise { return RNFSManager.mkdir(normalizeFilePath(filepath), options).then(() => void 0); }, moveFile(filepath: string, destPath: string): Promise { return RNFSManager.moveFile(normalizeFilePath(filepath), normalizeFilePath(destPath)).then(() => void 0); }, copyFile(filepath: string, destPath: string): Promise { return RNFSManager.copyFile(normalizeFilePath(filepath), normalizeFilePath(destPath)).then(() => void 0); }, pathForBundle(bundleNamed: string): Promise { return RNFSManager.pathForBundle(bundleNamed); }, pathForGroup(groupName: string): Promise { return RNFSManager.pathForGroup(groupName); }, getFSInfo(): Promise { return RNFSManager.getFSInfo(); }, unlink(filepath: string): Promise { return RNFSManager.unlink(normalizeFilePath(filepath)).then(() => void 0); }, exists(filepath: string): Promise { return RNFSManager.exists(normalizeFilePath(filepath)); }, stopDownload(jobId: number): void { RNFSManager.stopDownload(jobId); }, stopUpload(jobId: number): void { RNFSManager.stopUpload(jobId); }, readDir(dirpath: string): Promise { return readDirGeneric(dirpath, RNFSManager.readDir); }, // Android-only readDirAssets(dirpath: string): Promise { if (!RNFSManager.readDirAssets) { throw new Error('readDirAssets is not available on this platform'); } return readDirGeneric(dirpath, RNFSManager.readDirAssets); }, // Android-only existsAssets(filepath: string) { if (!RNFSManager.existsAssets) { throw new Error('existsAssets is not available on this platform'); } return RNFSManager.existsAssets(filepath); }, // Node style version (lowercase d). Returns just the names readdir(dirpath: string): Promise { return RNFS.readDir(normalizeFilePath(dirpath)).then(files => { return files.map(file => file.name); }); }, stat(filepath: string): Promise { return RNFSManager.stat(normalizeFilePath(filepath)).then((result) => { return { 'ctime': new Date(result.ctime * 1000), 'mtime': new Date(result.mtime * 1000), 'size': result.size, 'mode': result.mode, isFile: () => result.type === RNFSFileTypeRegular, isDirectory: () => result.type === RNFSFileTypeDirectory, }; }); }, readFile(filepath: string, encodingOrOptions?: any): Promise { return readFileGeneric(filepath, encodingOrOptions, RNFSManager.readFile); }, read(filepath: string, length: number = 0, position: number = 0, encodingOrOptions?: any): Promise { var options = { encoding: 'utf8' }; if (encodingOrOptions) { if (typeof encodingOrOptions === 'string') { options.encoding = encodingOrOptions; } else if (typeof encodingOrOptions === 'object') { options = encodingOrOptions; } } return RNFSManager.read(normalizeFilePath(filepath), length, position).then((b64) => { var contents; if (options.encoding === 'utf8') { contents = utf8.decode(base64.decode(b64)); } else if (options.encoding === 'ascii') { contents = base64.decode(b64); } else if (options.encoding === 'base64') { contents = b64; } else { throw new Error('Invalid encoding type "' + String(options.encoding) + '"'); } return contents; }); }, // Android only readFileAssets(filepath: string, encodingOrOptions?: any): Promise { if (!RNFSManager.readFileAssets) { throw new Error('readFileAssets is not available on this platform'); } return readFileGeneric(filepath, encodingOrOptions, RNFSManager.readFileAssets); }, hash(filepath: string, algorithm: string): Promise { return RNFSManager.hash(filepath, algorithm); }, // Android only copyFileAssets(filepath: string, destPath:string) { if (!RNFSManager.copyFileAssets) { throw new Error('copyFileAssets is not available on this platform'); } return RNFSManager.copyFileAssets(normalizeFilePath(filepath), normalizeFilePath(destPath)).then(() => void 0); }, // iOS only // Copies fotos from asset-library (camera-roll) to a specific location // with a given width or height // @see: https://developer.apple.com/reference/photos/phimagemanager/1616964-requestimageforasset copyAssetsFileIOS(imageUri: string, destPath: string, width: number, height: number, scale : number = 1.0, compression : number = 1.0, resizeMode : string = 'contain' ): Promise { return RNFSManager.copyAssetsFileIOS(imageUri, destPath, width, height, scale, compression, resizeMode ); }, writeFile(filepath: string, contents: string, encodingOrOptions?: any): Promise { var b64; var options = { encoding: 'utf8' }; if (encodingOrOptions) { if (typeof encodingOrOptions === 'string') { options.encoding = encodingOrOptions; } else if (typeof encodingOrOptions === 'object') { options = encodingOrOptions; } } if (options.encoding === 'utf8') { b64 = base64.encode(utf8.encode(contents)); } else if (options.encoding === 'ascii') { b64 = base64.encode(contents); } else if (options.encoding === 'base64') { b64 = contents; } else { throw new Error('Invalid encoding type "' + options.encoding + '"'); } return RNFSManager.writeFile(normalizeFilePath(filepath), b64).then(() => void 0); }, appendFile(filepath: string, contents: string, encodingOrOptions?: any): Promise { var b64; var options = { encoding: 'utf8' }; if (encodingOrOptions) { if (typeof encodingOrOptions === 'string') { options.encoding = encodingOrOptions; } else if (typeof encodingOrOptions === 'object') { options = encodingOrOptions; } } if (options.encoding === 'utf8') { b64 = base64.encode(utf8.encode(contents)); } else if (options.encoding === 'ascii') { b64 = base64.encode(contents); } else if (options.encoding === 'base64') { b64 = contents; } else { throw new Error('Invalid encoding type "' + options.encoding + '"'); } return RNFSManager.appendFile(normalizeFilePath(filepath), b64); }, write(filepath: string, contents: string, position?: number, encodingOrOptions?: any): Promise { var b64; var options = { encoding: 'utf8' }; if (encodingOrOptions) { if (typeof encodingOrOptions === 'string') { options.encoding = encodingOrOptions; } else if (typeof encodingOrOptions === 'object') { options = encodingOrOptions; } } if (options.encoding === 'utf8') { b64 = base64.encode(utf8.encode(contents)); } else if (options.encoding === 'ascii') { b64 = base64.encode(contents); } else if (options.encoding === 'base64') { b64 = contents; } else { throw new Error('Invalid encoding type "' + options.encoding + '"'); } if (position === undefined) { position = -1; } return RNFSManager.write(normalizeFilePath(filepath), b64, position).then(() => void 0); }, downloadFile(options: DownloadFileOptions): { jobId: number, promise: Promise } { if (typeof options !== 'object') throw new Error('downloadFile: Invalid value for argument `options`'); if (typeof options.fromUrl !== 'string') throw new Error('downloadFile: Invalid value for property `fromUrl`'); if (typeof options.toFile !== 'string') throw new Error('downloadFile: Invalid value for property `toFile`'); if (options.headers && typeof options.headers !== 'object') throw new Error('downloadFile: Invalid value for property `headers`'); if (options.background && typeof options.background !== 'boolean') throw new Error('downloadFile: Invalid value for property `background`'); if (options.progressDivider && typeof options.progressDivider !== 'number') throw new Error('downloadFile: Invalid value for property `progressDivider`'); if (options.readTimeout && typeof options.readTimeout !== 'number') throw new Error('downloadFile: Invalid value for property `readTimeout`'); if (options.connectionTimeout && typeof options.connectionTimeout !== 'number') throw new Error('downloadFile: Invalid value for property `connectionTimeout`'); var jobId = getJobId(); var subscriptions = []; if (options.begin) { subscriptions.push(NativeAppEventEmitter.addListener('DownloadBegin-' + jobId, options.begin)); } if (options.progress) { subscriptions.push(NativeAppEventEmitter.addListener('DownloadProgress-' + jobId, options.progress)); } var bridgeOptions = { jobId: jobId, fromUrl: options.fromUrl, toFile: normalizeFilePath(options.toFile), headers: options.headers || {}, background: !!options.background, progressDivider: options.progressDivider || 0, readTimeout: options.readTimeout || 15000, connectionTimeout: options.connectionTimeout || 5000 }; return { jobId, promise: RNFSManager.downloadFile(bridgeOptions).then(res => { subscriptions.forEach(sub => sub.remove()); return res; }) }; }, uploadFiles(options: UploadFileOptions): { jobId: number, promise: Promise } { if (!RNFSManager.uploadFiles) { return { jobId: -1, promise: Promise.reject(new Error('`uploadFiles` is unsupported on this platform')) }; } var jobId = getJobId(); var subscriptions = []; if (typeof options !== 'object') throw new Error('uploadFiles: Invalid value for argument `options`'); if (typeof options.toUrl !== 'string') throw new Error('uploadFiles: Invalid value for property `toUrl`'); if (!Array.isArray(options.files)) throw new Error('uploadFiles: Invalid value for property `files`'); if (options.headers && typeof options.headers !== 'object') throw new Error('uploadFiles: Invalid value for property `headers`'); if (options.fields && typeof options.fields !== 'object') throw new Error('uploadFiles: Invalid value for property `fields`'); if (options.method && typeof options.method !== 'string') throw new Error('uploadFiles: Invalid value for property `method`'); if (options.begin) { subscriptions.push(NativeAppEventEmitter.addListener('UploadBegin-' + jobId, options.begin)); } if (options.beginCallback && options.beginCallback instanceof Function) { // Deprecated subscriptions.push(NativeAppEventEmitter.addListener('UploadBegin-' + jobId, options.beginCallback)); } if (options.progress) { subscriptions.push(NativeAppEventEmitter.addListener('UploadProgress-' + jobId, options.progress)); } if (options.progressCallback && options.progressCallback instanceof Function) { // Deprecated subscriptions.push(NativeAppEventEmitter.addListener('UploadProgress-' + jobId, options.progressCallback)); } var bridgeOptions = { jobId: jobId, toUrl: options.toUrl, files: options.files, headers: options.headers || {}, fields: options.fields || {}, method: options.method || 'POST' }; return { jobId, promise: RNFSManager.uploadFiles(bridgeOptions).then(res => { subscriptions.forEach(sub => sub.remove()); return res; }) }; }, touch(filepath: string, mtime?: Date, ctime?: Date): Promise { if (ctime && !(ctime instanceof Date)) throw new Error('touch: Invalid value for argument `ctime`'); if (mtime && !(mtime instanceof Date)) throw new Error('touch: Invalid value for argument `mtime`'); return RNFSManager.touch( normalizeFilePath(filepath), mtime && mtime.getTime(), ctime && ctime.getTime() ); }, MainBundlePath: RNFSManager.RNFSMainBundlePath, CachesDirectoryPath: RNFSManager.RNFSCachesDirectoryPath, DocumentDirectoryPath: RNFSManager.RNFSDocumentDirectoryPath, ExternalDirectoryPath: RNFSManager.RNFSExternalDirectoryPath, ExternalStorageDirectoryPath: RNFSManager.RNFSExternalStorageDirectoryPath, TemporaryDirectoryPath: RNFSManager.RNFSTemporaryDirectoryPath, LibraryDirectoryPath: RNFSManager.RNFSLibraryDirectoryPath, PicturesDirectoryPath: RNFSManager.RNFSPicturesDirectoryPath }; module.exports = RNFS;