HTML5 FileSystem API - javascript

I have created file 'log.txt' by fileSystem API
function initFS(grantedBytes) {
window.requestFileSystem(window.PERSISTENT, grantedBytes, function (filesystem) {
fs = filesystem;
fs.root.getFile('log.txt', { create: true, exclusive: true }, function (fileEntry) {
// fileEntry.isFile === true
// fileEntry.name == 'log.txt'
// fileEntry.fullPath == '/log.txt'
console.log(fileEntry.fullPath);
}, errorHandler);
}, errorHandler);
}
initFS(1024*1024);
And do not fully understand its structure. Is There any way to explore this file
for example from Windows Explorer and see it in file system?

There is an even simpler way. On chrome, visit these urls.
For http, it's "filesystem:http://"+location.host+"/persistent/".
For https, it's "filesystem:https://"+location.host+"/persistent/".

Sort of, the File-system API doesn't encrypt the data being stored locally. It does however change the file naming conventions up. So you may have named it log.txt but if you poke around where the file-system API stores files, you'd probably find it under some arbitrary randomly generated file name like "00010" or in a random directory like "24/00123".
Anyway, you can open each file up in a text editor - if your file had text written to it you would be able to view it as such. Or if you wrote JSON to the file-system API it would be in human-readable string format when you opened in the text editor.
On Windows 7, with Chrome it's found here:
C:\Users\{user}\AppData\Local\Google\Chrome\User Data\Default\File System\
If you want to find out where it is stored via Chrome on other OS please see this post

Log files that an end-user or maintainer might want to see should be stored someplace in the normal file system. While the checked answer suggests how to find them when the HTML5 API is used, this location is subject to change and is troublesome to find.
A better solution is to have the user choose the directory for log files (and perhaps other files) when the app is installed, using chrome.fileSystem.chooseEntry, and then retain that entry and save it in local storage so it can be reused on subsequent launches.

Related

Copy/download temporary files

I am not an HTML/JavaScript developer. I am having to modify some legacy code written by someone who has left.
We have a Python app which acts as a local server with an HTML/JavaScript front end that can be viewed in a browser.
The Python creates a temporary cache file. I would like to give the user the option to save a copy of this temp file to a location of their choice or at least download it to the downloads directory (Windows & Linux)
I've tried adapting some of the ideas from here: https://www.delftstack.com/howto/javascript/javascript-download/
E.g.
const saveAnalysisBtn = document.getElementById("saveAnalysisBtn");
saveAnalysisBtn.addEventListener('click', saveAnalysis);
function saveAnalysis(evt) {
function download(filename) {
var element = document.createElement('a');
// hardcode temp file name just for POC
element.setAttribute('href','file://C:\\tmp\\my_temp_cache.db');
element.setAttribute('download', filename);
document.body.appendChild(element);
element.click();
//document.body.removeChild(element);
}
var filename = "output.txt";
console.log(`Call Download`);
download(filename);
}
In Firefox this gives a security error:
Security Error: Content at
http://127.0.0.1:5000/replay/fapi_15_6_udi.bin may not load or link to
file:///C:/tmp/my_temp_cache.db
Which isn't terribly surprising. (Edge & Chrome give similar errors)
Is there a way to do this? Can be in HTML or JavaScript or Python (though I would like user to see evidence of download taking place in the browser).
Maybe I'm not understanding, but it looks like we're talking about just copying a file from one local location to a user specified location. The file you want to copy is on the machine the user is using? Couldn't you just provide the location in the web page and then just go there in a file explorer, finder, or command line tool to copy it however you want? It would solve the security issue.
But if you're required to create a link, you could create a download process that zips the file up to make a file like "my_temp_cache_db.zip" (or whatever compression tool/extension works best for you), and then provide the link for that. Zip files work through browsers better than some other types of files, and the user just has to unzip it wherever it ended up.
If that's not ideal, you could create a download process that makes a copy of the file and just changes the extension to something like "txt". The user downloads that file and then has to rename it to have the right extension.

How to Launch a PDF from a UWP (Universal Windows Platform) Web Application

I've converted an existing web application (HTML5, JS, CSS, etc.) into a Windows UWP app so that (hopefully) I can distribute it via the Windows Store to Surface Hubs so it can run offline. Everything is working fine, except PDF viewing. If I open a PDF in a new window, the Edge-based browser window simply crashes. If I open an IFRAME and load PDFJS into it, that also crashes. What I'd really like to do is just hand off the PDF to the operating system so the user can view it in whatever PDF viewer they have installed.
I've found some windows-specific Javascript APIs that seem promising, but I cannot get them to work. For example:
Windows.System.Launcher.launchUriAsync(
new Windows.Foundation.Uri(
"file:///"+
Windows.ApplicationModel.Package.current.installedLocation.path
.replace(/\//g,"/")+"/app/"+url)).then(function(success) {
if (!success) {
That generates a file:// URL that I can copy into Edge and it shows the PDF, so I know the URL stuff is right. However, in the application it does nothing.
If I pass an https:// URL into that launchUriAsync function, that works. So it appears that function just doesn't like file:// URLs.
I also tried this:
Windows.ApplicationModel.Package.current.installedLocation.getFileAsync(url).then(
function(file) { Windows.System.Launcher.launchFileAsync(file) })
That didn't work either. Again, no error. It just didn't do anything.
Any ideas of other things I could try?
-- Update --
See the accepted answer. Here is the code I ended up using. (Note that all my files are in a subfolder called "app"):
if (location.href.match(/^ms-appx:/)) {
url = url.replace(/\?.+/, "");
Windows.ApplicationModel.Package.current.installedLocation.getFileAsync(("app/" + url).replace(/\//g,"\\")).then(
function (file) {
var fn = performance.now()+url.replace(/^.+\./, ".");
file.copyAsync(Windows.Storage.ApplicationData.current.temporaryFolder,
fn).then(
function (file2) {
Windows.System.Launcher.launchFileAsync(file2)
})
});
return;
}
Turns out you have to turn the / into \ or it won't find the file. And copyAsync refuses to overwrite, so I just use performance.now to ensure I always use a new file name. (In my application, the source file names of the PDFs are auto-generated anyway.) If you wanted to keep the filename, you'd have to add a bunch of code to check whether it's already there, etc.
LaunchFileAsync is the right API to use here. You can't launch a file directly from the install directory because it is protected. You need to copy it first to a location that is accessible for the other app (e.g. your PDF viewer). Use StorageFile.CopyAsync to make a copy in the desired location.
Official SDK sample: https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/AssociationLaunching
I just thought I'd add a variation on this answer, which combines some details from above with this info about saving a blob as a file in a JavaScript app. My case is that I have a BLOB that represents the data for an epub file, and because of the UWP content security policy, it's not possible simply to force a click on a URL created from the BLOB (that "simple" method is explicitly blocked in UWP, even though it works in Edge). Here is the code that worked for me:
// Copy BLOB to downloads folder and launch from there in Edge
// First create an empty file in the folder
Windows.Storage.DownloadsFolder.createFileAsync(filename,
Windows.Storage.CreationCollisionOption.generateUniqueName).then(
function (file) {
// Open the returned dummy file in order to copy the data to it
file.openAsync(Windows.Storage.FileAccessMode.readWrite).then(function (output) {
// Get the InputStream stream from the blob object
var input = blob.msDetachStream();
// Copy the stream from the blob to the File stream
Windows.Storage.Streams.RandomAccessStream.copyAsync(input, output).then(
function () {
output.flushAsync().done(function () {
input.close();
output.close();
Windows.System.Launcher.launchFileAsync(file);
});
});
});
});
Note that CreationCollisionOption.generateUniqueName handles the file renaming automatically, so I don't need to fiddle with performance.now() as in the answer above.
Just to add that one of the things that's so difficult about UWP app development, especially in JavaScript, is how hard it is to find coherent information. It took me hours and hours to put the above together from snippets and post replies, following false paths and incomplete MS documentation.
You will want to use the PDF APIs https://github.com/Microsoft/Windows-universal-samples/tree/master/Samples/PdfDocument/js
https://github.com/Microsoft/Windows-universal-samples/blob/master/Samples/PdfDocument/js/js/scenario1-render.js
Are you simply just trying to render a PDF file?

Is it possible to stream an octet stream being generated in javascript?

Lets suppose a case where a huge string is generated from a small string using some javascript logic, and then the text file is forced to be downloaded on the browser.
This is possible using an octet-stream download by putting it as an href , as mentioned in this answer :
Create a file in memory for user to download, not through server.
function download(filename, text) {
var pom = document.createElement('a');
pom.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
pom.setAttribute('download', filename);
pom.click();
}
But this solution requires 'text' to be fully generated before being pushed for the download,
hence it will have to be held in browser memory fully .
Is it possible to stream the text as it gets generated using CLIENT SIDE LOGIC ONLY ?
For example :
var inputString = "A";
var outStr = "";
for(var i = 0; i < 10000000 ; i++)
{
/* concatenate inputString to output on the go */
}
Yes & no. No because there's not a way to write to files with just client-side javascript. Kinda. You can prompt a user to download & save a file, but as you mentioned, the code must generate the whole file before that download happens. Note: By "stream" I assume you mean stream to file (constantly write to a file) & by "CLIENT SIDE LOGIC ONLY" I assume you mean in the browser.
Looks like Mozilla has been working on a way to let client-side code interact with files. Here comes the yes. Kind of. They have their own file system api that lets you interact with (write to) the local machines file system. Specifically, there's a function that lets you write an input stream to a file. However, there's a few asterisks:
1) looks like that whole system is being deprecated; they encourage developers to use OS.file over File I/O
2) You have to use XPConnect, a system that lets you access Mozilla's XPCOM (component library) in javascript. If you want to do this in the browser, it looks like only firefox extensions have the proper permissions to interact with those components (). If you didn't want to do this in the browser, you obviously could just use node.
Assuredly, more complications are bound to show up during implementation. But this looks like the most sure path forward, seeing as how OS.File gives you access to functions like OS.File.writeAtomic() & basic write to file
That being said, it's not that great of a path, but hopefully this gives you a solid starting point. As #dandavis mentioned, browsers (i.e. "client side logic") are designed to not allow this sort of thing. It would be an incredibly huge oversight / security flaw if a website could interact with any user's local file system.
Additional resources:
Wikipedia on XPConnect
Guide on working with XPCOM in javascript - may not be that useful
There is a way to do this, but it relies on a Chrome only Filesystem API. We will create and write to a temporary file in a sandboxed file system and the copy it to the regular file system once we are done. This way you do not have to store the entire file in memory. The asynchronous version of the Chrome API is not currently being considered for standardization by W3C, but the synchronous verison (which uses web workers) is. If browser support is a concern, then this answer is not for you.
The API works like this:
First, we get the requestFileSystem() function from the browser. Currently it is prefixed by "webkit":
window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
Next, we request a temporary file system (this way we do not need to ask for user permission):
var fileSystem; //This will store the fileSystem for later access
var fileSize = 1024*1024 //Our maximum file system size.
function errorHandler(e) {
console.log('Error: ' + e.name);
}
window.requestFileSystem(window.TEMPORARY, fileSize, function (fs) { fileSystem = fs; }, errorHandler);
Now that we have access to the file system it is time to create a file:
var fileOptions = {
create: true, //If the file is not found, create it
exclusive: false //Don't throw an error if the file doesn't exist
};
Here we call the getFile() function, which can create a file if it doesn't exist. Inside of the callback, we can create a new fileWriter for writing to the file. The fileWriter is then moved to the end of the file, and we create a new text blob to append to it.
fileSystem.root.getFile(fileName, fileOptions, function(fileEntry) {
fileEntry.createWriter(function(fileWriter) {
fileWriter.seek(fileWriter.length);
var blob = new Blob([STRING_TO_WRITE], {type: 'text/plain'});
fileWriter.write(blob);
}, errorHandler);
});
Note that this API does not save to the normal, user filesystem. Instead, it saves to a special sandboxed folder. If you want to save it to the user's file system, you can create a filesystem: link. When the user clicks on it, it will prompt them to save it. After they save it, you can then remove the temporary file.
This function generates the filesystem link using the fileEntry's toURL() function:
var save = function () {
var download = document.querySelector("a[download]");
if (!fileSystem) { return; }
fileSystem.root.getFile(fileName, {create: false, exclusive: true}, function(fileEntry) {
download.href = fileEntry.toURL();
}, errorHandler);
}
Using a link with the download attribute will force the download of the file.
<a download></a>
Here is a plunker that demonstrates this: http://plnkr.co/edit/q6ihXWEXSOtutbEy1b5G?p=preview
Hopefully this accomplishes what you want. You can continuously append to the file, it won't be kept in memory, but it will be in the sandboxed filesystem until the user saves it to the regular filesystem.
For more information take a look at this HTML5rocks article or this one if you want to use the newer, synchronous Web Worker API.
I would have suggest it the way #quantumwannabe describes it, using temporary sandbox file to append chunks.
But there is a new way that can be used today (behind a flag) but will be enabled in the next version of chrome (52)
And here is where i will make #KeenanLidral-Porter answer incorrect. And #quantumwannabe answer a unnecessary step
Because there is now a way to write a stream to the filesystem directly: StreamSaver.js
It acts as if there was a server sending octet-stream header and tells the browser to download chunks of data with help of a service worker
const writeStream = streamSaver.createWriteStream('filename.txt')
const encoder = new TextEncoder
let data = 'a'.repeat(1024) // Writing some stuff triggers the save dialog to show
let uint8array = encoder.encode(data + "\n\n")
writeStream.write(uint8array) // Write some data when you got some
writeStream.close() // End the saving

Downloading a file to the file system in WinJS

We are developing an app that is to download files from HTTP URLs, the extensions/file types of which we will not know until runtime. We've been following this tutorial as a starting point, but since we aren't dealing with images, it hasn't helped us.
The issue is that the code in the tutorial will get you a Blob object and I can't find any code that will allow us to either:
Convert the Blob to a byte array.
Save the Blob straight to the file system.
The ultimate goal is to seamlessly save the file at the given URL to the file system and launch it with the default application, or to just launch it from the URL directly (without the save prompt you get if you just call Windows.System.Launcher.launchUriAsync(uri);).
Any insight anyone might have is greatly appreciated.
Regarding downloading content into byte array:
Using WinJS.xhr with the responseType option as 'arraybuffer' will return the contents in ArrayBuffer. A javascript typed array can be instantiated from the ArrayBuffer for example UInt8Array. This way contents can be read into byte array. code should look something like this:
// todo add other options reqd
var options = { url: url, responseType: 'arraybuffer' };
WinJS.xhr(options).then(function onxhr(ab)
{
var bytes = new Uint8Array(ab, 0, ab.byteLength);
}, function onerror()
{
// handle error
});
Once you take care of permissions to save the file to file system either by user explicitly picking the save file location using SaveFilePicker or pick folder using folder picker - file can be saved on local file system. Also, file can be saved to app data folder.
AFAIK, html/js/css files from local file system or the app data cannot be loaded for security reasons. Although DOM can be manipulated under constraints, to add content. I am not sure of your application requirements. You might need to consider alternatives instead of launching downloaded html files.

How to store file in a browser using JavaScript

I am working for a prototype of website(Only Browser based). There is a part where I need to upload some files.Here I am using JavaScript and HTML.
Whenever user wants to upload(Like Browse button in applications) some files then it will available for next time.I am unable to do this.
Question Can we save/store/upload a file using JavaScript/HTML in browser only(Not server )??
Thanks
Downloading file directly to user's file-system
If you by save/store mean directly to user's computer/file system then no, this is not possible due to security reasons.
However, you can initiate a download which will pop up a "save as" type of requester and allow user to accept or deny to download the file.
There are more than one way to initiate a download. An interesting one is the new download attribute for the anchor tag where you can specify a file name as well as automatically initiate the download setting its href to a data-uri or something else you want to reference for download:
Click to download
Local storage mechanisms
If you simply want to save the file locally you can use one of the many local storage mechanisms such as:
File system API (only supported in Chrome currently and the new Opera. In draft status)
Indexed DB (allows Blob objects. Good support)
Web SQL (deprecated but widely in use)
Web Storage (very good support but only stores strings and has limited space, objects can be saved as JSON strings)
Note that all of these as sand-boxed and only available in the browser using the same origin as they was written from. The data may or may not be discarded at any point as well (by user or by browser) so they are not a "safe" storage (always keep a server copy or a way to regenerate the data).
Yes, it's possible via FileSystem API (currently only Chrome and Opera).
window.requestFileSystem = window.requestFileSystem || window.webkitRequestFileSystem;
document.querySelector('input[type="file"]').onchange = function(e) {
var files = this.files;
window.requestFileSystem(window.TEMPORARY, 1024*1024, function(fs) {
// Duplicate each file the user selected to the app's fs.
for (var i = 0, file; file = files[i]; ++i) {
// Capture current iteration's file in local scope for the getFile() callback.
(function(f) {
fs.root.getFile(f.name, {create: true, exclusive: true}, function(fileEntry) {
fileEntry.createWriter(function(fileWriter) {
fileWriter.write(f); // Note: write() can take a File or Blob object.
}, errorHandler);
}, errorHandler);
})(file);
}
}, errorHandler);
};
"Can we save/store/upload a file using JavaScript/HTML in browser only(Not server )??"
Ans. is No. If you want to retain the uploaded file then you need to store it on server.
The moment the browser tab is closed the file will get lost.
Alternative: What you can do is store the name of the file on server and whenever user request the file then ask him to upload it.
For uploading the file into memory you can refer to this link .
Live Demo

Categories