Download file: filename

hello guys,

my "program" creates a new file with date and time in its name (the file contains measured data, not important for my problem). Now I want to download the newest file via a download button. With following "tutorial" I can download the newest file which is fine so far (see reply post for link):

the problem I have now is that the downloaded file contains the correct (newest) data, but the filename is static. It doesn't have the date and time in it as original, instead its name is defined in the template node (In the tutorial it's "log.log"). In my flow (see code below), I wanted to change the download link to flow.filename_download (I defined this variable in another node), but instead of taking the value stored in flow.filename_download, it just saves the file as "flow.filename_download".

So when I make a measure which creates a new file, download this file, make a new measure and download the new file, both files have the "same" name (only difference is the browser creates automatically an (1) after the name).

In my node I worked with <button onclick...>, but the same happens with <a href..>

[{"id":"84f44064.d1cf4","type":"tab","label":"Flow 5","disabled":false,"info":""},{"id":"615a6ffd.f28aa","type":"function","z":"84f44064.d1cf4","name":"Set base path","func":"var basePath = \"/home/pi/Documents/S0_data/\";\nvar filename = msg.req.params.fn;\n\n\nif(filename.includes(\"..\\\\\")){\n    msg.payload = \"Illegal file path\";\n    msg.statusCode = 405;//not allowed\n    return [null, msg];//fire output 2\n} else if(filename.includes(\"../\")){\n    msg.payload = \"Illegal file path\";\n    msg.statusCode = 405;//not allowed\n    return [null, msg];//fire output 2\n} \n//TODO: add more checks\n\nmsg.filename = basePath + filename;\nreturn [msg, null];//fire output 1\n\n\n","outputs":2,"noerr":0,"initialize":"","finalize":"","libs":[],"x":300,"y":100,"wires":[["3660340e.5439fc"],["db65d67d.bbb078"]]},{"id":"db65d67d.bbb078","type":"http response","z":"84f44064.d1cf4","name":"","statusCode":"","headers":{},"x":710,"y":200,"wires":[]},{"id":"66b84db9.a899d4","type":"file in","z":"84f44064.d1cf4","name":"","filename":"","format":"","chunk":false,"sendError":false,"encoding":"none","x":710,"y":60,"wires":[["db65d67d.bbb078"]]},{"id":"98bbe24b.33931","type":"catch","z":"84f44064.d1cf4","name":"","scope":null,"uncaught":false,"x":120,"y":220,"wires":[["4e017f6.c2ae28","7b5c3dce.93b074"]]},{"id":"4e017f6.c2ae28","type":"function","z":"84f44064.d1cf4","name":"Set 404","func":"msg.payload = msg.error;\nmsg.statusCode = 404;//resource not found\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":480,"y":240,"wires":[["db65d67d.bbb078"]]},{"id":"7b5c3dce.93b074","type":"debug","z":"84f44064.d1cf4","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":130,"y":280,"wires":[]},{"id":"8269edfc.29861","type":"ui_template","z":"84f44064.d1cf4","group":"48c804f9.20970c","name":"Download Button","order":0,"width":0,"height":0,"format":"<button onclick=\"document.location='/files/flow.filename_download'\">Download Daten</button>\n","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":false,"templateScope":"local","x":370,"y":320,"wires":[[]]},{"id":"b2cc8c98.3f73a","type":"http in","z":"84f44064.d1cf4","name":"","url":"/files/:fn","method":"get","upload":false,"swaggerDoc":"","x":130,"y":100,"wires":[["615a6ffd.f28aa"]]},{"id":"3660340e.5439fc","type":"change","z":"84f44064.d1cf4","name":"","rules":[{"t":"set","p":"filename","pt":"msg","to":"filename_counter","tot":"flow"}],"action":"","property":"","from":"","to":"","reg":false,"x":510,"y":60,"wires":[["66b84db9.a899d4"]]},{"id":"48c804f9.20970c","type":"ui_group","name":"allgemein","tab":"8ec7137.b39f5f","order":5,"disp":false,"width":"6","collapse":false},{"id":"8ec7137.b39f5f","type":"ui_tab","name":"MenĂĽ","icon":"more_vert","order":2,"disabled":false,"hidden":false}]

