How to hash image data in React-Native (Expo)? - react-native

My intent:
I want my app to upload images to S3. If image already exists, server should record a reference to existing image rather than asking for an upload of another copy.
How I imagine that works:
Hash image data
Send hash to server with request for signed url (to upload to AWS S3)
If hash matches something already stored, reference it and tell app
Initial thoughts:
Use imageEditor.cropImage to get image into ImageStore, which will give me an appropriate uri. Then use getBase64ForTag(uri, success, failure) to retrieve base64 data for a hash calculation.
The problem:
According to the answer on this question, this process is not efficient in the least. The usual solution would be to use native methods, as described in the answer to this question, however I do not want to eject my Expo app for this feature.
My Question:
Is there a better way to hash image data? Or more fundamentally, is there a better way of ensuring that identical images are not duplicated in S3 storage?

EDIT 2020-10-21 :
The library updated itself, and you should now call:
_hashImage = async (imageUri) => {
return await FileSystem.getInfoAsync(imageUri, { md5: true } );
}
ORIGINAL:
It turns out that Expo provides this out of the box.
Expo.FileSystem.getInfoAsync
myImageHashFunction = async (imageUri) => {
let fsInfo = await Expo.FileSystem.getInfoAsync(imageUri, [{ md5: true }] )
console.log(fsInfo.md5)
}

If you are still looking for a solution:
This is how I got it working - create a base64 of the image and then create a hash of it.
import * as FileSystem from 'expo-file-system';
import * as Crypto from 'expo-crypto';
let info = await FileSystem.readAsStringAsync(imageUri,
{ encoding: FileSystem.EncodingType.Base64 });
const hashData = await Crypto.digestStringAsync (
Crypto.CryptoDigestAlgorithm.MD5,
info
)

Related

How to convert asset-library:// to file:// in react-native expo?

I'm using expo to build a cross-platform application. In my app, I have a screen where user can select images or videos to upload.
When I use expo-image-picker to select image it gives me an object which its uri starts with file:/// and I can use this uri to display the image.
When I use expo-image-picker-multiple to select multiple images it gives me objects and the uri starts with asset-library:// and I can't use this uri to display the content of it nor send it to server.
How can I convert this asset-library:// to file://? What keyword should I use to get better results when doing google search on this problem or which tool should I use? I can't really find a proper solution to this one. This occurs on IOS devices.
Thanks!
[EDIT]
here is my code
var assetUri = 'asset-library://....'
var tempDir = `${FileSystem.cacheDirectory}${Math.random().toString(36).substring(7)}.jpg`
FileSystem.copyAsync({
from: assetUri,
to: tempDir
})
try {
var assetResult = await FileSystem.readAsStringAsync(tempDir, {
encoding: FileSystem.EncodingType.UTF8
})
console.log(assetResult)
}
catch(e) {
console.log(e)
}
File 'file:///var/mobile/Containers/Data/Application/0E-FC42-4630-B3C7-537D5EFB7D1F/Library/Caches/ExponentExperienceData/ichardexpohong/gwmke.jpg' could not be read.
I wanted only the filename so i used
let filename= result.assets[0].uri.split('/')[result.assets[0].uri.split('/').length-1]
so maybe if the path after both asset-library and file:// are the same you can use
take away the asset-library:// then replace it with file://
let uri='file://' + object.uri.split('asset-library://')[1]
hope it helps

Adobe PDF Embed API Save Content To Base64

Using Adobe PDF Embed API, you can register a callback:
this.adobeDCView = new window.AdobeDC.View(config);
this.adobeDCView.registerCallback(
window.AdobeDC.View.Enum.CallbackType.SAVE_API, (metaData, content, options) => {
})
Content is according to the docs here: https://www.adobe.io/apis/documentcloud/dcsdk/docs.html?view=view
content: The ArrayBuffer of file content
When I debug this content using chrome inspector, it shows me that content is a Int8Array.
Normally when we upload a pdf file, the user selects a file and we read as dataURI and get base64 and push that to AWS. So I need to convert this PDF's data (Int8Array) to Base64, so I can also push it to AWS.
Everything I have found online uses UInt8Array to base64, and I don't understand how to go from Int8Array to UInt8Array. I would think you can just add 128 to the signed int to get a ratio between 0-256, but this doesn't seem to work.
I have tried using this:
let decoder = new TextDecoder('utf8');
let b64 = btoa(decoder.decode(content));
console.log(b64);
But I get this error:
ERROR DOMException: Failed to execute 'btoa' on 'Window': The string to be encoded contains characters outside of the Latin1 range.
Please help me figure out how to go from Int8Array to Base64.
I use the function in this answer.
For Embed API, use the "content" parameter from the save callback as the input to the function.
You can see a working example at this CodePen. The functional part is below.
adobeDCView.registerCallback(
AdobeDC.View.Enum.CallbackType.SAVE_API,
function (metaData, content, options) {
/* Add your custom save implementation here...and based on that resolve or reject response in given format */
var base64PDF = arrayBufferToBase64(content);
var fileURL = "data:application/pdf;base64," + base64PDF;
$("#submitButton").attr("href", fileURL);
/* End save code */
return new Promise((resolve, reject) => {
resolve({
code: AdobeDC.View.Enum.ApiResponseCode.SUCCESS,
data: {
/* Updated file metadata after successful save operation */
metaData: { fileName: urlToPDF.split("/").slice(-1)[0] }
}
});
});
},
saveOptions
);

