Properly Reading Firebase data, using it, and Writing using React Native - firebase
I'm trying to utilize my Google firebase to hold data such a equipment details, work details, and work orders. The idea I had was to hold it all on firebase firestore. In react-native, I wanted to be able to use a separate function file to be able to access the data on firebase, then process it, and then write new information on the firestore. I'm having a bit of trouble temporarily saving the data in an array using the async function with push, and being able to regularly use that information for a variety of functions. The code below just results with too many promises exceptions. Is there a better way to write a script? Or, do you have a way to be able to simplify this?
import firestore from '#react-native-firebase/firestore';
class Worker {
constructor(name, certifications, shift, location) {
this.name = name;
this.certifications = certifications;
this.shift = shift;
this.location = location;
this.currentPosition = location;
this.timeLeftinShift = 8;
this.schedule = [];
this.doneScheduling = false;
}
}
class WorkOrder {
constructor(
eID,
eType,
facility,
location,
priority,
timeStamp,
timeToComplete,
) {
this.eID = eID;
this.eType = eType;
this.facility = facility;
this.location = location;
this.priority = priority;
this.timeStamp = timeStamp;
this.timeToComplete = timeToComplete;
this.done = false;
}
}
class Facility {
constructor(name, location, maxOcc) {
this.name = name;
this.location = location;
this.maxOcc = maxOcc;
this.curOcc = 0;
}
}
//assigns a worker
export function assignWorker() {
var tempWorkers = [];
async function workers() {
const workersCollection = await firestore()
.collection('workers')
.get()
.then(async (querySnapshot) => {
querySnapshot.forEach(async (documentSnapshot) => {
let temp = new Worker(
documentSnapshot.id,
documentSnapshot.data().Certifications,
documentSnapshot.data().Shift,
[
documentSnapshot.data().Location.longitude,
documentSnapshot.data().Location.latitude,
],
);
tempWorkers.push(temp);
});
});
return tempWorkers;
}
var tempFacilities = [];
async function Facilities() {
var tempFacilities = [];
const facilitiesCollection = await firestore()
.collection('facility')
.get()
.then(async (querySnapshot) => {
querySnapshot.forEach(async (documentSnapshot) => {
let temp = new Facility(
documentSnapshot.id,
[
documentSnapshot.data().Location.longitude,
documentSnapshot.data().Location.latitude,
],
documentSnapshot.data()['Max Occupancy'],
);
tempFacilities.push(temp);
});
});
return tempFacilities;
}
var tempWorkOrders = [];
async function workOrders() {
var tempWorkOrders = [];
const workOrdersCollection = await firestore()
.collection('sample work order')
.get()
.then(async (querySnapshot) => {
querySnapshot.forEach(async (documentSnapshot) => {
let temp = new WorkOrder(
documentSnapshot.data()['Equipment ID'],
documentSnapshot.data()['Equipment Type'],
documentSnapshot.data().Facility,
getFCoord(documentSnapshot.data().Facility, tempFacilities),
documentSnapshot.data()['Priority(1-5)'],
documentSnapshot.data()['Submission Timestamp'],
documentSnapshot.data()['Time to Complete'],
);
tempWorkOrders.push(temp);
});
});
return tempWorkOrders;
}
// final part
console.log('Reached Assigned Schedule');
workOrders().then((y) => {
workers().then((z) => {
assignSchedule(z, y);
for (let i = 0; i < z.length; ++i) {
firestore()
.collection('schedules')
.doc(tempWorkers[i].name)
.set({
schedule: tempWorkers[i].schedule,
})
.then(() => {});
}
});
});
console.log('Reached Assigned Schedule');
function getFCoord(inFacility) {
Facilities().then((x) => {
for (let i = 0; i < x.length; ++i) {
if (inFacility === x[i].name) {
// //console.log(
// // 'fCoord lat: ',
// // x[i].location[0],
// // ', fCoord long: ',
// x[i].location[1],
// );
return [x[i].location[0], x[i].location[1]];
}
}
});
}
// if a task is completed, we remove the work order
// if a task isn't completed by shift end, we subtract the time spent on the task and change the work order to reflect that
// two points (long, lat) distance function
function distBetweenTwoGeoPoints(lat, long, lat2, long2) {
const earthRadius = 6371;
let deltaPhi = ((lat2 - lat) * Math.PI) / 180;
let deltaLambda = ((long2 - long) * Math.PI) / 180;
let phi1 = (lat * Math.PI) / 180;
let phi2 = (lat2 * Math.PI) / 180;
let a =
Math.sin(deltaPhi / 2) ** 2 +
Math.cos(phi1) * Math.cos(phi2) * Math.sin(deltaLambda / 2) ** 2;
let c = 2 * Math.atan(Math.sqrt(a), Math.sqrt(1 - a));
return earthRadius * c;
}
// initial schedule for the day will come from all work orders that haven't been completed before their shift
//based upon technician certification we'd filter the work orders applicable
// letiables for optimization
// probability of failure
// location distance
// priority
// function -> ()
//filter each task by worker which can complete them
//for each worker, filter tasks that they can complete
// urgency score will be function of priority and time since task has been requested
// real time will be time takes to complete + travel time
// multiplier will be a function of how long a task will take and how much time is left in that person's shift
function score(workorder) {
//return workorder.priority * timeSinceRequest(workorder.timeStamp);
return workorder.priority;
}
function listOfTasksPerWorker() {
workOrders().then((Workers) => {
workers().then((workorders) => {
let numWorkers = Workers.length;
let numTasks = workorders.length;
let possibleTasks = [];
for (let i = 0; i < numWorkers; ++i) {
let taskList = [];
for (let j = 0; j < numTasks; ++j) {
//console.log("equipment " + workorders[j].eType);
for (let k = 0; k < workers[i].certifications.length; ++k) {
//console.log("certif " + workers[i].certifications[k]);
if (workorders[j].eType === workers[i].certifications[k]) {
//check certifications
taskList.push(workorders[j]);
}
}
}
possibleTasks.push(taskList);
}
return possibleTasks;
});
});
}
//given task will be apart of taskList
function removeTask(task, taskList) {
let newArr = [];
for (let i = 0; i < taskList.length; ++i) {
if (task.eID === taskList[i].eID) {
newArr = taskList.slice(i, i + 1);
return newArr;
}
}
}
// while workers have shift
// go through workorders
// terminates either when all the workers shifts are full or when there are no work orders
function assignSchedule() {
workOrders().then((Workers) => {
workers().then((workorders) => {
let numDone = 0;
while (numDone < Workers.length && workorders.length > 0) {
console.log('before choose workers');
chooseWorkers(Workers, workorders);
console.log('pass choose workers');
for (let i = 0; i < Workers.length; ++i) {
if (Workers[i].timeLeftinShift <= 0) {
++numDone;
}
}
}
});
});
}
//change time left in workshift
//change location of worker
//say task is assigned, and remove from workorder IF COMPLETED
function assignTask(worker, workorder) {
workOrders().then((workorders) => {
let taskTime =
workorder.timeToComplete +
timeToGetFacility(worker.location, workorder.location);
if (worker.timeLeftinShift < taskTime) {
worker.timeLeftinShift = 0;
workorder.timeToComplete =
worker.timeToComplete - worker.timeLeftinShift;
} else {
worker.timeLeftinShift = worker.timeLeftinShift - taskTime;
workorder.timeToComplete = 0;
workorders = removeTask(workorder, workorders);
}
worker.location = workorder.location;
worker.schedule.push(workorder);
console.log('assign task');
});
}
function chooseWorkers() {
workOrders().then((Workers) => {
workers().then((workorders) => {
for (let i = 0; i < Workers.length; ++i) {
let keep = true;
let bestTaskw1;
let bestTaskw2;
let worker1Tasks = listOfTasksPerWorker(Workers, workorders)[i];
console.log('tasks: ', worker1Tasks);
let schedule = oneSchedule(Workers[i], worker1Tasks);
if (!schedule[0].length) {
break;
} else {
bestTaskw1 = schedule[0][0];
}
console.log('pass schedule');
if (!Workers[i].doneScheduling) {
for (let j = i + 1; j < Workers.length; ++j) {
if (!Workers[j].doneScheduling) {
let worker2Tasks = listOfTasksPerWorker(Workers, workorders)[j];
let schedule2 = oneSchedule(Workers[j], worker2Tasks);
if (!schedule2[0].length) {
break;
} else {
bestTaskw2 = schedule[0][0];
if (bestTaskw1.eID === bestTaskw2.eID) {
let w1SecondSchedule = oneSchedule(
Workers[i],
removeTask(bestTaskw1, worker1Tasks),
);
let w2SecondSchedule = oneSchedule(
Workers[j],
removeTask(bestTaskw1, worker1Tasks),
);
if (
bestTaskw1[1] - w1SecondSchedule[1] <
bestTaskw2[1] - w2SecondSchedule[1]
) {
keep = false;
i = i - 1;
//fix next line
worker1Tasks = removeTask(bestTaskw1, worker1Tasks);
break;
}
}
}
}
}
if (keep) {
assignTask(Workers[i], bestTaskw1, workorders);
}
}
}
});
});
}
function timeToGetFacility(point1, point2) {
const avgSpdKm = 70;
let timeToGetThere =
distBetweenTwoGeoPoints(point1[0], point1[1], point2[0], point2[1]) /
avgSpdKm;
return timeToGetThere;
}
function oneSchedule(worker1, worker1Tasks) {
let time1 = worker1.timeLeftinShift;
let total1 = 0;
let tasks1 = [];
let tempLocation = worker1.location;
while (time1 > 0) {
let bestOrder;
let bestOrderInd;
let bestScorePerHour = 0;
let travelTime = 0;
let bestTravelTime = 0;
console.log('before for');
console.log(worker1Tasks.length.toString());
for (let i = 0; i < worker1Tasks.length; ++i) {
console.log('before if');
console.log('test: ', i, worker1Tasks.length);
if (!worker1Tasks[i].done) {
console.log('after if');
let tempScorePerHour = 0;
console.log('location: ', tempLocation[0], tempLocation[1]);
travelTime = timeToGetFacility(
tempLocation,
worker1Tasks[i].location,
);
console.log('travel time: ', travelTime);
if (time1 - (worker1Tasks[i].timeToComplete + travelTime) < 0) {
tempScorePerHour =
(score(worker1Tasks[i]) *
((time1 - travelTime) / worker1Tasks[i].timeToComplete)) /
time1;
} else {
tempScorePerHour =
score(worker1Tasks[i]) /
(worker1Tasks[i].timeToComplete + travelTime);
}
console.log('score: ', score(worker1Tasks[i]));
console.log('temp: ', tempScorePerHour);
if (tempScorePerHour > bestScorePerHour) {
bestScorePerHour = tempScorePerHour;
bestOrder = worker1Tasks[i];
bestOrderInd = i;
bestTravelTime = travelTime;
}
}
}
if (bestScorePerHour > 0) {
worker1Tasks.splice(bestOrderInd, 1);
//bestOrder.done = true;
tasks1.push(bestOrder);
console.log(bestOrder.eID);
console.log(bestScorePerHour);
if (time1 - (bestOrder.timeToComplete + bestTravelTime) < 0) {
total1 += bestScorePerHour * time1;
time1 = 0;
} else {
total1 += score(bestOrder);
time1 -= bestOrder.timeToComplete + bestTravelTime;
}
console.log(bestOrder.timeToComplete);
tempLocation = bestOrder.location;
} else {
break;
}
}
return [tasks1, total1];
}
}
Related
How to get the entire path in Next.js 13 in custom loader function, for server components only?
I have a loader function called getBlogData which is like this: import { getFromCacheOrApi } from 'Base' const getBlogData = async () => { const { pathname } = { pathname: "" } var url = '/blog/data?' let matches = /\/blog(\/\d+)?\/?$/.exec(pathname) if (matches != null) { const pageNumber = matches[1] if (pageNumber !== undefined) { url += `&pageNumber=${pageNumber.replace('/', '')}` } } else { const secondSegments = ['category', 'tag', 'author', 'search'] if (pathname.split('/').length >= 2 && !secondSegments.includes(pathname.split('/')[2])) { response.status = 404 return } for (let i = 0; i < secondSegments.length; i++) { const segment = secondSegments[i] if (pathname.startsWith(`/blog/${segment}`)) { matches = new RegExp(`(?<=\\/blog\\/${segment}\\/)[^/]+\\/?(\\d+)?\\/?$`).exec(pathname) if (matches == null) { response.status = 404 return } else { url += `&${segment}=${encodeURI(matches[0].split('/')[0])}` const pageNumber = matches[1] if (pageNumber !== undefined) { url += `&pageNumber=${pageNumber}` } break } } } } url = url.replace('?&', '?') const data = await getFromCacheOrApi(url) // console.log(params, response.status) // if (pageNumber && isNaN(pageNumber)) { // console.log(pageNumber, isNaN(pageNumber)) // response.status = 400 // return // } const { seoParameters } = data return data } export default getBlogData This function is only used in my page which is inside app directory in Next 13, which means that it's a server component, and I don't want to change it to a client component. However, I need to access request data, in this particular case, the path of the URL. How can I get that?
wait on async fuction +
I want to get the last deviceId. Please try the following code on a smartphone. https://www.ofima.ch/file1.html Inside the function "getConnectedDevices" the variable deviceId ok. But outside is returned a promise and not the variable deviceId. How can I get the variable deviceId ? Thanks Miche
Explanation: You need to wrap your alert in an async function and use await. Also to take the value from the promise you needed .then(). Hope the below helps. Original code: async function getConnectedDevices() { var index; const devices = await navigator.mediaDevices.enumerateDevices(); for (var i=0; i<devices.length; i++) { if (devices[i].kind == "videoinput") { index = i; } } var deviceId = devices[index].deviceId; alert('deviceId is ok: ' + deviceId); return (deviceId); } const deviceId = getConnectedDevices(); alert('deviceId is not defined, why ?: ' + deviceId); New code: async function getConnectedDevices() { let index; const devices = await navigator.mediaDevices.enumerateDevices(); for (let i=0; i < devices.length; i++) { console.debug(devices[i]); if (devices[i].kind == "videoinput") { index = i; } } console.log('deviceId is ok: ', devices[index]); return devices[index].deviceId; } (async() => { const deviceId = await getConnectedDevices().then(); alert(`deviceId: ${deviceId}`); })(); And a quick hack for storing the deviceId in the window console.log('globalDeviceId should be undefined', window.globalDeviceObj); async function getConnectedDevices() { let index; const devices = await navigator.mediaDevices.enumerateDevices(); for (let i = 0; i < devices.length; i++) { console.debug(devices[i]); if (devices[i].kind == "videoinput") { index = i; } } console.log('deviceId is ok', devices[index]); return devices[index]; } function getDeviceId() { (async() => { window.globalDeviceObj = await getConnectedDevices().then(); console.log(`globalDeviceId set: ${JSON.stringify(window.globalDeviceObj)}`); })(); } function tick() { if(typeof window.globalDeviceObj === 'undefined'){ requestAnimationFrame(tick); }else { alert(`globalDeviceId get: ${JSON.stringify(window.globalDeviceObj)}, with deviceId: ${(window.globalDeviceObj.deviceId)}`) } } function init() { tick(); getDeviceId(); } init();
Multi document creation in sub-collection in Firestore
I am trying to write a function that will: Create documents in a sub collection Allow for a then/catch call back after all sub documents have been created export const doCreateSubs = (Id, count) => { if (count > 0 && count <= 50){ const times = n => f => { let iter = i => { if (i === n) return; f(i); iter(i + 1); }; return iter(0); }; times(count)(i => { db .collection("parent") .doc(`${Id}`) .collection("sub") .add({ subName: `name ${i + 1}`, dateCreated: new Date() }); }); } } I've played around with batch but it doesn't work with .collection. I know my function is really poor - is there a generally bettery way of doing this?
So i've just realised you can .doc() with no value and it will create a uid for the key. I can also return .commit and recieve a call back when it's complete! export const doCreateSubs = (Id, count) => { if (count > 0 && count <= 50){ const times = n => f => { let iter = i => { if (i === n) return; f(i); iter(i + 1); }; return iter(0); }; const batch = db.batch(); times(count)(i => { const ref = db .collection("parent") .doc(`${Id}`) .collection("subs") .doc(); batch.set(ref, { boxName: `Sub ${i + 1}`, dateCreated: new Date() }); }); return batch.commit(); } }
Firebase Cloud Function To Sum up or aggregate values
I have been trying to sum up the price to bring up a total through the new firebase cloud functions "Cart" : { "-Ksdasd50oR04q073k5" : { "ID" : 0.01, "price" : 20 }, "-KhOc0CGdsddihGB2N" : { "ID" : 001,, "price" : 20 }, This is my start , exports.cartadd = functions.database.ref('Cart/{uid}').onWrite(event => { let ref = admin.database().ref('Cart/{uid}'); return ref.once('value').then(snapshot => { if (snapshot.hasChildren()) { var total= 0; snapshot.forEach(function(child) { total += snapshot.val().price; }); console.log(total); } });
This should do it: exports.cartadd = functions.database.ref('Cart').onWrite(event => { const snapshot = event.data; if (snapshot.hasChildren()) { var total= 0; snapshot.forEach(function(item) { total += item.child('price').val(); }); console.log(total); } });
Try this: exports.cartadd = functions.database.ref('Cart').onWrite(event => { const snapshot = event.data; const totalRef = admin.database().ref("Cart/total"); if (snapshot.hasChildren()) { let total= 0; snapshot.forEach(function(item) { total += item.child('price').val(); }); const transaction = totalRef.set(total).then(()=>{console.log(total);}); return Promise.all([transaction]); } });
Since the total needs to change, it should be a var not const. Change const total = 0; to var total = 0;
total += child.val().price; // instead
How to avoid blockin while uploading file using Meteor method
I've created a Meteor method to upload a file, it's working well but until the file is fully uploaded, I cannot move around, all subscriptions seem to wait that the upload finishes... is there a way to avoid that ? Here is the code on the server : Meteor.publish('product-photo', function (productId) { return Meteor.photos.find({productId: productId}, {limit: 1}); }); Meteor.methods({ /** * Creates an photo * #param obj * #return {*} */ createPhoto: function (obj) { check(obj, Object); // Filter attributes obj = filter(obj, [ 'name', 'productId', 'size', 'type', 'url' ]); // Check user if (!this.userId) { throw new Meteor.Error('not-connected'); } // Check file name if (typeof obj.name !== 'string' || obj.name.length > 255) { throw new Meteor.Error('invalid-file-name'); } // Check file type if (typeof obj.type !== 'string' || [ 'image/gif', 'image/jpg', 'image/jpeg', 'image/png' ].indexOf(obj.type) === -1) { throw new Meteor.Error('invalid-file-type'); } // Check file url if (typeof obj.url !== 'string' || obj.url.length < 1) { throw new Meteor.Error('invalid-file-url'); } // Check file size if (typeof obj.size !== 'number' || obj.size <= 0) { throw new Meteor.Error('invalid-file-size'); } // Check file max size if (obj.size > 1024 * 1024) { throw new Meteor.Error('file-too-large'); } // Check if product exists if (!obj.productId || Meteor.products.find({_id: obj.productId}).count() !== 1) { throw new Meteor.Error('product-not-found'); } // Limit the number of photos per user if (Meteor.photos.find({productId: obj.productId}).count() >= 3) { throw new Meteor.Error('max-photos-reached'); } // Resize the photo if the data is in base64 if (typeof obj.url === 'string' && obj.url.indexOf('data:') === 0) { obj.url = resizeImage(obj.url, 400, 400); obj.size = obj.url.length; obj.type = 'image/png'; } // Add info obj.createdAt = new Date(); obj.userId = this.userId; return Meteor.photos.insert(obj); } }); And the code on the client : Template.product.events({ 'change [name=photo]': function (ev) { var self = this; readFilesAsDataURL(ev, function (event, file) { var photo = { name: file.name, productId: self._id, size: file.size, type: file.type, url: event.target.result }; Session.set('uploadingPhoto', true); // Save the file Meteor.call('createPhoto', photo, function (err, photoId) { Session.set('uploadingPhoto', false); if (err) { displayError(err); } else { notify(i18n("Transfert terminé pour {{name}}", photo)); } }); }); } });
I finally found the solution myself. Explication : the code I used was blocking the subscriptions because it was using only one method call to transfer all the file from the first byte to the last one, that leads to block the thread (I think, the one reserved to each users on the server) until the transfer is complete. Solution : I splitted the file into chunks of about 8KB, and send chunk by chunk, this way the thread or whatever was blocking the subscriptions is free after each chunk transfer. The final working solution is on that post : How to write a file from an ArrayBuffer in JS Client Code // data comes from file.readAsArrayBuffer(); var total = data.byteLength; var offset = 0; var upload = function() { var length = 4096; // chunk size // adjust the last chunk size if (offset + length > total) { length = total - offset; } // I am using Uint8Array to create the chunk // because it can be passed to the Meteor.method natively var chunk = new Uint8Array(data, offset, length); if (offset < total) { // Send the chunk to the server and tell it what file to append to Meteor.call('uploadFileData', fileId, chunk, function (err, length) { if (!err) { offset += length; upload(); } } } }; upload(); Server code var fs = Npm.require('fs'); var Future = Npm.require('fibers/future'); Meteor.methods({ uploadFileData: function(fileId, chunk) { var fut = new Future(); var path = '/uploads/' + fileId; // I tried that with no success chunk = String.fromCharCode.apply(null, chunk); // how to write the chunk that is an Uint8Array to the disk ? fs.appendFile(path, new Buffer(chunk), function (err) { if (err) { fut.throw(err); } else { fut.return(chunk.length); } }); return fut.wait(); } });
Improving #Karl's code: Client This function breaks the file into chunks and sends them to the server one by one. function uploadFile(file) { const reader = new FileReader(); let _offset = 0; let _total = file.size; return new Promise((resolve, reject) => { function readChunk() { var length = 10 * 1024; // chunk size // adjust the last chunk size if (_offset + length > _total) { length = _total - _offset; } if (_offset < _total) { const slice = file.slice(_offset, _offset + length); reader.readAsArrayBuffer(slice); } else { // EOF setProgress(100); resolve(true); } } reader.onload = function readerOnload() { let buffer = new Uint8Array(reader.result) // convert to binary Meteor.call('fileUpload', file.name, buffer, _offset, (error, length) => { if (error) { console.log('Oops, unable to import!'); return false; } else { _offset += length; readChunk(); } } ); }; reader.onloadend = function readerOnloadend() { setProgress(100 * _offset / _total); }; readChunk(); }); } Server The server then writes to a file when offset is zero, or appends to its end otherwise, returning a promise, as I used an asynchronous function to write/append in order to avoid blocking the client. if (Meteor.isServer) { var fs = require('fs'); var Future = require('fibers/future'); } Meteor.methods({ // Upload file from client to server fileUpload( fileName: string, fileData: Uint8Array, offset: number) { check(fileName, String); check(fileData, Uint8Array); check(offset, Number); console.log(`[x] Received file ${fileName} data length: ${fileData.length}`); if (Meteor.isServer) { const fut = new Future(); const filePath = '/tmp/' + fileName; const buffer = new Buffer(fileData); const jot = offset === 0 ? fs.writeFile : fs.appendFile; jot(filePath, buffer, 'binary', (err) => { if (err) { fut.throw(err); } else { fut.return(buffer.length); } }); return fut.wait(); } } )}; Usage uploadFile(file) .then(() => { /* do your stuff */ });