I am creating an Electron Application in which I am recording data from webcam and desktop, at the end of the recording session, I want to save the data to a file in the background. I do not know how to write the data from a blob to a file directly. Any suggestions?
Below is my current handling for MediaRecord Stop event.
this.mediaRecorder.onstop = (e) => {
var blob = new Blob(this.chunks,
{ 'type' : 'video/mp4; codecs=H.264' });
var fs = require('fs');
var fr = new FileReader();
var data = null;
fr.onload = () => {
data = fr.result;
fs.writeFile("test.mp4", data, err => {
if(err) {
return console.log(err);
}
console.log("The file was saved!");
});
};
fr.readAsArrayBuffer(blob);
}
You can do it using FileReader and Buffer.
In the renderer process, send the event to the main process to save the file with the buffer:
function saveBlob(blob) {
let reader = new FileReader()
reader.onload = function() {
if (reader.readyState == 2) {
var buffer = new Buffer(reader.result)
ipcRenderer.send(SAVE_FILE, fileName, buffer)
console.log(`Saving ${JSON.stringify({ fileName, size: blob.size })}`)
}
}
reader.readAsArrayBuffer(blob)
}
Get back the confirmation:
ipcRenderer.on(SAVED_FILE, (event, path) => {
console.log("Saved file " + path)
})
(SAVE_FILE and SAVED_FILE are static strings containing event name)
and in the main process:
ipcMain.on(SAVE_FILE, (event, path, buffer) => {
outputFile(path, buffer, err => {
if (err) {
event.sender.send(ERROR, err.message)
} else {
event.sender.send(SAVED_FILE, path)
}
})
})
outputFile is from 'fs-extra'
Handling node operations in main process is preferred. See Electron Security suggestions.
If you do want to not use main process, you can use 'electron-remote' to create background processes to write the file. Additionally, you can invoke ffmpeg in the background process to compress/encode the file into different format.
Related
I am using the <p-fileUpload> from PrimeNG to upload a json file in my web-app. I want to read the json file, in the front-end, and change some values of a data table.
However, I have no idea how to parse the uploaded file as a json to a typescript object. Any ideas?
In the HTML file:
<p-fileUpload #ratingsUpload mode="basic" name="demo[]"
url="" accept=".json"
styleClass="p-button-raised"
[auto]="true" chooseLabel="Upload ratings"
(onUpload)="onRatingsUpload($event)"></p-fileUpload>
In the typescript file:
onRatingsUpload(event: any) {
console.log(event.files)
// TODO:
// data = JSON(event.files);
}
Edit: I can't get the event to fire. onRatingsUpload does not seem to be called...
You have to use FileReader:
const reader = new FileReader();
reader.onload = (event) => {
try {
var obj = JSON.parse((event.target.result) as string);
console.log('my json:', obj);
} catch (error) {
console.error(error);
}
};
reader.readAsText(file);
//You need to use FileReader
onFileChanged(event) {
this.selectedFile = event.target.files[0];
const fileReader = new FileReader();
fileReader.readAsText(this.selectedFile, "UTF-8");
fileReader.onload = () => {
console.log(JSON.parse(fileReader.result));
}
fileReader.onerror = (error) => {
console.log(error);
}
}
I am trying to upload multiple large size JSON files from React-native to node js.
The files are being uploaded unless the file in larger in size, in which case, it does not upload in one try.
I suspect that:
Since the upload code is in a for loop the code is starting the upload but not waiting for the file to upload and starting to upload the next file
Is there any way to ensure that each file gets uploaded in one go?
syncFunction() {
var RNFS = require('react-native-fs');
var path = RNFS.DocumentDirectoryPath + '/toBeSynced';
RNFS.readDir(path)
.then((success) => {
for (let i = 0; i < success.length; i++) {
var fileName = success[i].name
var filePath = success[i].path
var uploadUrl = 'http://192.168.1.15:3333/SurveyJsonFiles/GetFiles/'
if (Platform.OS === 'android') {
filePath = filePath.replace("file://", "")
} else if (Platform.OS === 'ios') {
filePath = filePath
}
const data = new FormData();
data.append("files", {
uri: filePath,
type: 'multipart/form-data',
name: fileName,
});
const config = {
method: 'POST',
headers: {
'Accept': 'application/json',
},
body: data,
};
fetch(uploadUrl, config)
.then((checkStatusAndGetJSONResponse) => {
console.log(checkStatusAndGetJSONResponse);
this.moveFile(filePath, fileName)
}).catch((err) => {
console.log(err)
});
}
})
.catch((err) => {
console.log(err.message);
});
}
The JSON files will more than 50Mb depending on data, since it contains base64 image data the size will increase as the user takes more photos.
The app will be creating new files when the user records any information, There is no error message displayed for partial file upload.
The this.moveSyncedFiles() is moving the synced files to another folder so that the same file does not get uploaded multiple times
moveFile(oldpath, oldName) {
var syncedPath = RNFS.DocumentDirectoryPath + '/syncedFiles'
RNFS.mkdir(syncedPath)
syncedPath = syncedPath + "/" + oldName
RNFS.moveFile(oldpath, syncedPath)
.then((success) => {
console.log("files moved successfully")
})
.catch((err) => {
console.log(err.message)
});
}
It turns out the fault was on the nodejs side and nodemon was restarting the server every time a new file was found so we just moved the uploads folder outside the scope of the project
I am trying to upload a file from mobile to google bucket using ionic 4. Although a file can upload into the could. I am struggling to get the file properties out of file object.
Here is my method,
async selectAFile() {
const uploadFileDetails = {
name: '',
contentLength: '',
size: '',
type: '',
path: '',
};
this.fileChooser.open().then(uri => {
this.file.resolveLocalFilesystemUrl(uri).then(newUrl => {
let dirPath = newUrl.nativeURL;
const dirPathSegments = dirPath.split('/');
dirPathSegments.pop();
dirPath = dirPathSegments.join('/');
(<any>window).resolveLocalFileSystemURL(
newUrl.nativeURL,
function(fileEntry) {
uploadFileDetails.path = newUrl.nativeURL;
const file: any = getFileFromFileEntry(fileEntry);
//log 01
console.log({ file });
uploadFileDetails.size = file.size;
uploadFileDetails.name = `${newUrl.name
.split(':')
.pop()}.${file.type.split('/').pop()}`;
uploadFileDetails.type = file.type;
async function getFileFromFileEntry(fileEntry) {
try {
return await new Promise((resolve, reject) =>
fileEntry.file(resolve, reject)
);
} catch (err) {
console.log(err);
}
}
},
function(e) {
console.error(e);
}
);
});
});
// here uploadFileDetails is simller to what I declared at the top ;)
// I wan't this to be populated with file properties
// console.log(uploadFileDetails.name) --> //''
const uploadUrl = await this.getUploadUrl(uploadFileDetails);
const response: any = this.uploadFile(
uploadFileDetails,
uploadUrl
);
response
.then(function(success) {
console.log({ success });
this.presentToast('File uploaded successfully.');
this.loadFiles();
})
.catch(function(error) {
console.log({ error });
});
}
even though I can console.log the file in log 01. I am unable to get file properties like, size, name, type out of the resolveLocalFileSystemURL function. basically, I am unable to populate uploadFileDetails object. What am I doing wrong? Thank you in advance.
you actually need 4 Ionic Cordova plugins to upload a file after getting all the metadata of a file.
FileChooser
Opens the file picker on Android for the user to select a file, returns a file URI.
FilePath
This plugin allows you to resolve the native filesystem path for Android content URIs and is based on code in the aFileChooser library.
File
This plugin implements a File API allowing read/write access to files residing on the device.
File Trnafer
This plugin allows you to upload and download files.
getting the file's metadata.
file.resolveLocalFilesystemUrl with fileEntry.file give you all the metadata you need, except the file name. There is a property called name in the metadata but it always contains value content.
To get the human readable file name you need filePath. But remember you can't use returning file path to retrieve metadata. For that, you need the original url from fileChooser.
filePathUrl.substring(filePathUrl.lastIndexOf('/') + 1) is used to get only file name from filePath.
You need nativeURL of the file in order to upload it. Using file path returning from filePath is not going to work.
getFileInfo(): Promise<any> {
return this.fileChooser.open().then(fileURI => {
return this.filePath.resolveNativePath(fileURI).then(filePathUrl => {
return this.file
.resolveLocalFilesystemUrl(fileURI)
.then((fileEntry: any) => {
return new Promise((resolve, reject) => {
fileEntry.file(
meta =>
resolve({
nativeURL: fileEntry.nativeURL,
fileNameFromPath: filePathUrl.substring(filePathUrl.lastIndexOf('/') + 1),
...meta,
}),
error => reject(error)
);
});
});
});
});
}
select a file from the file system of the mobile.
async selectAFile() {
this.getFileInfo()
.then(async fileMeta => {
//get the upload
const uploadUrl = await this.getUploadUrl(fileMeta);
const response: Promise < any > = this.uploadFile(
fileMeta,
uploadUrl
);
response
.then(function(success) {
//upload success message
})
.catch(function(error) {
//upload error message
});
})
.catch(error => {
//something wrong with getting file infomation
});
}
uploading selected file.
This depends on your backend implementation. This is how to use File Transfer to upload a file.
uploadFile(fileMeta, uploadUrl) {
const options: FileUploadOptions = {
fileKey: 'file',
fileName: fileMeta.fileNameFromPath,
headers: {
'Content-Length': fileMeta.size,
'Content-Type': fileMeta.type,
},
httpMethod: 'PUT',
mimeType: fileMeta.type,
};
const fileTransfer: FileTransferObject = this.transfer.create();
return fileTransfer.upload(file.path, uploadUrl, options);
}
hope it helps. :)
I am using Cypress to use my application and encounter a problem by sending an uploaded file to the backend. It sends an empty FormData.
I am using the code found here https://github.com/cypress-io/cypress/issues/170 to handle file upload which is:
return cy.get('input[type=file]').then(subject => {
return cy
.fixture('blueprint.xlsx', 'base64')
.then(Cypress.Blob.base64StringToBlob)
.then(blob => {
const el = <HTMLInputElement>subject[0]
if (el != null) {
const testFile = new File([blob], 'blueprint.xlsx')
const dataTransfer = new DataTransfer()
dataTransfer.items.add(testFile)
el.files = dataTransfer.files
}
return subject
})
})
When I debug the API call, the file is set, it is in the fixtures folder and everything seems fine but the call doesn't have any formdata (which should be the file) and ends in a 400 Bad request error.
Why is the formdata empty? Is this a Cypress problem? Is there a way to send my fixture file to the backend?
Your code seems to run ok on the ng-file-upload demo page.
I also tested with an 'xlsx' file, no problem found.
describe('Angular file upload Demo', () => {
/*
To run these tests, add a file 'logo.png' to /cypress/fixtures
*/
it('uploads the fixture file', () => {
cy.visit('https://angular-file-upload.appspot.com/')
cy.get('[name=userName]').type('myLogo')
cy.get('[name=file]').then(subject => {
return cy.fixture('logo.png', 'base64')
.then(Cypress.Blob.base64StringToBlob)
.then(blob => {
console.log('blob', blob)
const el = subject[0]
if (el != null) {
const testFile = new File([blob], 'logo.png')
const dataTransfer = new DataTransfer()
dataTransfer.items.add(testFile)
el.files = dataTransfer.files
}
return subject
})
})
cy.contains('button', 'Submit').click()
cy.contains('.progress', '100%')
cy.contains('body', 'Upload Successful')
})
Cypress.Commands.add('uploadFile', { prevSubject: 'element' }, (subject, fileName) => {
console.log('subject', subject)
return cy.fixture(fileName, 'base64')
.then(Cypress.Blob.base64StringToBlob)
.then(blob => {
console.log('blob', blob)
const el = subject[0]
if (el != null) {
const testFile = new File([blob], fileName)
const dataTransfer = new DataTransfer()
dataTransfer.items.add(testFile)
el.files = dataTransfer.files
}
return subject
})
}
)
it('uploads the file via custom command', () => {
cy.visit('https://angular-file-upload.appspot.com/')
cy.get('[name=userName]').type('myLogo')
cy.get('[name=file]').uploadFile('logo.png')
cy.contains('button', 'Submit').click()
cy.contains('.progress', '100%')
cy.contains('body', 'Upload Successful')
})
})
I use "cypress": "3.3.1"
The following codes work for me,
const fixturePath = 'test.png';
const mimeType = 'application/png';
const filename = 'test.png';
cy.getTestElement('testUploadFrontID')
.get('input[type=file')
.eq(0)
.then(subject => {
cy.fixture(fixturePath, 'base64').then(front => {
Cypress.Blob.base64StringToBlob(front, mimeType).then(function(blob) {
var testfile = new File([blob], filename, { type: mimeType });
var dataTransfer = new DataTransfer();
var fileInput = subject[0];
dataTransfer.items.add(testfile);
fileInput.files = dataTransfer.files;
cy.wrap(subject).trigger('change', { force: true });
});
});
});
getTestElement is a command added by myself,
Cypress.Commands.add(`getTestElement`, selector =>
cy.get(`[data-testid="${selector}"]`)
);
after many hours of trying, i figured out a workaround to make ng-file-upload works.
At least my problem was about the File that was not passed as an instance of Blob, i guess.
I've used the same snippet as Jonas one on cypress side.
The workaround is to add a check into the upload function that manages changes in select and drop directives.
function upload() {
if (!Upload.isFile(file)) {
file = new File([file], file.name, { type: file.type })
}
Upload.upload({
url: "/api/upload",
data: {
file: file
}
})
.then(/* ... */)
/* ... */
}
This is just a workaround and i don't really like it.
I don't know why this happens, it happens for me only when i test it using cypress, so i don't like to add that in my production code.
Could someone please help me understanding why this happens?
Does anyone know why the file instance passed into the upload function seems to be a File instance but then it's not?
I'm working on this project using IPFS and I'm trying to create a website that allows users to upload files directly from their browser to IPFS. My goal was that the website would be a front-end website but whenever I add a file to IPFS and I check it's hash on https://gateway.ipfs.io/ipfs/hash-here nothing happens, which made me think that the files are probably not getting uploaded to IPFS because I'm not running it on my local machine. Is this correct?
const Buffer = require('safe-buffer').Buffer;
export default function uploadFiles(node, files) {
let reader = new FileReader();
reader.onloadend = () => {
let byteData = reader.result.split('base64,')[1];
let fileData = Buffer.from(byteData);
node.files.add(fileData, (err, res) => {
if (err) {
throw err
}
let hash = res[0].hash
console.log(hash); ///////prints a hash that isnt visible on
//gateway
node.files.cat(hash, (err, res) => {
if (err) {
throw err
}
let data = ''
res.on('data', (d) => {
data = data + d
})
res.on('end', () => {
// console.log(data);
// console.log(atob(data));
})
})
});
}
reader.readAsDataURL(files['0']);
};
Are you running a js-ipfs node in your browser? Did you get the chance to look at the examples in the examples folder in js-ipfs repo? Url here: https://github.com/ipfs/js-ipfs/tree/master/examples
If you add a file to your node and the node is on, the IPFS gateway node will be able to find the content from your browser node.