https://flows.nodered.org/flow/db68bd4934cf46f39e6e453a348bc419

-> the link for the tutorial which I couldn't add in the original post somehow

Ok, I have a question for you:

Why did you add a change node to the flow. I see you are getting a flow variable to use as the filename but where do you set that flow variable?

ah sorry, I took these nodes from the bigger flow which I didn't want to post here. I have a function node where I create this flow variable which then is used as filename when the file is created (also by using a change node before the file out node).

I somehow need to get the flow.filename_download variable read into the dashboard template node, which only reads HTML if I'm correct?

I've already tried to use a button node instead, but I don't know how to add the "download function" to it. But at least in the button node I could use javascript to get flow.filename

Ok, I see what the issue is but I want you to find it, so
add a debug node (set to display the complete msg object) to the output of the function node and add a second debug node (also set to display the complete msg object) to the output of the change node and run a test.

Now look at the two debug outputs and what do you see that is different?

hmm so after the change node the filename is correct (in this case /home/pi/Documents/S0_data/Messung_2021-06-09_19-57-29.txt), but the URL and originalURL say /files/flow.filename_download.
In the function output the filename is not correct, it's /home/pi/Documents/S0_data/flow.filename_download and the URLs are the same as above. But I still can't figure out how I can change the "flow.filename_download" to the actual filename :sweat_smile:

So the button node is setting the documentation location as 'files' and giving the name of the document as flow.filename_download. The HTTP-in node grabs the name of the document as the parameter that is picked up in the function node by var filename = msg.req.params.fn;
Since you define the path as /home/pi/Documents/S0_data/ and stick the filename to it, you get:
/home/pi/Documents/S0_data/flow.filename_download
but that is not where the document lives. You need to change the path to include 'files' so it is /home/pi/Documents/S0_data/files/.

But since you don't actually want the file name stored in the button node, in the function node change:
var basePath = "/home/pi/Documents/S0_data/";
to
var msg.basePath = "/home/pi/Documents/S0_data/files/xxxx";
and comment out the line:
msg.filename = basePath + filename;

Then change the change node to this:
Screen Shot 2021-06-10 at 7.26.25 AM
and that should do the trick.

thx for the suggestions, I tried several things now but still end up with the fact the the name of the downloaded file is "flow.filename..." instead of the actual filename which was created in the begining. I had to remove the "var" from

var msg.basePath

In the end it just searches in the folder /home/pi/Documents/S0_data/files/ which doesn't exist, so I removed /files. But as I said, I end up with the same problem as in the begining. :sob:

Hi @limpstar

I wrote that original flow. I haven't read this full thread (as I'm too lazy :wink: )

Can you explain your issue clearly and export your flow. I'll take a look.

Hello, first of all thx for the tutorial.

Basically, it works: I can download the newest file that my program created. --> Why do I want to download the file? Because I access the dashboard, which runs on a Raspi, via a Smartphone or a Notebook (via browser), therefore I want to directly be able to get the files that are created and stored on the Raspi. The file that is created on the Raspi has date and time when it was created in its name. (ex. Messung_2021-06-10_04-50-28.txt). With the download button, I want to download the newest file. This works, but the filename when downloaded isn't correct --> it is called whatever I write in the template node. So instead of picking the value which is stored in flow.filename_download, it just names the downloaded file flow.filename_download.

The flow is the same as posted before. I don't want to post the whole flow because it is very big and you maybe wouldn't find the correct spots and you would have to create a new node to manually create the file.