VueJS and Image CDNs: How to check when image is done being optimized through the Image CDN?

I might be over thinking this, but I got a an avatarprofile component that lets a user update their avatar image. I am using Google Cloud storage as the image hosting and then I am using an image CDN (imagekit) to handle the image optimization/caching. All works good...however, I do have a minor annoyance that I was wondering if someone could help me with:
First, here's my code (will help explain what I am having issue with):
async avatarChangeHandler(e) {
this.overlayShow = true //<-- show a loading indicator
try {
if (e.target.files.length) {
this.image = await new Promise((resolve) => {
const reader = new FileReader()
reader.onload = (e) => {
resolve(e.target.result)
}
reader.readAsDataURL(e.target.files[0])
})
await this.photoUpload() //<-- upload image to Google storage and return new image URL
await this.updateAvatar() //<-- update user's profile in the database with new URL
await this.$store.dispatch('getUserProfile', this.currentUser) //<-- grab updated profile
console.log('updating...done')
this.overlayShow = false //<-- terminate the loading indicator
The problem is that the user profile is updated with the new image URL and fetched faster than the image CDN...and so that means the overlayShow is already set to false and still shows the old image for a second or so (depending on level of optimization needed).
What can I do to make sure the overlayShow is not set to false until the image CDN is done with the new image? Thanks and I realize this may not be feasible, but looking for advice or suggestions on approach. Thanks!

Image caching in Github?

Is there any caching policy of images in GitHub?
I am facing trouble while rendering an image from an API. The image works fine when I open it in the browser.
However, Github renders some older version of that image.
How can I workaround this issue or is there any fix?
Considering this netlify/netlify-cms PR, maybe adding the ts=$(date) as parameter of a GitHub API v3 call would be enough of a "cache busting" for your need.
From the code:
const cacheBuster = new Date().getTime();
const params = [`ts=${cacheBuster}`];
if (options.params) {
for (const key in options.params) {
params.push(`${ key }=${ encodeURIComponent(options.params[key]) }`);
}
}
if (params.length) {
path += `?${ params.join("&") }`;
}
return this.api_root + path;
This adds a &ts=xxx timestamp parameter to the api.github.com call.

React Native Speed up converting image uri to base64

I'm working on a react native iOS app where I want to take certain images from a user's Camera Roll and save them in cloud storage (right now I'm using Firebase).
I'm currently getting the images off the Camera Roll and in order to save each image to the cloud I'm converting each image uri to base64 and then to a blob using the react-native-fetch-blob library. While this is working I am finding the conversion process to base64 for each image to be taking a very long time.
An example image from the Camera Roll:
What would be the most efficient/quickest way to take the image uri for each image from the Camera Roll, convert it, and store it to cloud storage.
Is there a better way I can be handling this? Would using Web Workers speed up the base64 conversion process?
My current image conversion process:
import RNFetchBlob from 'react-native-fetch-blob';
const Blob = RNFetchBlob.polyfill.Blob;
const fs = RNFetchBlob.fs
window.XMLHttpRequest = RNFetchBlob.polyfill.XMLHttpRequest
window.Blob = Blob
function saveImages(images) {
let blobs = await Promise.all(images.map(async asset => {
let response = await convertImageToBlob(asset.node.image.uri);
return response;
}));
// I will then send the array of blobs to Firebase storage
}
function convertImageToBlob(uri, mime = 'image/jpg') {
const uploadUri = uri.replace('file://', '');
return new Promise(async (resolve, reject) => {
let data = await readStream(uploadUri);
let blob = await Blob.build(data, { type: `${mime};BASE64` });
resolve(blob);
})
}
function readStream(uri) {
return new Promise(async (resolve, reject) => {
let response = await fs.readFile(uri, 'base64');
resolve(response);
})
}
I found the solution below to be extremely helpful in speeding up the process. The base64 conversion now takes place on the native side rather than through JS.
React Native: Creating a custom module to upload camera roll images.
It's also worth noting this will convert the image to thumbnail resolution.
To convert an image to full resolution follow guillaumepiot's solution here:
https://github.com/scottdixon/react-native-upload-from-camera-roll/issues/1
I would follow the example here form the react-native-fetch docs. It looks like you're trying to add an extra step when they take care of that for you.
https://github.com/wkh237/react-native-fetch-blob#upload-a-file-from-storage