I have an Electron app. It sends via a ipcRenderer.send in renderer.js a download event to an ipcMain.on listener in index.js:
renderer.js:
for (var i=0; i < data3.projects.length; i++) {
var project = data3.projects[i];
for (var j=0; j < project.files.length; j++) {
var file = project.files[j];
async function downloadFunction(file) {
await ipcRenderer.send("download", {
url: file.url,
location: "/Users/Marc/GreenPool/" + project.projectId + "/files/"
})
console.log("downloaded " + file.url);
}
await downloadFunction(file);
}
}
index.js:
ipcMain.on("download", function (event, info) {
console.log(info.location);
download(mb.window.webContents, info.url, {directory:info.location});
});
So it is supposed to download (thanks to electron-dl npm package) all the files under project.files, and save them under info.location.
When there is only one project under data3.projects array, it works, the files get downloaded and stored under "/Users/Marc/GreenPool/" + project.projectId + "/files/".
But when there is more than one value in data3.projects array, it only saves the downloaded files of one of the data3.project, not of the others:
One has the files:
One doesn't:
We can see on the first picture that one of the files is duplicated. I think it shows that the issue has to do with the fact that after writing the first files in the first folder, it somehow still thinks that it is in this one when starting to write the second files (we can see it with file compute2Numbers(1).js being a duplicate because it is in the wrong folder).
And the log of console are logic:
index.js:
/Users/Marc/GreenPool/qjggnatri/files/
/Users/Marc/GreenPool/qjggnatri/files/
/Users/Marc/GreenPool/qjggnatri/files/
/Users/Marc/GreenPool/qjggnatri/files/
/Users/Marc/GreenPool/z9g7_a1qa/files/
/Users/Marc/GreenPool/z9g7_a1qa/files/
/Users/Marc/GreenPool/z9g7_a1qa/files/
/Users/Marc/GreenPool/z9g7_a1qa/files/
renderer.js:
It seems like it is an asynchronous issue. But I have wrapped the queries in an async await function to prevent the two download events from overlapping each other. And the logs of the console show it: first the files of the first value in data3.projects array are downloaded, then the files of the second value in data3.projects array are downloaded.
So how can I make it happen that the files get saved in the proper folder?
Related
I am trying to open multiple images simultaneously in the default Windows Photo Viewer that I have stored in my folder using the npm open library:
let files = ['Dog.gif', 'Cat.jpeg'];
for(let i=0; i<files.length; i++){
open(`${files[i]}`, {wait:true});
}
But it's only opening the first file? I am not sure how to fix this?
in my case, it happened because the file name or file type did not exist or did not match the source file
{wait: true} is supposed to cause open() to wait until you exit the application that opens the file before resuming execution. So if you expect them all to open at the same time, remove the {wait: true} option.
If you, in fact, do want it to wait until the application exits before opening the second file and hare having it only open one file, read on for at least some minimal debugging help.
Assuming you are using a recent version of open, it is a promise-based API. It seems to make it hard to get at errors. However, you can at least confirm that you're getting an error like this:
const open = require('open');
let files = ['dog.txt', 'cat.txt'];
(async () => {
for(let i=0; i<files.length; i++){
const cp = await open(`${files[i]}`, {wait: true});
console.log(`${files[i]} exit code: ${cp.exitCode}`);
}
})();
That will print an error code of 0 for all files where open() succeeded and a non-zero value for all files where open() failed.
I'm trying to use the File and Directory Entries API to create a file uploader tool that will allow me to drop an arbitrary combination of files and directories into a browser window, to be read and uploaded.
(I'm fully aware that similar functionality can be achieved by using an file input element with webkitdirectory enabled, but I'm testing a use case where the user isn't forced to put everything into a single folder)
Using the Drag and Drop API, I've managed to read the DataTransfer items and convert them to FileSystemEntry objects using DataTransferItem.webkitGetAsEntry.
From there, I am able to tell that if the entry is a FileSystemFileEntry or a FileSystemDirectoryEntry. My plan of course if to recursively walk the directory structure, if any, which I should be able to do using the FileSystemDirectoryReader method readEntries, like this:
handleDrop(event) {
event.preventDefault();
event.stopPropagation();
//assuming I dropped only one directory
const directory = event.dataTransfer.items[0];
const directoryEntry = directory.webkitGetAsEntry();
const directoryReader = directoryEntry.createReader();
directoryReader.readEntries(function(entires){
// callback: the "entries" param is an Array
// containing the directory entries
});
}
However, I'm running into the following issue: in Chrome, the readEntries method only returns 100 entries. Apparently, this is the expected behavior as the way to obtain subsequent files from the directory is to call readEntries again. However, I'm finding this impossible to do. A subsequent call to the method throws the error:
DOMException: An operation that depends on state cached in an interface object was made but the state had changed since it was read from disk.
Does anyone know a way around this? Is this API hopelessly broken for directories of 100+ files in Chrome? Is this API deprecated? (not that it was ever "precated"). In Firefox, readEntries returns the whole directory content at once, which apparently against the spec, but it is usable.
Please advice.
Of course, as soon as I had posted this question the answer hit me. What I was trying to do was akin to the following:
handleDrop(event) {
event.preventDefault();
event.stopPropagation();
//assuming I dropped only one directory
const directory = event.dataTransfer.items[0];
const directoryEntry = directory.webkitGetAsEntry();
const directoryReader = directoryEntry.createReader();
directoryReader.readEntries(function(entries){
// callback: the "entries" param is an Array
// containing the directory entries
}, );
directoryReader.readEntries(function(entries){
//call entries a second time
});
}
The problem with this is that readEntries is asynchronous, so I'm trying to call it while it's "busy" reading the first batch (I'm sure lower-level programmers will have a better term for that). A better way of achieving what I was trying to do:
handleDrop(event) {
event.preventDefault();
event.stopPropagation();
//assuming I dropped only one directory
const directory = event.dataTransfer.items[0];
const directoryEntry = directory.webkitGetAsEntry();
const directoryReader = directoryEntry.createReader();
function read(){
directoryReader.readEntries(function(entries){
if(entries.length > 0) {
//do something with the entries
read(); //read the next batch
} else {
//do whatever needs to be done after
//all files are read
}
});
}
read();
}
This way we ensure the FileSystemDirectoryReader is done with one batch before starting the next one.
I am attempting to zip the contents of two directories and download the resulting .zip file. One directory contains .txt files and the other .jpg. I am using archiver to zip the files and running the express framework on node js. I am inclined to think that the problem exists in the download step, as the resulting zipped file that is created in the project root expands as expected, however when the file is downloaded, I get an "Error 2 - No such file or directory."
app.get('/download',function(req, res){
zipFile = new Date() + "-Backup.zip";
var output = fs.createWriteStream(__dirname +"/backups/"+ zipFile);
var archive = archiver('zip');
output.on('close', function() {
console.log(archive.pointer() + ' total bytes');
console.log('archiver has been finalized and the output file descriptor has closed.');
});
archive.on('error', function(err) {
throw err;
});
archive.pipe(output);
var files1 = fs.readdirSync(__dirname+'/posts');
var files2 = fs.readdirSync(__dirname+'/uploads');
for(var i = 0; i< files1.length; i++){
archive.append(fs.createReadStream(__dirname+"/posts/"+files1[i]), { name: files1[i] });
}
for(var i = 0; i< files2.length; i++){
archive.append(fs.createReadStream(__dirname+"/uploads/"+files2[i]), { name: files2[i] });
}
archive.finalize();
res.download(__dirname + "/backups/" + zipFile, zipFile);
});
zipFile is a global variable.
The on 'close' logs fire properly and no errors occur, but the file will not open after being downloaded. Is there an issue with response headers or something else I am unaware of?
Thanks for the help.
I solved my own problem using node-zip as the archive utility.
var zip = require('node-zip')(); // require the node-zip utility
var fs = require('fs'); // I use fs to read the directories for their contents
var zipName = "someArbitraryName.zip"; // This just creates a variable to store the name of the zip file that you want to create
var someDir = fs.readdirSync(__dirname+"/nameOfDirectoryToZip"); // read the directory that you would like to zip
var newZipFolder = zip.folder('nameOfDirectoryToZip'); // declare a folder with the same name as the directory you would like to zip (we'll later put the read contents into this folder)
//append each file in the directory to the declared folder
for(var i = 0; i < someDir.length,i++){
newZipFolder.file(someDir[i], fs.readFileSync(__dirname+"/nameOfDirectoryToZip/"+someDir[i]),{base64:true});
}
var data = zip.generate({base64:false,compression:'DEFLATE'}); //generate the zip file data
//write the data to file
fs.writeFile(__dirname +"/"+ zipName, data, 'binary', function(err){
if(err){
console.log(err);
}
// do something with the new zipped file
}
Essentially, what is happening can broken down into 3 steps:
Use fs to read the contents of a directory that you would like to zip.
Use zip.folder to declare the folder, then use zip.file to append files to that directory. I just used a for loop to iteratively add each file in the directory that was read in step 1.
Use zip.generate to create the .zip file data, and write it to file using fs.
The resulting file can be downloaded or whatever you would like to do with it. I have seen no issues using this method.
If you want to zip more than one directory, just repeat steps 1 and 2 before you zip.generate, creating a new zip.folder for each directory.
Just use
archive.on("finish",function(){
res.download(__dirname + "/backups/" + zipFile);
})
I followed the interactive live sdk and added it into my HTML page.
Also, I have successfully added the callback.html page where I am successfully getting the file picker dialog box. Once I select file from the file dialog box it's getting downloaded which I understand because of WL.download function.
But all I want is to attach the files rather than downloading it. how to change the javascript in interactive live sdk
Any suggestions?
Sorry about that. You can either use the "source" or the "link" to accomplish this. On the ISDK for "Using the open from OneDrive picker", change the code following code snippet (I used "file.link" below). The Output box should give you some idea on what the link would be if you include it in your app. You'll, of course, will want to remove the "WL.download" function so that it doesn't download the file and add the file.link or file.source somewhere into your code instead of logging it like the ISDK does.
function openFromSkyDrive() {
WL.fileDialog({
mode: 'open',
select: 'single'
}).then(
function(response) {
log("The following file is being downloaded:");
log("");
var files = response.data.files;
for (var i = 0; i < files.length; i++) {
var file = files[i];
log(file.link);
WL.download({ "path": file.id + "/content" });
}
},
function(errorResponse) {
log("WL.fileDialog errorResponse = " + JSON.stringify(errorResponse));
}
);
}
In the ISDK (http://isdk.dev.live.com), you'll want to try using the "Using the save to OneDrive picker". You'll notice that WL.fileDialog is set to ({ mode: 'save' }) and the WL.upload function is called. I hope that helps.
Summary
I am attempting to find out why the wl.download function will not download more than one file even though the Microsoft examples seem to indicate that they can.
And, the code seems to be called for each file you attempt to download, but only the one file is actually downloaded.
Details
Here are the details of how you can see this problem which I've tried in IE 11.x and Chrome 30.x
If you will kindly go to :
http://isdk.dev.live.com/dev/isdk/ISDK.aspx?category=scenarioGroup_skyDrive&index=0
You will be able to run an example app which allows you to download files from your skydrive.
Note: the app does require you to allow the app to access your skydrive.
Once you get there you'll see code that looks like this on the right side of the page:
Alter One Value: select:
You need to alter one value: Change the
select: 'single'
to
select: 'multi'
which will allow you to select numerous files to download to your computer. If you do not make that one change then you won't be able to choose more than one file in the File dialog.
Click the Run Button to Start
Next, you'll see a [Run] button to start the app (above the code sample).
Go ahead and click that button.
Pick Files For Download
After that just traverse through your skydrive files and choose more than one in a folder and click the [Open] button. At that point, you will see one of the files actually downloads, and a number of file names are displayed in the bottom (output) section of the example web page.
My Questions
Why is it that the others do not download, even though wl.download is called in the loop, just as the console.log is called in the loop?
Is this a known limitation of the browser?
Is this a known bug in skydrive API?
Is this just a bug in the example code?
The problem here is that the call to wl.download({ "path": file.id + "/content" }) stores some internal state (among other things, the file being downloaded and the current status thereof). By looping over the list of files, that state is in fact overwritten with each call. When I tried downloading three text files at once, it was always the last one that was actually downloaded and never the first two.
The difficulty here is that the downloads are executed in the traditional fashion, whereby the server adds Content-Disposition: attachment to the response headers to force the browser to download the file. Because of this, it is not possible to receive notification of any kind when the download has actually completed, meaning that you can't perform the downloads serially to get around the state problem.
One approach that I thought might work is inspired by this question. According to the documentation, we can get a download link to a file if we append /content?suppress_redirects=true to its id. Using this approach, we can set the src property of an IFrame and download the file that way. This works OK, but it will only force a download for file types that the browser can't natively display (zip files, Exe files, etc.) due to the lack of the Content-Disposition: attachment response header.
The following is what I used in the Interactive Live SDK.
WL.init({ client_id: clientId, redirect_uri: redirectUri });
WL.login({ "scope": "wl.skydrive wl.signin" }).then(
function(response) {
openFromSkyDrive();
},
function(response) {
log("Failed to authenticate.");
}
);
function openFromSkyDrive() {
WL.fileDialog({
mode: 'open',
select: 'multi'
}).then(
function(response) {
log("The following file is being downloaded:");
log("");
var files = response.data.files;
for (var i = 0; i < files.length; i++) {
var file = files[i];
log(file.name);
WL.api({
path: file.id + "/content?suppress_redirects=true",
method: "GET"
}).then(
function (response) {
var iframe = document.createElement("iframe");
iframe.src = response.location;
iframe.style.display = "none";
document.body.appendChild(iframe);
},
function (responseFailed) {
log("Error calling API: " + responseFailed.error.message);
}
);
}
},
function(errorResponse) {
log("WL.fileDialog errorResponse = " + JSON.stringify(errorResponse));
}
);
}
function log(message) {
var child = document.createTextNode(message);
var parent = document.getElementById('JsOutputDiv') || document.body;
parent.appendChild(child);
parent.appendChild(document.createElement("br"));
}
Did you try to bind some events to the WL.download() method? According to the documentation:
The WL.download method accepts only one parameter:
The required path parameter specifies the unique SkyDrive file ID of the file to download.
If the WL.download method call is unsuccessful, you can use its then method's onError parameter to report the error. In this case, the WL.download doesn't support the onSuccess and onProgress parameters. If the WL.download method call is successful, the user experience for actually downloading the files will differ based on the type of web browser in use.
Perhaps you are getting some errors in your log to identify the problem.
For me, one suggestion without having checked the documentation, I can think of the fact that you are not waiting for each download to end. Why not change your loop in such a manner that you call WL.download() only if you know no other download is currently running ( like calling the next WL.download only in the success/complete event ):
WL.download({ "path": file.id + "/content" }).then(
function (response) {
window.console && console.log("File downloaded.");
//call the next WL.download() here <!-----------------
},
function (responseFailed) {
window.console && console.log( "Error downloading file: " + responseFailed.error.message);
}
);