[{"id":"84f44064.d1cf4","type":"tab","label":"Flow 5","disabled":false,"info":""},{"id":"615a6ffd.f28aa","type":"function","z":"84f44064.d1cf4","name":"Set base path","func":"var basePath = \"/home/pi/Documents/S0_data/\";\nvar filename = msg.req.params.fn;\n\n\nif(filename.includes(\"..\\\\\")){\n    msg.payload = \"Illegal file path\";\n    msg.statusCode = 405;//not allowed\n    return [null, msg];//fire output 2\n} else if(filename.includes(\"../\")){\n    msg.payload = \"Illegal file path\";\n    msg.statusCode = 405;//not allowed\n    return [null, msg];//fire output 2\n} \n//TODO: add more checks\n\nmsg.filename = basePath + filename;\nreturn [msg, null];//fire output 1\n\n\n","outputs":2,"noerr":0,"initialize":"","finalize":"","libs":[],"x":300,"y":100,"wires":[["3660340e.5439fc"],["db65d67d.bbb078"]]},{"id":"db65d67d.bbb078","type":"http response","z":"84f44064.d1cf4","name":"","statusCode":"","headers":{},"x":710,"y":200,"wires":[]},{"id":"66b84db9.a899d4","type":"file in","z":"84f44064.d1cf4","name":"","filename":"","format":"","chunk":false,"sendError":false,"encoding":"none","x":710,"y":60,"wires":[["db65d67d.bbb078"]]},{"id":"98bbe24b.33931","type":"catch","z":"84f44064.d1cf4","name":"","scope":null,"uncaught":false,"x":120,"y":220,"wires":[["4e017f6.c2ae28","7b5c3dce.93b074"]]},{"id":"4e017f6.c2ae28","type":"function","z":"84f44064.d1cf4","name":"Set 404","func":"msg.payload = msg.error;\nmsg.statusCode = 404;//resource not found\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":480,"y":240,"wires":[["db65d67d.bbb078"]]},{"id":"7b5c3dce.93b074","type":"debug","z":"84f44064.d1cf4","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":130,"y":280,"wires":[]},{"id":"8269edfc.29861","type":"ui_template","z":"84f44064.d1cf4","group":"48c804f9.20970c","name":"Download Button","order":0,"width":0,"height":0,"format":"<button onclick=\"document.location='/files/flow.filename_download'\">Download Daten</button>\n","storeOutMessages":true,"fwdInMessages":true,"resendOnRefresh":false,"templateScope":"local","x":370,"y":320,"wires":[[]]},{"id":"b2cc8c98.3f73a","type":"http in","z":"84f44064.d1cf4","name":"","url":"/files/:fn","method":"get","upload":false,"swaggerDoc":"","x":130,"y":100,"wires":[["615a6ffd.f28aa"]]},{"id":"3660340e.5439fc","type":"change","z":"84f44064.d1cf4","name":"","rules":[{"t":"set","p":"filename","pt":"msg","to":"filename_counter","tot":"flow"}],"action":"","property":"","from":"","to":"","reg":false,"x":510,"y":60,"wires":[["66b84db9.a899d4"]]},{"id":"48c804f9.20970c","type":"ui_group","name":"allgemein","tab":"8ec7137.b39f5f","order":5,"disp":false,"width":"6","collapse":false},{"id":"8ec7137.b39f5f","type":"ui_tab","name":"MenĂĽ","icon":"more_vert","order":2,"disabled":false,"hidden":false}]

What do you have stored in flow.filename_counter? - a file name or full file path? show me what is in this variable.

Do you ALWAYS want the same file (or rather to say, the file specified by flow.filename_counter)? Its just this flow was designed to permit you to chose the file you want to download (thats why the path is /files/:fn)

the full file path including the filename is stored in that variable. I thought that was correct as it's recognised correctly (see screenshot)

Yes, I always want to download the newest file (the one stored in the variable). Of course it would be nice if I could choose from a list with all the files that are stored, but this doesn't have priority.

ok, so I have modified it to download the file specified by your global variable.

