PhoneGap FileTransfer.download expects different path than FileSystem provides - android

I am downloading a file to the local file system. I can successfully create the empty file via fileSystem.root.getFile but fileTransfer.download fails with FILE_NOT_FOUND_ERR even though it's using the same path.
The problem is that my file is being created at //sdcard/MyDir/test.pdf (I confirmed using adb shell) but the fileEntry returned a path without sdcard: //MyDir/test.pdf. fileTransfer.download fails with this path. It also fails with the relative path MyDir/test.pdf.
If I hardcode the full path with 'sdcard' in it I can avoid the FILE_NOT_FOUND_ERR (specifically, in FileTransfer.java the resourceApi.mapUriToFile call succeeds) but then I get a CONNECTION_ERR and the console shows "File plugin cannot represent download path". (In FileTransfer.java, the filePlugin.getEntryForFile call returns null. I assume it doesn't like 'sdcard' in the path.)
Is there a better way to specify the target path in fileTransfer.download?
var downloadUrl = "http://mysite/test.pdf";
var relativeFilePath = "MyDir/test.pdf"; // using an absolute path also does not work
window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function (fileSystem) {
fileSystem.root.getFile(relativeFilePath, { create: true }, function (fileEntry) {
console.log(fileEntry.fullPath); // outputs: "//MyDir/test.pdf"
var fileTransfer = new FileTransfer();
fileTransfer.download(
downloadUrl,
/********************************************************/
/* THE PROBLEM IS HERE */
/* These paths fail with FILE_NOT_FOUND_ERR */
//fileEntry.fullPath, // this path fails. it's "//MyDir/test.pdf"
//relativeFilePath, // this path fails. it's "MyDir/test.pdf"
/* This path gets past the FILE_NOT_FOUND_ERR but generates a CONNECTION_ERR */
"//sdcard/MyDir/test.pdf"
/********************************************************/
function (entry) {
console.log("Success");
},
function (error) {
console.log("Error during download. Code = " + error.code);
}
);
});
});
I'm using the Android SDK emulator if that makes a difference.

I was able to resolve this by using a URI for the file's path. (I also removed the unnecessary call to fileSystem.root.getFile per #Regent's comment.)
var downloadUrl = "http://mysite/test.pdf";
var relativeFilePath = "MyDir/test.pdf"; // using an absolute path also does not work
window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function (fileSystem) {
var fileTransfer = new FileTransfer();
fileTransfer.download(
downloadUrl,
// The correct path!
fileSystem.root.toURL() + '/' + relativeFilePath,
function (entry) {
console.log("Success");
},
function (error) {
console.log("Error during download. Code = " + error.code);
}
);
});

Related

Cordova: How to move file to the Download folder?

THE SITUATION:
In my mobile app I need to download a file and store in the Download folder.
The download part is working fine.
The file is properly downloaded from the server and stored in the following folder:
file:///storage/emulated/0/Android/data/org.cordova.MY_APP_NAME.app/my_file.pdf
But the location is not really user-friendly.
To access it I have to go to: Internal storage / Android / data / org.cordova.MY_APP_NAME.app /
So I need to move it to the main Download folder.
The file transfer is what I don't manage to do.
I know that there are already several similar questions on SO.
I have tried them all but none really worked for me, I could never see the file in the actual Download folder.
PROJECT INFO:
I am using Quasar with Vuejs and Cordova.
PLATFORM:
For the moment I am working with Android. But ideally I am looking for a solution that works for both Android and IOS.
THE CODE:
The download code:
var fileTransfer = new FileTransfer() // eslint-disable-line
var uri = encodeURI('https://MY_SERVER_PATH')
fileTransfer.download(
uri,
cordova.file.externalApplicationStorageDirectory + 'my_file.pdf',
entry => {
console.log('download complete: ' + entry.toURL())
this.moveFile(entry.toURL())
},
error => {
console.log('download error source ' + error.source)
console.log('download error target ' + error.target)
console.log('download error code' + error.code)
},
false,
{
headers: {
'Authorization': 'Basic asdasdasdasdassdasdasd'
}
}
)
The File transfer code:
moveFile(fileUri) {
window.resolveLocalFileSystemURL(
fileUri,
fileEntry => {
let newFileUri = 'file:///storage/emulated/0/Download'
window.resolveLocalFileSystemURL(
newFileUri,
dirEntry => {
fileEntry.moveTo(dirEntry, 'new_filename.pdf', this.moveFileSuccess, this.moveFileError)
},
this.moveFileError)
},
this.moveFileError)
},
moveFileSuccess(entry) {
console.log('file move success')
console.log(entry)
},
moveFileError(error) {
console.log('file move error')
console.log(error)
}
THE QUESTION:
How can I move a file to the Download folder?
Thanks
EDIT:
This is the console log of the cordova.file object:
applicationDirectory: "file:///android_asset/"
applicationStorageDirectory: "file:///data/user/0/org.cordova.MY_APP_NAME.app/"
cacheDirectory:"file:///data/user/0/org.cordova.MY_APP_NAME.app/cache/"
dataDirectory: "file:///data/user/0/org.cordova.MY_APP_NAME.app/files/"
documentsDirectory: null
externalApplicationStorageDirectory: "file:///storage/emulated/0/Android/data/org.cordova.MY_APP_NAME.app/"
externalCacheDirectory: "file:///storage/emulated/0/Android/data/org.cordova.MY_APP_NAME.app/cache/"
externalDataDirectory: "file:///storage/emulated/0/Android/data/org.cordova.MY_APP_NAME.app/files/"
externalRootDirectory: "file:///storage/emulated/0/"
sharedDirectory: null
syncedDataDirectory: null
tempDirectory: null
Okay I managed to resolve it.
First of all is totally unnecessary to download and then move the file. It can just be directly downloaded in the desired direction.
The correct path (in my case) was this:
cordova.file.externalRootDirectory + 'download/' + 'my_file.pdf
that correspond to: file:///storage/emulated/0/download/my_file.pdf
and that means that to find the file inside the device you have to go to: Internal Storage / Download / my_file.pdf
Add the following value in the config.xml:
<preference name="AndroidPersistentFileLocation" value="Compatibility" />
<preference name="AndroidExtraFilesystems" value="files,files-external,documents,sdcard,root" />
It's important to check for permission using this cordova plugin: cordova-plugin-android-permissions
You can make a quick test like this:
let permissions = cordova.plugins.permissions
permissions.checkPermission(permissions.READ_EXTERNAL_STORAGE, checkPermissionCallback, null)
function checkPermissionCallback(status) {
console.log('checking permissions')
console.log(status)
}
Most probably the result is false. And that means that we have to request permission to the user:
permissions.requestPermission(successCallback, errorCallback, permission)
In this way it will appear the alert asking for permission.
THE CODE:
To put it all together, this is the working code:
let pdfPath = 'https://MY_SERVER_PATH'
let permissions = cordova.plugins.permissions
permissions.checkPermission(permissions.READ_EXTERNAL_STORAGE, checkPermissionCallback, null)
// Checking for permissions
function checkPermissionCallback(status) {
console.log('checking permissions')
console.log(status)
if (!status.hasPermission) {
var errorCallback = function () {
console.warn('Storage permission is not turned on')
}
// Asking permission to the user
permissions.requestPermission(
permissions.READ_EXTERNAL_STORAGE,
function (status) {
if (!status.hasPermission) {
errorCallback()
} else {
// proceed with downloading
downloadFile()
}
},
errorCallback)
} else {
downloadFile()
}
}
function downloadFile() {
let filePath = cordova.file.externalRootDirectory + 'download/' + 'my_file.pdf'
let fileTransfer = new window.FileTransfer()
let uri = encodeURI(decodeURIComponent(pdfPath))
// Downloading the file
fileTransfer.download(uri, filePath,
function (entry) {
console.log('Successfully downloaded file, full path is ' + entry.fullPath)
console.log(entry)
},
function (error) {
console.log('error')
console.log(error)
},
false
)
}
var fileTransfer = new FileTransfer() // eslint-disable-line
var uri = encodeURI('https://MY_SERVER_PATH')
var fileURL = "///storage/emulated/0/Download";
fileTransfer.download(
uri,
fileURL+ 'your_file.pdf',
entry => {
console.log('download complete: ' + entry.toURL())
this.moveFile(entry.toURL())
},
error => {
console.log('download error source ' + error.source)
console.log('download error target ' + error.target)
console.log('download error code' + error.code)
},
false,
{
headers: {
'Authorization': 'Basic asdasdasdasdassdasdasd'
}
}
)
you can directly save downloaded file to your path
Try above code and let me know if its work.
cordova.file.externalApplicationStorageDirectory.
For that path you do not need to request any permission in manifest or require any permission at all.
But for others like external storage and so you need them.
You are #1244 with this problem this year.
Google for runtime permissions.
You can than directly download to the Download directory.

Cordova - Save a file to storage

