Android webview - how to fetch a file from external storage - javascript

I have a mobile app that wraps around the web-app, using webview.
The web-app has a button to open a large .zip file (e.g. 100 MB).
The user clicks a button, and selects a .zip file.
This triggers an onChange function with a variable of type File (Blob), which includes attributes like:
file name
file size
file type (application/zip)
The javascript code then parses the .zip file, extracts specific data within it and uses it within the web-app.
This works well within the web-app, when the app is called via the Chrome browser.
For example when operated in chrome browser on an Android phone, I can pull the .zip file and open it in the web-app.
I want to do the same but using the mobile app.
I am able to pick up the .zip file using a File Chooser, and pass it to Webview but I have problems to fetch the file from the Javascript code.
For reference, I am able to pass an image, by creating a data_uri using stringBuilder and passing the content (as data:image/jpeg;base64).
But the zip file is much larger.
When calling fetch(fileUri) from the Javascript side I'm getting errors.
I'm using the following uri
/storage/emulated/0/Android/data/com.example/files/Download/file2.zip
The fetch succeeds but returns a blob with size of 165 (i.e. not the actual size of the file) which hosts the error message:
{
"error": "Not Found",
"message": "The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again."
}
The program flow is like so:
I select a .zip file via FileChooser.
In onActivityResult, the uri value is /document/msf:12858 (seen via uri = intent.getData();)
The uri needs to be mapped into a real path file url, such that the fileUrl will be passed to webview.
Webview will then fetch the file using the fileUrl.
I searched how to get the real path file url when selecting a file with FileChooser, and found
this, and this links.
I wasn't able to get the real file path, so I decided to read the file and write it to another location, so I can get a file path. (this is not efficient and done just to check the functionality).
I create the new file using the following code:
InputStream stream = context.getContentResolver().openInputStream(uri);
File file2 = new File(context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "file2.zip");
writeBytesToFile(stream, file2);
I don't see any errors when creating the file, and when creating the file, the number of bytes that are read and written to the new file are as expected.
For file2, I get a value of:
/storage/emulated/0/Android/data/com.example/files/Download/file2.zip
Then, within the Javascript code I fetch this file path.
But I'm getting a Blob with the "file-not-found" content as above.
So:
How can I verify that the file is indeed created and that the path can be fetched from webview?
How can I get the real file path of the original selected file, so I don't have to read and write the original file to new location just to get the file path?
Thanks

I was able to get the file from external storage by doing the following steps:
create an initial uri (uri1)
The uri is created by:
creating a temporary file (file1) in the storage dir via
context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)
I'm not sure why a temporary file need to be created but if I don't create a file I cannot get the uri.
createFile3
get the uri via
Uri uri1 = FileProvider.getUriForFile(context, "com.example.android.fileprovider", file1);
create an intent with the following attributes:
Intent.ACTION_OPEN_DOCUMENT
category: Intent.CATEGORY_OPENABLE
type: "application/zip"
extra attribute: fileIntent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, uri1);
this opens a dialog box for selecting openable zip files in the Downloads directory,
after the file is selected, a new uri (uri2) is created that includes the name of the selected file.
extract the name of the file via
String fileName = getFileName(context, uri2);
create the dirPath by appending the filename
dirPath = "/data/user/0/com.example/" + fileName;
if the dirPath does not exist (first time), write the file to its dirPath location.
on successive ocassions dirPath exists, so there is no need to re-write the file.
open the file with regular Java means, e.g. via
ZipFile zip = new ZipFile(dirPath);

Related

Open PDF in new tab, saving file gives wrong file name

There is an Angular 6 app having a feature to download pdf files. It uses REST API from another Spring Boot application to download files.
The requirement is that i need to open pdf file in new tab and while saving it shall have a meaningful name.
I am able to get the file downloaded and open in new tab too. But while saving the file, it takes name from the url.
I tried by opening the file directly from REST API in browser and save it as well as getting blob and open in new window from angular app. Both open the files but while saving they take name from URL.
Angular - Open blob in new window
blob:https://defg.com/89a0396e-994b-43d0-b3e3-aaa95d47db9f
If i try to download opened file, it suggests to save with name
89a0396e-994b-43d0-b3e3-aaa95d47db9f.pdf
Is there a way to pass or set filename in below approach?
var fileURL = window.URL.createObjectURL(file);
window.open(fileURL, "_blank");
API - Takes Encrypted name
https://abcd.com/ws/fileDownload/85C1E5010AFAE309E89534FAC594C9110D74FED4DF78AAA8EE199C78B570FABF8BC6640F0563778DD963
If i try to download opened file, it suggests to save with correct name for first time, shows string from url as name from second attempt, 85C1E5010AFAE309E89534FAC594C9110D74FED4DF78AAA8EE199C78B570FABF8BC6640F0563778DD963.pdf
Inspected response, headers look good,
Content-Disposition: inline; filename= "test.pdf"
Any suggestions would be helpful. Thanks in advance.
You could use file-saver library as follow:
import { saveAs } from 'file-saver';
...
awesomeDownloader(url: string, fileName: string) {
const blobFile = functionsThatGetsTheBlob(url);
saveAs(blobFile, fileName);
}
...

Questions about extract.autodesk.io - taking a file path instead of choosing with the file chooser

I'm trying to modify the project so I could plug in a file path or a file as a variable instead of the user choosing the model file. So I'm looking for where the actual upload happens.
In submitProject():
https://github.com/cyrillef/extract.autodesk.io/blob/master/www/js/app.js#L129
I see that it just sends (with an ajax request) an object that holds the file name and unique identifier but not the actual binary file.
In here:
https://github.com/cyrillef/extract.autodesk.io/blob/master/www/js/upload-flow.js#L34
there's r.upload(), is this the actual upload of the model?
Does it start to upload the file right as you press ok in the file chooser?
Is there a way to give it a file path to upload instead of uploading with the form and file chooser?
The author of this sample should be on Christmas vacation, I just downloaded and setup the extractor sample on my machine, with a little debug into the code, let me try to answer as much as I can.
In general, I think some of your understanding is correct, but let me explain a little more:
For a local file to be uploaded and translated, there are actually 2 steps of actual “upload”.
As you mentioned, when you press ok in the file chooser, yes, the file will be first uploaded to the "extractor" server as you noticed by some methods like r.upload(), it’s actually using a JavaScript library call “flow.js", which provides multiple simultaneous, stable, fault-tolerant and resumable/restartable file uploads via the HTML5 File API. I am not expert on this, but you can check that module about how to use it to upload a file.
By now, your file is uploaded from client to the "extractor" server, but if you want to translate the file to "svf", the file is required to be uploaded to Autodesk Server(OSS), that is done by clicking “submit my project” buton, when you click this button, as you mentioned, from client, it will call the method submitProject() in https://github.com/cyrillef/extract.autodesk.io/blob/master/www/js/app.js, this method will send a post request of “/api/projects” to the "extractor" server, if you check the code at server side https://github.com/cyrillef/extract.autodesk.io/blob/master/server/projects.js , you can see the extractor server actually upload the file to Autodesk OSS, and then triggers the translation service.
This feature (passing a URL string vs a file binary) is already implemented. You can use the uri: edit box and paste your file URL there. It supports http(s) or S3 uri with access token.
The physical upload happens in this file, whereas the SubmitProject() code sends only information as JSON. The JSON object only contains a reference to the file which was uploaded using flow.js. But would contain the uri string if you had choose that method.

How to load the contents of a local text file by name using Javascript and HTML 5?

Working in Chrome, loading a local html or JS file.
I found many examples of how to load a file that is selected using the Choose File input.
However, didn't figure out how to do it given a file name without using the Choose File input.
The Choose File input returns a File object.
How to create the File object without the Choose File input?
From the File API:
new File(
Array parts,
String filename,
BlobPropertyBag properties
);
But didn't figure out what the parts and properties would be.
Edit: Use case:
I have code coverage results generated as part of a test suite. It is stored as JSON (which is easy to read), but I need to display it with the source code.
So the feature is to load the source code and JSON data, and render them together on a web page using HTML and Javascript.
The file would be opened from the browser and lives on the local machine. There is no server.
The browser cannot load arbitrary files by name from your filesystem without special extensions or other shenanigans. This is a security policy to prevent random web sites from reading files from your hard disk as you browse the internet.
If you're down to do something special like if you want to write a chrome app, you could get access to some nice APIs for accessing the filesystem:
https://developer.chrome.com/apps/fileSystem
The File constructor doesn't read a file from the harddrive, but rater make a virtual file, consider this:
var file = new File(["some", "content"], "/tmp/my-name.txt");
var reader = new FileReader();
reader.onload = function() {
console.log(reader.result); // somecontent
};
No file will be read or stored on the clients machine.
If you are talking about creating files in nodejs then you should take a look at fs.
For security reasons all browsers don't support predefined values on file fields so the answer is you can't.

HTML Object data URL from dynamic source

So all uploads for my app are not stored in my web server space, they are stored in a file system storage. When my users want access to the file they call a URL and the backend process will buffer the data to the browser via the HttpServletResponse outputstream. This works great as intended for downloading a file. Now my use-case has a scenario where I need to load an embedded object using this same method.
I am essentially loading a preview of the PDF file in the browser. This works fine if the PDF is stored on the web server and I provide a direct URL to the file. But when I use my method of sending files to the user then it doesn't work.
<object data='"+pdfUrl+"' type='application/pdf' width='160px' height='160px' />
If i put pdfURL into a browser my file gets downloaded no problem. So I think the issue is the HTTP headers I am sending in the outputstream that maybe is preventing the Object from loading properly. I am not sure if maybe its expecting something specific to be set in order to trigger loading the file
I am currently using very basic headers as follows:
BufferedInputStream is = <Some File Inputstream>;
resp.setContentType(new MimetypesFileTypeMap().getContentType(directory+filename));
resp.setHeader("Content-Disposition", "attachment; filename="+StringFormatHelper.formatFileName(filename));
bufferedCopy(is, resp.getOutputStream());
is.close();
resp.getOutputStream().flush();
Anyone have any ideas on what I have to change to get the data to properly load in the Object tag? I don't get any errors in the JS console or server side. I am not sure how to debug this issue.
Edit:
SO i just realized that if i right click on where the blank Object tag is at I have the option to "Save as..." and when I do I download the PDF. So the pdf data is loaded but Its just not displaying in the UI.
The issue is this line of code
resp.setContentType(new MimetypesFileTypeMap().getContentType(directory+filename));
This was not setting the correct mime-type for the file as I thought it was. So there was a mismatch in that the Object tag was looking for application/pdf but the server was sending a different MIME type in the header. Once I matched them up everything worked.
I was able to get the correct MIME type using the Spring provided lookup instead of the JDK lookup
new ConfigurableMimeFileTypeMap().getContentType(directory+filename)

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.

Categories