I changed the endpoint to /report1 since there is no need for it to be variable & to set the content-disposition header filename (see the function node).

Test it in a browser by accessing http://localhost:1880/report1
(where localhost:1880 is your node-red)

[{"id":"615a6ffd.f28aa","type":"function","z":"84f44064.d1cf4","name":"Set base path","func":"\nvar basePath = \"/home/pi/Documents/S0_data/\";\nvar filepath = flow.get(\"filename_counter\");\n\n\nif (!filename) {\n    msg.payload = \"No file specified\";\n    msg.statusCode = 404;//not found\n    return [null, msg];//fire output 2\n} else if(filename.includes(\"..\\\\\")){\n    msg.payload = \"Illegal file path\";\n    msg.statusCode = 405;//not allowed\n    return [null, msg];//fire output 2\n} else if(filename.includes(\"../\")){\n    msg.payload = \"Illegal file path\";\n    msg.statusCode = 405;//not allowed\n    return [null, msg];//fire output 2\n} \n\n\n//set msg.filename for the file in node\nmsg.filename = filepath;\n\n//get file name only & add it as the Content-Disposition header\nvar filename = filepath.split(\"/\").pop();\nmsg.headers = {\n    \"Content-Disposition\": `attachment; filename=\"${filename}\"`\n}\n\nreturn [msg, null];//fire output 1\n\n\n","outputs":2,"noerr":0,"initialize":"","finalize":"","libs":[],"x":320,"y":100,"wires":[["66b84db9.a899d4"],["db65d67d.bbb078"]]},{"id":"db65d67d.bbb078","type":"http response","z":"84f44064.d1cf4","name":"","statusCode":"","headers":{},"x":550,"y":180,"wires":[]},{"id":"66b84db9.a899d4","type":"file in","z":"84f44064.d1cf4","name":"","filename":"","format":"","chunk":false,"sendError":false,"encoding":"none","x":550,"y":100,"wires":[["db65d67d.bbb078"]]},{"id":"98bbe24b.33931","type":"catch","z":"84f44064.d1cf4","name":"","scope":null,"uncaught":false,"x":120,"y":180,"wires":[["4e017f6.c2ae28","7b5c3dce.93b074"]]},{"id":"4e017f6.c2ae28","type":"function","z":"84f44064.d1cf4","name":"Set 404","func":"msg.payload = msg.error;\nmsg.statusCode = 404;//resource not found\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":300,"y":180,"wires":[["db65d67d.bbb078"]]},{"id":"7b5c3dce.93b074","type":"debug","z":"84f44064.d1cf4","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":130,"y":240,"wires":[]},{"id":"b2cc8c98.3f73a","type":"http in","z":"84f44064.d1cf4","name":"","url":"/report1","method":"get","upload":false,"swaggerDoc":"","x":130,"y":100,"wires":[["615a6ffd.f28aa"]]}]

I apologize, I misread your issue and thought only the file name was in the flow variable not the entire path and file name.

this code is too complex for me :sweat_smile: but I implemented it and it says "no file specified"

EDIT: i put the part where you define filename above the if and now it seems to work :slight_smile: I am doing some testing now and will give you again a feedback asap. Thx so far!

no need to apologize :wink: I appreciate every help!

What is in flow context

image

your flow you posted 1h ago (post 10) was using it ...

So I moved that to the function as you need it in there to generate the download filename...
image

-->
grafik
so this seems to be correct.

as in my edited post above, it works and every time I create a new file, the new file is being downloaded with the correct name. :+1: thx for this. But now I have another problem: Obviously the link to download the file contains the IP adress of the Raspi. Is there another way to generate a download link? Because I have two ways to acces the Raspi: Over LAN or WLAN. The IP adresses are different. So when I'm connected via LAN but the link contains the IP of the WLAN it won't work..

Use a relative link e.g. <a href="/report1">Download Report</a>

1 Like

aah so great :slight_smile: Thx so much, I really appreciate the help.