The background is that I allow user drags multiple files into Dropzone. I need to check each file type. If one of them is no allowed, set message and get out early.
Please see code below
Starts at for (let i = 0; i < acceptedFiles.length; i++) {
In side this for loop, I call reader.onloadend, which is a callback.
How do I run callback inside for-loop?
// Keep it internal
const getMimetype = signature => {
switch (signature) {
case '89504E47':
return 'image/png';
case '47494638':
return 'image/gif';
case '25504446':
return 'application/pdf';
case 'FFD8FFDB':
case 'FFD8FFE0':
return 'image/jpeg';
case '504B0304':
return 'application/zip';
default:
return 'Unknown filetype';
}
};
const onDropAccepted = useCallback(acceptedFiles => {
// reset to default state
resetToDefaultState();
//test
console.log('acceptedFiles', acceptedFiles);
// reader
const reader = new FileReader();
let file;
// Multi
if (config.isMultipleFiles === true) {
// Loop all files and check file types
for (let i = 0; i < acceptedFiles.length; i++) {
file = acceptedFiles[i];
// get 1st 4 byptes
const blob = file.slice(0, 4);
reader.readAsArrayBuffer(blob);
reader.onloadend = evt => {
if (evt.target.readyState === FileReader.DONE) {
const uint = new Uint8Array(evt.target.result);
let bytes = [];
uint.forEach(byte => {
bytes.push(byte.toString(16));
});
const hex = bytes.join('').toUpperCase();
const type = getMimetype(hex);
// type is allowed
if (config.fileTypes.includes(type)) {
setFiles([...files, ...acceptedFiles]);
} else {
// type no good
setIsInvaildFileType(true);
}
}
};
}
} else {
// drop 1 file
if (acceptedFiles.length <= 1) {
// bucket no file
if (files.length === 0) {
file = acceptedFiles[0];
// 1st 4 bytes
const blob = file.slice(0, 4);
// read 4 bytes
reader.readAsArrayBuffer(blob);
// later
reader.onloadend = evt => {
if (evt.target.readyState === FileReader.DONE) {
// event res to unit
const uint = new Uint8Array(evt.target.result);
// byte
let bytes = [];
// loop each unit
uint.forEach(byte => {
bytes.push(byte.toString(16));
});
// hex
const hex = bytes.join('').toUpperCase();
const type = getMimetype(hex);
//test
console.log('hex', hex);
console.log('output', type);
// type is allowed
if (config.fileTypes.includes(type)) {
setFiles([...files, ...acceptedFiles]);
} else {
// type no good
setIsInvaildFileType(true);
}
}
};
} else {
// bucket has file already
setIsMaxFileNum(true);
}
} else {
// drop multiple files, no thinking of bucket
setIsMaxFileNum(true);
}
}
});
I've also had to validate per file using react-dropzone in a similar way.
My workaround was to promisify FileReader.
1️⃣ This is the promisified version of "FileReader"
const isValidFile = file => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = evt => {
// other logic removed for brevity...
2️⃣ Your custom logic dictates, if the file is valid or not
if (config.fileTypes.includes(type)) {
resolve(true);
} else {
resolve(false);
}
};
3️⃣ Should there was an error, this file is not good.
reader.onerror = () => resolve(false)
4️⃣ Start the reading process.
const blob = file.slice(0, 4);
reader.readAsArrayBuffer(blob);
});
};
Now you can use that within the for loop you mentioned.
const onDropAccepted = useCallback(acceptedFiles => {
// reset to default state
resetToDefaultState();
1️⃣ As `useCallback` accepts a non-async method,
Create a wrapped async method we can call here.
const processFiles = async () => {
if (config.isMultipleFiles === true) {
for (let i = 0; i < acceptedFiles.length; i++) {
const file = acceptedFiles[i];
2️⃣ Here is where we validate the file using the code above.
const isValid = await isValidFile(file);
if (!isValid) {
setIsInvaildFileType(true);
return;
}
}
3️⃣ At this point, all files are good.
setFiles([...files, ...acceptedFiles]);
} else {
// removed for brevity...
}
};
4️⃣ Let's fire up the process
processFiles();
});
Related
I can successfully upload multiple files to firebase storage the issue is when I try to upload only 1 file nothing happened.
When I have multiple files they are added to an array then I can upload it to firebase database but how to do it with only 1 file.
//choose files
document.querySelector('#pictures').addEventListener('change', (e) => {
const formData = extractFormData('#statusForm');
while (fileCollection.length) {
fileCollection.pop();
console.log("test");
}
[].slice.call(formData.pictures).map(f => fileCollection.push(f));
console.log(formData.pictures);
});
//choose files
document.forms.statusForm.addEventListener('submit', (e) => {
e.preventDefault();
e.stopPropagation();
const formData = extractFormData('#statusForm');
const text = formData.status;
formData.status = '';
sendData(text, fileCollection)
setTimeout(() => {
while (fileCollection.length) {
fileCollection.pop();
}
}, 100);
});
var extractFormData = function (form) {
const formData = new FormData(document.querySelector(form));
values = {};
for(var pair of formData.entries()) {
if( values[pair[0]] ) {
if(!(values[pair[0]] instanceof Array)) {
values[pair[0]] = new Array(values[pair[0]]);
}
values[pair[0]].push(pair[1]);
} else {
values[pair[0]] = pair[1];
}
}
console.log("values: " + values);
return values;
}
This is how it was fixed i changed the extractFormData function to
const extractFormData = function (form) {
const formData = new FormData(document.querySelector(form));
const values = {};
for (let [key, value] of formData.entries()) {
if (values[key]) {
if ( ! (values[key] instanceof Array) ) {
values[key] = new Array(values[key]);
}
values[key].push(value);
} else {
values[key] = value;
if(typeof values[key] === 'undefined') {
// does not exist
console.log("doesnt exist")
}
else {
// does exist
values[key] = new Array(values[key]);
}
}
}
return values;
}
I am unable to download image in expo application(React Native for Android) after building my app file, but I am able to download image while debugging the application using expo client
import * as Sharing from 'expo-sharing';
import * as FileSystem from 'expo-file-system';
import * as MediaLibrary from 'expo-media-library';
import * as Permissions from "expo-permissions";
const downloadFtn = async () => {
console.log("downloadFtn");
const fileUri: string = `${FileSystem.documentDirectory}test.png`;
const downloadedFile: FileSystem.FileSystemDownloadResult = await FileSystem.downloadAsync("https://i.ibb.co/K5Tyv2C/img-5-1.png", fileUri);
console.log(FileSystem.documentDirectory);
console.log(downloadedFile.status);
if (downloadedFile.status != 200) {
console.log(downloadedFile);
} else {
const perm = await Permissions.askAsync(Permissions.MEDIA_LIBRARY);
if (perm.status != 'granted') {
return;
}
try {
const asset = await MediaLibrary.createAssetAsync(downloadedFile.uri);
const album = await MediaLibrary.getAlbumAsync('Download');
if (album == null) {
await MediaLibrary.createAlbumAsync('Download', asset, false);
} else {
await MediaLibrary.addAssetsToAlbumAsync([asset], album, false);
}
} catch (e) {
handleError(e);
}
}
}
This is my code, it work well
import {shareAsync} from "expo-sharing";
import * as MediaLibrary from 'expo-media-library';
import * as FileSystem from 'expo-file-system';
function getAllUrlParams(url) {
// get query string from url (optional) or window
var queryString = url ? url.split('?')[1] : window.location.search.slice(1);
// we'll store the parameters here
var obj = {};
// if query string exists
if (queryString) {
// stuff after # is not part of query string, so get rid of it
queryString = queryString.split('#')[0];
// split our query string into its component parts
var arr = queryString.split('&');
for (var i = 0; i < arr.length; i++) {
// separate the keys and the values
var a = arr[i].split('=');
// set parameter name and value (use 'true' if empty)
var paramName = a[0];
var paramValue = typeof (a[1]) === 'undefined' ? true : a[1];
// (optional) keep case consistent
paramName = paramName.toLowerCase();
if (typeof paramValue === 'string') paramValue = paramValue.toLowerCase();
// if the paramName ends with square brackets, e.g. colors[] or colors[2]
if (paramName.match(/\[(\d+)?\]$/)) {
// create key if it doesn't exist
var key = paramName.replace(/\[(\d+)?\]/, '');
if (!obj[key]) obj[key] = [];
// if it's an indexed array e.g. colors[2]
if (paramName.match(/\[\d+\]$/)) {
// get the index value and add the entry at the appropriate position
var index = /\[(\d+)\]/.exec(paramName)[1];
obj[key][index] = paramValue;
} else {
// otherwise add the value to the end of the array
obj[key].push(paramValue);
}
} else {
// we're dealing with a string
if (!obj[paramName]) {
// if it doesn't exist, create property
obj[paramName] = paramValue;
} else if (obj[paramName] && typeof obj[paramName] === 'string'){
// if property does exist and it's a string, convert it to an array
obj[paramName] = [obj[paramName]];
obj[paramName].push(paramValue);
} else {
// otherwise add the property
obj[paramName].push(paramValue);
}
}
}
}
return obj;
}
export class fileAPI {
downloadFile = async (url) => {
let params = getAllUrlParams(url);
console.log("params", params);
let file_name = params.name;
console.log("file_name", file_name)
if (file_name !== null) {
FileSystem.downloadAsync(
url,
FileSystem.documentDirectory + file_name
)
.then(async ({uri}) => {
console.log('Finished downloading to ', uri);
MediaLibrary.createAssetAsync(uri).then(asset => {
console.log('asset', asset);
this.share(asset)
MediaLibrary.createAlbumAsync('albumnameyouneedtosave', asset)
.then(() => {
console.log("download complete");
})
.catch(error => {
console.log("issue with download contact support");
});
});
})
.catch(error => {
console.error(error);
});
}
};
share = async (asset) => {
console.log(asset.filename.split("."))
console.log(asset.filename.split(".").length)
await shareAsync(asset.uri, {
UTI: "." + asset.filename.split(".")[asset.filename.split(".").length - 1],
mimeType: "application/" + asset.filename.split(".")[asset.filename.split(".").length - 1]
});
}
}
I have a NodeJS script which is making thousands of REST API GET calls to SharePoint to retrieve all folders and files recursively. A problem I have encountered is once in a while I am receiving ECONNRESET errors and 404 Not Found for some folders with unsupported characters. This causes my code to stop and I lose my progress in retrieving all the files and folders. Is there something I can do to prevent these errors from stopping my code and just have it ignore the error and continue onto the next folder?
I thought catching the error and console logging it would suffice but this still ends the execution of the code. Thanks
//Imports and Declarations
const prompt = require("prompt-sync")({ sigint: true });
const { getCreds, pullFromSharePoint } = require('./helper');
const sprequest = require('sp-request');
var path = require('path');
let creds = getCreds();
let spr = sprequest.create(creds)
let SPUrl = 'url to sharepoint site';
let files = [];
let folders = [];
let scannedFolders = [];
let reiteration = false;
let processingReq = false;
//Prompt user for which folder to copy
let targetFolder = prompt("Folder to copy: ");
//Call function to get list of files and folders in specified folder
// getFolderContents(targetFolder);
processInfo([], targetFolder)
//Gets list of files/folders inside folder
function getFolderContents(targetFolder) {
return new Promise((resolve, reject) => {
logger.info('Scanning this folder: ' + targetFolder)
//Skip if folder name contains chars we dont support
if (targetFolder.includes("'")) {
targetFolder = targetFolder.replace(/'/g, "''")
}
//If reiteration = true, format the target folder
if (reiteration === true) {
targetFolder = targetFolder.replace('/sites/sharepointsite', '');
}
console.log('Scanning this folder: ' + targetFolder)
targetFolder = encodeURIComponent(targetFolder)
var url = `${SPUrl}/_api/web/GetFolderByServerRelativeUrl('Shared%20Documents/${targetFolder}')/Files?$filter=TimeLastModified gt datetime'2021-07-01T00:00:00'`;
var url2 = `${SPUrl}/_api/web/GetFolderByServerRelativeUrl('Shared%20Documents/${targetFolder}')?$expand=Folders`;
//THESE SPR CALLS ARE THE REST API CALLS TO SHAREPOINT RESULTING IN THE MOST 404 ERRORS, HOW TO IGNORE ERRORS FROM THESE?
// Call to get list of files
spr.get(url).then(response => {
console.log('Calling SP API to get files')
//Adds files to array
let fileRes = response.body.d.results;
for (let i = 0; i < fileRes.length; i++) {
files.push(fileRes[i].ServerRelativeUrl)
}
console.log(files.length)
}).catch(function (err) {
console.log('Fetch Error :-S', err);
});
//Call to get list of folders
spr.get(url2).then(response => {
//Adds folders to array
let folderRes = response.body.d.Folders.results;
for (let j = 0; j < folderRes.length; j++) {
folders.push(folderRes[j].ServerRelativeUrl)
}
//Push current folder read through to another array so we dont scan it multiple times
scannedFolders.push('/sites/sharepointsite/Shared Documents' + targetFolder);
resolve(folders);
}).catch(function (err) {
console.log('Fetch Error :-S', err);
});
})
}
//If folders exist in folders array scan them; once all folders have been scanned, send files to be copied
async function processInfo(folders, firstFolder) {
let firstRun = await getFolderContents(firstFolder);
let firstGone = false;
if (firstRun) {
if (firstRun.length > 0) {
for (let k = 0; k < firstRun.length; k++) {
await sleep(500);
//If folder has already been scanned, remove from array and skip to next iteration
if (scannedFolders.includes(firstRun[k])) {
if (k === 0) {
firstRun.splice(k, 1)
}
firstRun.splice(k, k)
continue;
}
//Else if folder has not been scanned, send it to be scanned
else {
if (firstRun[k].includes('%') || firstRun[k].includes('%')) {
console.log('skipping')
continue;
}
reiteration = true;
let foldersScanned = await getFolderContents(firstRun[k]).catch(function (err) {
console.log('Fetch Error :-S', err);
});;
//Send each file to pullFile() function to be downloaded
// if (foldersScanned && k == firstRun.length - 1) {
if (firstRun[k] == 10) {
do {
await pullFile(files[0]);
files.shift();
} while (files.length > 0)
}
}
}
}
}
console.log(files.length)
}
//Manipulate file string to get SP folder and File Name, then call the helper function to download the file
async function pullFile(file) {
let filename = file.replace(/^.*[\\\/]/, '')
let spFolder = file.replace('/sites/sharepointsite/', '')
spFolder = spFolder.replace(filename, '');
let downloadPath = path.join('./testfolder', spFolder)
const sppullContext = {
siteUrl: SPUrl, //SharePoint URL
creds: creds //Credentials
};
const sppullOptions = {
spRootFolder: spFolder, // The folder path for the file in SharePoint
dlRootFolder: downloadPath, // Where the file is saved locally
strictObjects: [filename], // Only download the filename specified as a query parameter
muteConsole: true
};
pullFromSharePoint(sppullContext, sppullOptions) //Pull file with filename from SharePoint
.then(() => {
return true;
}).catch(err => {
return err;
});
}
//Timer to wait specified amount of time
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
Code from helper.js
//Imports and Declarations
var fs = require('fs');
const { SPPull } = require('sppull');
const winston = require('winston');
//Winston logger
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
defaultMeta: { service: 'user-service' },
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'info.log' }),
],
});
/**
* Returns the SharePoint credentials object to use.
*/
function getCreds() {
return {
username: 'UN',
password: 'PW'
};
}
module.exports.getCreds = getCreds;
/**
* Pulls a file from SharePoint and then checks for errors.
*
* #param sppullContext context object for sppull
* #param sppullOptions options object for sppull
*/
async function pullFromSharePoint(sppullContext, sppullOptions) {
SPPull.download(sppullContext, sppullOptions)
.then((res) => {
logger.info(res)
return res
})
.catch((err) => {
logger.error(err)
return err
});
}
I was struggling with a file upload I want to save via a PUT request. I updated the question with the solution.
I have a form that allows for multiple images to be uploaded in different fields, but also other fields with strings:
I loop over all fields in order to collect the information in a JSON, then push them towards my API:
keys is a dict with the names of the keys for the JSON (API checks for those keys), for example: name|STR, price|FLOAT, ppic|IMG.
$('#checkButton').on('click', function( event ) {
let form = document.querySelector('#productForm');
let nbr = document.querySelector('#productdata').getAttribute("pid")
let return_dict = {"number": nbr};
const pendings = ["name", "pic"].map(async function ( key ) {
return_dict["name"] = form.elements[0].value;
return_dict["pic"] = await img_2_b64(form.elements[1]);
});
Promise.all(pendings).then((values) => {
write_2_DB_with_ajax_call ( return_dict )
});
});
If the key identifies the field as IMG I want to convert it to base64 and save it - this is my solution:
function img_2_b64( element ) {
return new Promise((resolve, reject) => {
let file = element.files[0];
let reader = new FileReader();
reader.onloadend = function(e) {
resolve(e.target.result);
};
reader.onerror = function() {
reject();
};
reader.readAsDataURL(file);
});
Maybe you can use promises to have more control over the information flow:
$('#checkButton').on('click', function( event ) {
let form = document.querySelector('#productForm');
let return_dict = {"number": 0, "data": d};
const pendings = keys.map(async function ( key ) {
for ( let fe = 0; fe < form.elements.length; fe++ ) {
if ( form.elements[fe].getAttribute("data-key") === key ) {
if ( key.split("|")[1] === "IMG" ) {
d[key] = await img_2_b64(form.elements[fe]);
} else {
d[key] = form.elements[fe].value;
}
};
};
});
Promise.all(pendings)
.then((values) => {
write_2_DB_with_ajax_call ( values )
});
});
and
function img_2_b64( element ) {
return new Promise((resolve, reject) => {
let fileprops = "";
let file = element.files[0];
let reader = new FileReader();
reader.onloadend = function() {
resolve(reader.readAsDataURL(file));
};
reader.onerror = function() {
reject();
};
})
}
I want to add all images to an array. But when I the following code returns an empty array. However when I console log , the data regarding to the images are shown as like this screenshot. Can anyone give me a solution
This is my code 👇
const readImages = uploader => {
let images = []
const selectedfiles = uploader.files
for (let index = 0; index < selectedfiles.length; index++) {
const fileReader = new FileReader()
fileReader.onload = fileLoadedEvent => {
images.push(fileLoadedEvent.target.result)
}
fileReader.readAsDataURL(selectedfiles[index])
}
return images;
}//End of readImages
this._qs('#uploadImages').addEventListener('input', () => {
const images = readImages(this._qs("#uploadImages"))
console.log(images)
for (let index = 0; index < images.length; index++) {
this._qs('#previewImages').innerHTML += `<img src="${images[index]}" alt="image-${index}"/>`
}
})
The images are pushed to your array onload, meaning that it happens asynchronously. You can't act on the images until they've been returned to the browser. Best way to handle this is to have readImages return a Promise.all(), and have each, individual image load as it's own Promise that you push to the Promise.all() array.
const readImage = (file) =>
new Promise((resolve, reject) => {
const fileReader = new FileReader();
fileReader.onload = ({ target: { result } }) => resolve(result);
fileReader.onerror = () => reject(fileReader.error);
fileReader.readAsDataURL(file);
});
const readImages = ({ files }) => {
if (!files) {
return Promise.reject('No files provided');
}
const imgPromises = [];
files.forEach((file) => imgPromises.push(readImage(file)));
return Promise.all(imgPromises);
};
// The input 'event' target will contain the value of the field
const onInput = ({ target: { value } }) => {
readImages(value).then((imgArray) => {
const preview = document.getElementById('previewImages');
imgArray.forEach((img, index) => {
preview.innerHTML += `<img src="${img}" alt="image_${index}" />`;
});
});
};
document.getElementById('uploadImages').addEventListener('input', onInput);
Note: This is really raw code, to give you an idea on the basics, and doesn't check your final result set for errors. It also uses object destructuring, which won't work in older browsers.