I am making a Cordova application from which I need to export a file. I would like to save the file to the Android device's storage: /storage/emulated/0/. The app should create a folder in which it will create a file with content in it.
I tried the cordova-plugin-file plugin but I'm not sure how to use it. There are examples on the plugin's documentation but I don't know which one to use, there is:
Create a persistent file
Write to a file
Append a file using alternative methods
And I tried them all however none of them works.
Your help and an example (if possible) would be greatly appreciated.
EDIT
There's the code I used. I'm not getting any error.
function createFile(dirEntry, fileName, fileContent, isAppend) {
dirEntry.getFile(fileName, {create: true, exclusive: false}, function(fileEntry) {
writeFile(fileEntry, fileContent, isAppend);
}, fail);
}
function savePasswords(fileSystem) {
createFile("/sdcard/testFolder", "testfile.txt", "TEST", true);
}
function fail(error) {
alert("ERROR: " + error.code);
}
function request() {
window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, savePasswords, fail);
}
document.addEventListener("deviceready", request, false);
I want this to create the file "testfile.txt" with content "TEST" in a folder named "testFolder".
This script works:
function writeFile(fileEntry, dataObj) {
// Create a FileWriter object for our FileEntry (log.txt).
fileEntry.createWriter(function (fileWriter) {
fileWriter.onwriteend = function() {
console.log("Successful file write...");
readFile(fileEntry);
};
fileWriter.onerror = function (e) {
console.log("Failed file write: " + e.toString());
};
// If data object is not passed in,
// create a new Blob instead.
if (!dataObj) {
dataObj = new Blob(["Content if there's nothing!"], { type: 'text/plain' });
}
fileWriter.write(dataObj);
});
}
window.resolveLocalFileSystemURL(cordova.file.externalRootDirectory, function (rootDirEntry) {
rootDirEntry.getDirectory(fileDir, { create: true }, function (dirEntry) {
var isAppend = true;
dirEntry.getFile(fileName, { create: true }, function (fileEntry) {
writeFile(fileEntry, "Content!", isAppend);
// Success
});
});
});
Since Android 4.4, the SD card root (/sdcard/) is read-only so you cannot write to it. Assuming your reference to writeFile() in your example code refers to the cordova-plugin-file example (since it's not defined in your code), then the fileWriter.onerror() function would be invoked with error code NO_MODIFICATION_ALLOWED_ERR.
You must write to the application storage directory on the SD card (e.g. /sdcard/Android/data/your.app.package.id/).
You can reference this location using cordova-plugin-file as cordova.file.externalApplicationStorageDirectory.
See this answer for details of SD card access in different versions of Android.
Note: above references to "SD card" refer to the emulated SD card (on internal memory (i.e. /storage/emulated/0/). Referencing the external/removable SD card present in some Android devices (e.g. Samsung Galaxy S range) is not possible via cordova-plugin-file, however you can use getExternalSdCardDetails() from cordova-diagnostic-plugin to do so.

Cordova 6.3.1 download PDF and open with FileSystem, FileTransfer and FileOpener2

Im banging my head against my desk because I dont seem to find any answer that works.
I want to download a PDF to the local storage of an android device, and then open it in an external reader as Android is not able to display PDF in the browser. For iOS I simply use the InAppBrowser plugin which works nice btw. Im using cordova 6.3.1.
So, this is my code:
if (cordova.platformId === "android") {
var remoteFile = url;
var localFileName = "tmp.pdf";
window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function(fileSystem) {
//var fileSystemRoot = cordova.file.dataDirectory; Does not work...
var fileSystemRoot = fileSystem.root.toURL()
console.log(cordova.file.dataDirectory);
var ft = new FileTransfer();
ft.download(remoteFile,
fileSystemRoot + "tmp.pdf", function(entry) {
cordova.plugins.fileOpener2.open(
entry.toURL(),
'application/pdf',
{
error : function(e) {
console.log('Error status: ' + e.status + ' - Error message: ' + e.message + ' - URL: ' + messageObj.url);
},
success : function () {
console.log('file opened successfully');
console.log(fileSystemRoot);
console.log(entry.toURL());
}
}
);
}, function(error) {
console.log("Error in downloading");
console.log(error);
});
}, function(error) {
console.log("Error in requesting filesystem");
console.log(error);
});
}
I have tried tons of different things. fileSystem.root.fullpath, fileSystem.root.toURL(), fileSystem.root.nativeURL but I always end up with a path that does not seem to correspond with the device. I always get the success message that the download worked, adobe reader pops up but says the file is not readable. Not a surprise to me as the path it gives me is something like:
file:///data/data/ch.novalogix.novalib/files/files
That can simply not be true? I searched the whole system for the uploaded file but I dont think its downloaded. I guess I always get a wrong path...
Any ideas?
Thanks in advance!
I used fixed paths instead of the localFileSystem.

How to download base-64 source images in mobile device(Android,iPhone,Windows) and save into device phone memory

I have created one sample app using telerik app builder. I have created one simple view and one js file, render one image in view and convert it into base-64. I have requirement to download this image and save it to device internal storage using javascript
I have used download.js plugin of javascript but download functionality is not working.Please give suggestion.
My Code is below:
function (image)
{
var imageData = image.src;
imageData = imageData.replace('data:image/png;base64,', '');
download(imageData, "image11111", "image/png");
}
I found my own question's answer.
You can download image in mobile device using cordova filetransfer.
function download()
{
var filepath = encodeURI("http://www.telerik.com/sfimages/default-source/logos/app_builder.png"),
window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function (fileSystem) {
fileSystem.root.getFile("sample.jpg", { create: true, exclusive: false }, function (fileEntry) {
// get the full path to the newly created file on the device
var localPath = fileEntry.fullPath;
// massage the path for android devices (not tested)
if (device.platform === "Android" && localPath.indexOf("file://") === 0) {
localPath = localPath.substring(7);
}
// download the remote file and save it
var remoteFile = filepath;
//loadingOverlay.displayLoading("Image will be save on your device.");
var fileTransfer = new FileTransfer();
fileTransfer.download(remoteFile, localPath, function (newFileEntry) {
// successful download, continue to the next image
var dwnldImagePath = newFileEntry.fullPath;
console.log('successful download');
},
function (error) { // error callback for #download
console.log('Error with #download method.', error);
});
});
function(error) { // error callback for #getFile
console.log('Error with #getFile method.', error);
});
})
}

phonegap iOS file.path

I have a app using cordova 3.4 and file 1.1.0.
If I copy a image using the camera-modul, I use
myFileObj.path = file.toNativeURL()
to get the file-path. If I put this path into a img-tag I get shown the picture on Android.
On iOS it doesn't work. The result of file.toNativeURL():
myFileObj.path -> file:///Users/.../Library/Application%20Support/..../myFolder/myImage.JPG
Using file 1.0 I had to build the url and it looked like this:
myFileObj.path = dirTarget.toURL() + '/' + targetFileName
myFileObj.path -> cdvfile://localhost/persisten/myFolder/myImage.JPG
Where videos and audios didn't work, but at least pictures.
Using file 1.1.0/1.1.1 the result of this method is different too:
myFileObj.path -> file:///Users/mak/Library/.../myFolder/myImage.JPG?id=.....&ext=JPG
This doesn't work on iOS either.
How can I get a working file-path by using cordova file-module version 1.1.0 and 1.1.1?
EDIT: What am I doing and what doesn't work:
I copy images from the media-library and put it into a folder I create myself.
What works in Android and doesn't work in iOS:
In Android the media-tags src attribute is able to display the resource, iOS can't find a resource at the src-path.
catching a file from the media-library:
navigator.camera.getPicture(onSuccess, onFail, {
destinationType: Camera.DestinationType.NATIVE_URI,
sourceType : Camera.PictureSourceType.PHOTOLIBRARY,
mediaType: Camera.MediaType.ALLMEDIA
});
success-callback:
function onSuccess(imageData) {
A.StoreFile(imageData, id);
}
create a folder and store file:
A.StoreFile = function(file, idBox) {
var targetDirectory = Config.getRootPath();
window.resolveLocalFileSystemURL(file, resolveFileSystemSuccess, resolveFileSystemError);
function resolveFileSystemSuccess(fileEntry) {
fileEntry.file(function(filee) {
mimeType = filee.type;
getFileSuccess(fileEntry, mimeType);
}, function() {
});
}
function getFileSuccess(fileEntry, mimeType) {
var targetFileName = name + '.' + fileNativeType;
var parentName = targetDirectory.substring(targetDirectory.lastIndexOf('/')+1),
parentEntry = new DirectoryEntry(parentName, targetDirectory);
window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function(fileSystem) {
fileSystem.root.getDirectory(targetDirectory, {create: true, exclusive: false}, function(dirTarget) {
fileEntry.copyTo(dirTarget, targetFileName, function(entry) {
addFileToLocalStorage(entry);
}, function() {
})
})
}, resolveFileSystemError);
}
store file-information to a localStorageObject
function addFileToLocalStorage(file) {
fileList.addFile(
{
name:file.name,
internalURL: file.toNativeURL()
});
}
adding files dynamically to the dom:
myElement.find('.myMimeTypeTag').attr('src', fileList[f].internalURL);
This works with android, and not with iOS.
iOS-result of img-container:
Error-message:
DEPRECATED: Update your code to use 'toURL'
toURL doesn't work either
id="org.apache.cordova.file"
version="1.1.1-dev"
I've just tested this out, with a slightly simplified version of your code (you seem to have a lot of extra structure to your code that isn't shown, but if there's a significant difference between what I've done here and what your app does, then let me know. The problem will be in the difference.)
I've run this with Cordova 3.5.0, just released, using File 1.1.0 and Camera 0.2.9.
To create the app, I used the cordova command line tool, and just ran
cordova create so23801369 com.example.so23801369 so23801369
cd so23801369
cordova platform add ios
cordova plugin add org.apache.cordova.file
cordova plugin add org.apache.cordova.camera
This creates a default "Hello, Cordova" app, to which I've added some code that (I believe) replicates what your code does.
I added two lines to index.html:
<button id="doit">Do it</button>
<img class="myMimeTypeTag" src="file:///nothing" />
And I edited www/js/index.js to look like this:
var app = {
initialize: function() {
// Don't activate the button until Cordova is initialized
document.addEventListener('deviceready', this.onDeviceReady, false);
},
onDeviceReady: function() {
document.getElementById('doit').addEventListener('click', app.runTest, false);
},
runTest: function(ev) {
var StoreFile = function(file) {
var targetDirectory = "myFolder";
window.resolveLocalFileSystemURL(file, resolveFileSystemSuccess, resolveFileSystemError);
function resolveFileSystemSuccess(fileEntry) {
console.log("resolveLocalFileSystemURL returned: ", fileEntry.toURL());
fileEntry.file(function(filee) {
mimeType = filee.type;
getFileSuccess(fileEntry, mimeType);
}, function() {
});
}
function resolveFileSystemError() {
console.log("resolveFileSystemError: FAIL");
console.log(arguments);
alert("FAIL");
}
function getFileSuccess(fileEntry, mimeType) {
var targetFileName = "myImage.JPG";
window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, function(fileSystem) {
fileSystem.root.getDirectory(targetDirectory, {create: true, exclusive: false}, function(dirTarget) {
fileEntry.copyTo(dirTarget, targetFileName, function(entry) {
console.log("copyTo returned: ", entry.toURL());
// I have replaced the localstorage handling with this code
// addFileToLocalStorage(entry);
var img = document.querySelector('.myMimeTypeTag');
img.setAttribute('src', entry.toNativeURL());
}, function() {
});
});
}, resolveFileSystemError);
}
};
var onSuccess = function(imageData) {
console.log("getPicture returned: ", imageData);
StoreFile(imageData);
};
var onFail = function() {
console.log("getPicture FAIL");
console.log(arguments);
alert("FAIL");
};
ev.preventDefault();
ev.stopPropagation();
navigator.camera.getPicture(onSuccess, onFail, {
destinationType: Camera.DestinationType.NATIVE_URI,
sourceType : Camera.PictureSourceType.PHOTOLIBRARY,
mediaType: Camera.MediaType.ALLMEDIA
});
}
};
When I run this, I can pick an image from the media library, and it successfully displays it in the page, with the image src set to the URL of the copied image file. If I connect Safari dev tools to the iPad, I see this console output:
[Log] getPicture returned: assets-library://asset/asset.JPG?id=F9B8C942-367E-433A-9A71-40C5F2806A74&ext=JPG (index.js, line 49)
[Log] resolveLocalFileSystemURL returned: cdvfile://localhost/assets-library/asset/asset.JPG?id=F9B8C942-367E-433A-9A71-40C5F2806A74&ext=JPG (index.js, line 18)
[Log] copyTo returned: file:///var/mobile/Applications/9C838867-30BE-4703-945F-C9DD48AB4D64/Documents/myFolder/myImage.JPG (index.js, line 36)
[Log] DEPRECATED: Update your code to use 'toURL' (Entry.js, line 202)
This shows the camera and file plugins going through three different types of URL:
Camera returns an assets-library:// URL, with query parameters to identify the asset
calling resolveLocalFileSystemURL on that turns it into a cdvfile:// URL, also with query paramaters, as an internal Cordova representation.
After being copied, File returns a new file:/// URL showing the image's new place on the filesystem. This URL has no query parameters. (Calling toNativeURL() on this entry returns the same URL now, as of File 1.1.0)
This final URL is usable by iOS as an image source, and so that's what gets assigned to the <img> element.
The problem was, that the new file-plugin has to be case-sensitve.
I created a folder with capital letters and refered the copy-instruction to a folder with lowercase letters. The point is that android is case insensitive and ios is not.
This came out with the new file-plugin where the output was case sensitive.

Categories

Resources