diff --git a/android/src/main/java/io/fullstack/firestack/FirestackStorage.java b/android/src/main/java/io/fullstack/firestack/FirestackStorage.java index b41301d..21ed217 100644 --- a/android/src/main/java/io/fullstack/firestack/FirestackStorage.java +++ b/android/src/main/java/io/fullstack/firestack/FirestackStorage.java @@ -5,6 +5,9 @@ import android.content.Context; import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; import java.util.Map; import java.util.HashMap; @@ -26,6 +29,8 @@ import com.google.android.gms.tasks.OnFailureListener; import com.google.android.gms.tasks.OnSuccessListener; +import com.google.firebase.storage.StorageException; +import com.google.firebase.storage.StreamDownloadTask; import com.google.firebase.storage.UploadTask; import com.google.firebase.storage.FirebaseStorage; import com.google.firebase.storage.StorageMetadata; @@ -49,6 +54,18 @@ class FirestackStorageModule extends ReactContextBaseJavaModule { private static final String FileTypeRegular = "FILETYPE_REGULAR"; private static final String FileTypeDirectory = "FILETYPE_DIRECTORY"; + private static final String STORAGE_UPLOAD_PROGRESS = "upload_progress"; + private static final String STORAGE_UPLOAD_PAUSED = "upload_paused"; + private static final String STORAGE_UPLOAD_RESUMED = "upload_resumed"; + + private static final String STORAGE_DOWNLOAD_PROGRESS = "download_progress"; + private static final String STORAGE_DOWNLOAD_PAUSED = "download_paused"; + private static final String STORAGE_DOWNLOAD_RESUMED = "download_resumed"; + private static final String STORAGE_DOWNLOAD_SUCCESS = "download_success"; + private static final String STORAGE_DOWNLOAD_FAILURE = "download_failure"; + + private ReactContext mReactContext; + public FirestackStorageModule(ReactApplicationContext reactContext) { super(reactContext); @@ -60,6 +77,118 @@ public String getName() { return TAG; } + + public boolean isExternalStorageWritable() { + String state = Environment.getExternalStorageState(); + if (Environment.MEDIA_MOUNTED.equals(state)) { + return true; + } + return false; + } + + @ReactMethod + public void downloadFile(final String urlStr, + final String fbPath, + final String localFile, + final Callback callback) { + Log.d(TAG, "downloadFile: "+urlStr+", "+localFile); + if (!isExternalStorageWritable()) { + Log.w(TAG, "downloadFile failed: external storage not writable"); + WritableMap error = Arguments.createMap(); + final int errorCode = 1; + error.putDouble("code", errorCode); + error.putString("description", "downloadFile failed: external storage not writable"); + callback.invoke(error); + return; + } + FirebaseStorage storage = FirebaseStorage.getInstance(); + String storageBucket = storage.getApp().getOptions().getStorageBucket(); + String storageUrl = "gs://" + storageBucket; + Log.d(TAG, "Storage url " + storageUrl + fbPath); + + StorageReference storageRef = storage.getReferenceFromUrl(storageUrl); + StorageReference fileRef = storageRef.child(fbPath); + + fileRef.getStream(new StreamDownloadTask.StreamProcessor() { + @Override + public void doInBackground(StreamDownloadTask.TaskSnapshot taskSnapshot, InputStream inputStream) throws IOException { + int indexOfLastSlash = localFile.lastIndexOf("/"); + String pathMinusFileName = localFile.substring(0, indexOfLastSlash) + "/"; + String filename = localFile.substring(indexOfLastSlash+1); + File fileWithJustPath = new File(pathMinusFileName); + if (!fileWithJustPath.mkdirs()) { + Log.e(TAG, "Directory not created"); + WritableMap error = Arguments.createMap(); + error.putString("message", "Directory not created"); + callback.invoke(error); + return; + } + File fileWithFullPath = new File(pathMinusFileName, filename); + FileOutputStream output = new FileOutputStream(fileWithFullPath); + int bufferSize = 1024; + byte[] buffer = new byte[bufferSize]; + int len = 0; + while ((len = inputStream.read(buffer)) != -1) { + output.write(buffer, 0, len); + } + output.close(); + } + }).addOnProgressListener(new OnProgressListener() { + @Override + public void onProgress(StreamDownloadTask.TaskSnapshot taskSnapshot) { + WritableMap data = Arguments.createMap(); + data.putString("ref", taskSnapshot.getStorage().getBucket()); + double percentComplete = taskSnapshot.getTotalByteCount() == 0 ? 0.0f : 100.0f * (taskSnapshot.getBytesTransferred()) / (taskSnapshot.getTotalByteCount()); + data.putDouble("progress", percentComplete); + FirestackUtils.sendEvent(mReactContext, STORAGE_DOWNLOAD_PROGRESS, data); + } + }).addOnPausedListener(new OnPausedListener() { + @Override + public void onPaused(StreamDownloadTask.TaskSnapshot taskSnapshot) { + WritableMap data = Arguments.createMap(); + data.putString("ref", taskSnapshot.getStorage().getBucket()); + FirestackUtils.sendEvent(mReactContext, STORAGE_DOWNLOAD_PAUSED, data); + } + }).addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(StreamDownloadTask.TaskSnapshot taskSnapshot) { + final WritableMap data = Arguments.createMap(); + StorageReference ref = taskSnapshot.getStorage(); + data.putString("fullPath", ref.getPath()); + data.putString("bucket", ref.getBucket()); + data.putString("name", ref.getName()); + ref.getMetadata().addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(final StorageMetadata storageMetadata) { + data.putMap("metadata", getMetadataAsMap(storageMetadata)); + callback.invoke(null, data); + } + }) + .addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception exception) { + final int errorCode = 1; + WritableMap data = Arguments.createMap(); + StorageException storageException = StorageException.fromException(exception); + data.putString("description", storageException.getMessage()); + data.putInt("code", errorCode); + callback.invoke(makeErrorPayload(errorCode, exception)); + } + }); + } + }).addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception exception) { + final int errorCode = 1; + WritableMap data = Arguments.createMap(); + StorageException storageException = StorageException.fromException(exception); + data.putString("description", storageException.getMessage()); + data.putInt("code", errorCode); + callback.invoke(makeErrorPayload(errorCode, exception)); + } + }); + } + @ReactMethod public void downloadUrl(final String javascriptStorageBucket, final String path, @@ -90,16 +219,7 @@ public void onSuccess(Uri uri) { public void onSuccess(final StorageMetadata storageMetadata) { Log.d(TAG, "getMetadata success " + storageMetadata); - WritableMap metadata = Arguments.createMap(); - metadata.putString("getBucket", storageMetadata.getBucket()); - metadata.putString("getName", storageMetadata.getName()); - metadata.putDouble("sizeBytes", storageMetadata.getSizeBytes()); - metadata.putDouble("created_at", storageMetadata.getCreationTimeMillis()); - metadata.putDouble("updated_at", storageMetadata.getUpdatedTimeMillis()); - metadata.putString("md5hash", storageMetadata.getMd5Hash()); - metadata.putString("encoding", storageMetadata.getContentEncoding()); - - res.putMap("metadata", metadata); + res.putMap("metadata", getMetadataAsMap(storageMetadata)); res.putString("name", storageMetadata.getName()); res.putString("url", storageMetadata.getDownloadUrl().toString()); callback.invoke(null, res); @@ -109,7 +229,8 @@ public void onSuccess(final StorageMetadata storageMetadata) { @Override public void onFailure(@NonNull Exception exception) { Log.e(TAG, "Failure in download " + exception); - callback.invoke(makeErrorPayload(1, exception)); + final int errorCode = 1; + callback.invoke(makeErrorPayload(errorCode, exception)); } }); @@ -129,6 +250,18 @@ public void onFailure(@NonNull Exception exception) { }); } + private WritableMap getMetadataAsMap(StorageMetadata storageMetadata) { + WritableMap metadata = Arguments.createMap(); + metadata.putString("getBucket", storageMetadata.getBucket()); + metadata.putString("getName", storageMetadata.getName()); + metadata.putDouble("sizeBytes", storageMetadata.getSizeBytes()); + metadata.putDouble("created_at", storageMetadata.getCreationTimeMillis()); + metadata.putDouble("updated_at", storageMetadata.getUpdatedTimeMillis()); + metadata.putString("md5hash", storageMetadata.getMd5Hash()); + metadata.putString("encoding", storageMetadata.getContentEncoding()); + return metadata; + } + // STORAGE @ReactMethod public void uploadFile(final String urlStr, final String name, final String filepath, final ReadableMap metadata, final Callback callback) { @@ -191,9 +324,9 @@ public void onProgress(UploadTask.TaskSnapshot taskSnapshot) { if (progress >= 0) { WritableMap data = Arguments.createMap(); - data.putString("eventName", "upload_progress"); + data.putString("eventName", STORAGE_UPLOAD_PROGRESS); data.putDouble("progress", progress); - FirestackUtils.sendEvent(getReactApplicationContext(), "upload_progress", data); + FirestackUtils.sendEvent(getReactApplicationContext(), STORAGE_UPLOAD_PROGRESS, data); } } }) @@ -204,13 +337,14 @@ public void onPaused(UploadTask.TaskSnapshot taskSnapshot) { StorageMetadata d = taskSnapshot.getMetadata(); String bucket = d.getBucket(); WritableMap data = Arguments.createMap(); - data.putString("eventName", "upload_paused"); + data.putString("eventName", STORAGE_UPLOAD_PAUSED); data.putString("ref", bucket); - FirestackUtils.sendEvent(getReactApplicationContext(), "upload_paused", data); + FirestackUtils.sendEvent(getReactApplicationContext(), STORAGE_UPLOAD_PAUSED, data); } }); } catch (Exception ex) { - callback.invoke(makeErrorPayload(2, ex)); + final int errorCode = 2; + callback.invoke(makeErrorPayload(errorCode, ex)); } } @@ -221,7 +355,8 @@ public void getRealPathFromURI(final String uri, final Callback callback) { callback.invoke(null, path); } catch (Exception ex) { ex.printStackTrace(); - callback.invoke(makeErrorPayload(1, ex)); + final int errorCode = 1; + callback.invoke(makeErrorPayload(errorCode, ex)); } } diff --git a/lib/modules/database.js b/lib/modules/database.js deleted file mode 100644 index cbf3bc4..0000000 --- a/lib/modules/database.js +++ /dev/null @@ -1,518 +0,0 @@ -/** - * Database representation wrapper - */ -import {NativeModules, NativeEventEmitter} from 'react-native'; -const FirestackDatabase = NativeModules.FirestackDatabase; -const FirestackDatabaseEvt = new NativeEventEmitter(FirestackDatabase); - -import promisify from '../utils/promisify' -import { Base, ReferenceBase } from './base' - -let dbSubscriptions = {}; - -class DataSnapshot { - static key:String; - static value:Object; - static exists:boolean; - static hasChildren:boolean; - static childrenCount:Number; - static childKeys:String[]; - - constructor(ref, snapshot) { - this.ref = ref; - this.key = snapshot.key; - this.value = snapshot.value; - this.exists = snapshot.exists || true; - this.priority = snapshot.priority; - this.hasChildren = snapshot.hasChildren || false; - this.childrenCount = snapshot.childrenCount || 0; - this.childKeys = snapshot.childKeys || []; - } - - val() { - return this.value; - } - - forEach(fn) { - (this.childKeys || []) - .forEach(key => fn({key: key, value: this.value[key]})) - } - - map(fn) { - let arr = []; - this.forEach(item => arr.push(fn(item))) - return arr; - } - - reverseMap(fn) { - return this.map(fn).reverse(); - } -} - -class DatabaseOnDisconnect { - constructor(ref) { - this.ref = ref; - } - - setValue(val) { - const path = this.ref.dbPath(); - if (typeof val == 'string') { - return promisify('onDisconnectSetString', FirestackDatabase)(path, val); - } else if (typeof val == 'object') { - return promisify('onDisconnectSetObject', FirestackDatabase)(path, val); - } - } - - remove() { - const path = this.ref.dbPath(); - return promisify('onDisconnectRemove', FirestackDatabase)(path); - } - - cancel() { - const path = this.ref.dbPath(); - return promisify('onDisconnectCancel', FirestackDatabase)(path); - } -} - -class DatabaseQuery { - static ref: DatabaseRef; - static orderBy: String[]; - static limit: String[]; - static filters: Object; - - constructor(ref) { - this.ref = ref; - this.reset(); - } - - setOrderBy(name, ...args) { - this.orderBy = [name].concat(args); - return this.ref; - } - - setLimit(name, ...args) { - this.limit = [name].concat(args); - return this.ref; - } - - setFilter(name, ...args) { - this.filters[name] = args.filter(n => n != undefined); - return this.ref; - } - - build() { - const argsSeparator = ':' - let modifiers = []; - if (this.orderBy) { - modifiers.push(this.orderBy.join(argsSeparator)); - } - if (this.limit) { - modifiers.push(this.limit.join(argsSeparator)); - } - Object.keys(this.filters) - .forEach(key => { - let filter = this.filters[key]; - if (filter) { - const cleanFilters = filter.filter((f) => typeof f !== "undefined"); - const filterArgs = ([key].concat(cleanFilters)).join(argsSeparator); - modifiers.push(filterArgs); - } - }) - return modifiers; - } - - reset() { - this.orderBy = null; - this.limit = null; - this.filters = {}; - ['startAt', 'endAt', 'equalTo'] - .forEach(key => this.filters[key] = null); - return this.ref; - } -} - -// https://firebase.google.com/docs/reference/js/firebase.database.Reference -const separator = '/'; -class DatabaseRef extends ReferenceBase { - constructor(db, path) { - super(db.firestack, path); - - this.db = db; - this.query = new DatabaseQuery(this); - this.listeners = {}; - - // Aliases - this.get = this.getAt; - this.set = this.setAt; - this.update = this.updateAt; - this.remove = this.removeAt; - - this.log.debug('Created new DatabaseRef', this.dbPath()); - } - - // Parent roots - parent() { - const parentPaths = this.path.slice(0, -1); - return new DatabaseRef(this.db, parentPaths); - } - - root() { - return new DatabaseRef(this.db, []); - } - - child(...paths) { - return new DatabaseRef(this.db, this.path.concat(paths)); - } - - keepSynced(bool) { - const path = this.dbPath(); - return promisify('keepSynced', FirestackDatabase)(path, bool); - } - - // Get the value of a ref either with a key - getAt() { - const path = this.dbPath(); - const modifiers = this.dbModifiers(); - return promisify('onOnce', FirestackDatabase)(path, modifiers, 'value'); - } - - setAt(val) { - const path = this.dbPath(); - const value = this._serializeValue(val); - return promisify('set', FirestackDatabase)(path, value) - } - - updateAt(val) { - const path = this.dbPath(); - const value = this._serializeValue(val); - return promisify('update', FirestackDatabase)(path, value) - } - - removeAt(key) { - const path = this.dbPath(); - return promisify('remove', FirestackDatabase)(path) - } - - push(val={}) { - const path = this.dbPath(); - const value = this._serializeValue(val); - return promisify('push', FirestackDatabase)(path, value) - .then(({ref}) => { - return new DatabaseRef(this.db, ref.split(separator)) - }) - } - - on(evt, cb) { - const path = this.dbPath(); - const modifiers = this.dbModifiers(); - return this.db.on(path, evt, cb) - .then(({callback, subscriptions}) => { - return promisify('on', FirestackDatabase)(path, modifiers, evt) - .then(() => { - this.listeners[evt] = subscriptions; - callback(this); - return subscriptions; - }) - }); - } - - once(evt='once', cb) { - const path = this.dbPath(); - const modifiers = this.dbModifiers(); - return promisify('onOnce', FirestackDatabase)(path, modifiers, evt) - .then(({snapshot}) => new DataSnapshot(this, snapshot)) - .then(snapshot => { - if (cb && typeof cb === 'function') { - cb(snapshot); - } - return snapshot; - }) - } - - off(evt='', origCB) { - const path = this.dbPath(); - return this.db.off(path, evt, origCB) - .then(({callback, subscriptions}) => { - if (dbSubscriptions[path] && dbSubscriptions[path][evt] && dbSubscriptions[path][evt].length > 0) { - return subscriptions; - } - - return promisify('off', FirestackDatabase)(path, evt) - .then(() => { - // subscriptions.forEach(sub => sub.remove()); - delete this.listeners[evt]; - callback(this); - return subscriptions; - }) - }) - .catch(err => { - console.error('Never get here', err); - }) - } - - cleanup() { - let promises = Object.keys(this.listeners) - .map(key => this.off(key)) - return Promise.all(promises); - } - - // Sanitize value - // As Firebase cannot store date objects. - _serializeValue(obj={}) { - return Object.keys(obj).reduce((sum, key) => { - let val = obj[key]; - if (val instanceof Date) { - val = val.toISOString(); - } - return { - ...sum, - [key]: val - } - }, {}); - } - - _deserializeValue(obj={}) { - return Object.keys(obj).reduce((sum, key) => { - let val = obj[key]; - if (val instanceof Date) { - val = val.getTime(); - } - return { - ...sum, - [key]: val - } - }, {}); - } - - // Modifiers - orderByKey() { - return this.query.setOrderBy('orderByKey'); - } - - orderByPriority() { - return this.query.setOrderBy('orderByPriority'); - } - - orderByValue() { - return this.query.setOrderBy('orderByValue'); - } - - orderByChild(key) { - return this.query.setOrderBy('orderByChild', key); - } - - // Limits - limitToLast(limit) { - return this.query.setLimit('limitToLast', limit); - } - - limitToFirst(limit) { - return this.query.setLimit('limitToFirst', limit); - } - - // Filters - equalTo(value, key) { - return this.query.setFilter('equalTo', value, key); - } - - endAt(value, key) { - return this.query.setFilter('endAt', value, key); - } - - startAt(value, key) { - return this.query.setFilter('startAt', value, key); - } - - presence(path) { - const presence = this.firestack.presence; - const ref = path ? this.child(path) : this; - return presence.ref(ref, this.dbPath()); - } - - // onDisconnect - onDisconnect() { - return new DatabaseOnDisconnect(this); - } - - // attributes - get fullPath() { - return this.dbPath(); - } - - get name() { - return this.path.splice(-1); - } - - dbPath() { - let path = this.path; - let pathStr = (path.length > 0 ? path.join('/') : '/'); - if (pathStr[0] != '/') { - pathStr = `/${pathStr}` - } - return pathStr; - } - - dbModifiers() { - const modifiers = this.query.build(); - this.query.reset(); // should we reset this - return modifiers; - } - - get namespace() { - return `firestack:dbRef` - } -} - -export class Database extends Base { - - constructor(firestack, options={}) { - super(firestack, options); - this.log.debug('Created new Database instance', this.options); - - this.persistenceEnabled = false; - this.successListener = null; - this.errorListener = null; - this.refs = {}; - } - - ref(...path) { - const key = this._pathKey(path); - if (!this.refs[key]) { - const ref = new DatabaseRef(this, path); - this.refs[key] = ref; - } - return this.refs[key]; - } - - setPersistence(enable=true) { - let promise; - if (this.persistenceEnabled !== enable) { - this.log.debug(`${enable ? 'Enabling' : 'Disabling'} persistence`); - promise = this.whenReady(promisify('enablePersistence', FirestackDatabase)(enable)); - this.persistenceEnabled = enable; - } else { - promise = this.whenReady(Promise.resolve({status: "Already enabled"})) - } - - return promise; - } - - handleDatabaseEvent(evt) { - const body = evt.body; - const path = body.path; - const evtName = body.eventName; - - const subscriptions = dbSubscriptions[path]; - - if (subscriptions) { - const cbs = subscriptions[evtName]; - cbs.forEach(cb => { - if (cb && typeof(cb) === 'function') { - const snap = new DataSnapshot(this, body.snapshot); - this.log.debug('database_event received', path, evtName); - cb(snap, body); - } - }); - } - } - - handleDatabaseError(evt) { - this.log.debug('handleDatabaseError ->', evt); - } - - on(path, evt, cb) { - const key = this._pathKey(path); - - if (!dbSubscriptions[key]) { - dbSubscriptions[key] = {}; - } - - if (!dbSubscriptions[key][evt]) { - dbSubscriptions[key][evt] = []; - } - dbSubscriptions[key][evt].push(cb); - - if (!this.successListener) { - this.successListener = FirestackDatabaseEvt - .addListener( - 'database_event', - this.handleDatabaseEvent.bind(this)); - } - - if (!this.errorListener) { - this.errorListener = FirestackDatabaseEvt - .addListener( - 'database_error', - this.handleDatabaseError.bind(this)); - } - - const callback = (ref) => { - const key = this._pathKey(ref.path); - this.refs[key] = ref; - } - const subscriptions = [this.successListener, this.errorListener]; - return Promise.resolve({callback, subscriptions}); - } - - off(path, evt, origCB) { - const key = this._pathKey(path); - // Remove subscription - if (dbSubscriptions[key]) { - if (!evt || evt === "") { - dbSubscriptions[key] = {}; - } else if (dbSubscriptions[key][evt]) { - if (origCB) { - dbSubscriptions[key][evt].splice(dbSubscriptions[key][evt].indexOf(origCB), 1); - } else { - delete dbSubscriptions[key][evt]; - } - } - - if (Object.keys(dbSubscriptions[key]).length <= 0) { - // there are no more subscriptions - // so we can unwatch - delete dbSubscriptions[key] - } - if (Object.keys(dbSubscriptions).length == 0) { - if (this.successListener) { - this.successListener.remove(); - this.successListener = null; - } - if (this.errorListener) { - this.errorListener.remove(); - this.errorListener = null; - } - } - } - const callback = (ref) => { - const key = this._pathKey(ref.path); - delete this.refs[key]; - } - const subscriptions = [this.successListener, this.errorListener]; - return Promise.resolve({callback, subscriptions}); - } - - cleanup() { - let promises = Object.keys(this.refs) - .map(key => this.refs[key]) - .map(ref => ref.cleanup()) - return Promise.all(promises); - } - - release(...path) { - const key = this._pathKey(path); - if (this.refs[key]) { - delete this.refs[key]; - } - } - - _pathKey(...path) { - return path.join('-'); - } - - get namespace() { - return 'firestack:database' - } -} - -export default Database pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy