#ionic-native/file-transfer /file-opener not working I'm getting "open failed: EACCES (permission denied) error while trying to download a pdf and open in my ionic-angular project. This is the header files and function to open the attachment. This is working fine in android version 9 but not in android 10. What is the reason for this issue?
Header files
import { FileTransfer, FileTransferObject } from '#ionic-native/file-transfer';
import { File } from '#ionic-native/file';
import { FileOpener } from '#ionic-native/file-opener';
import { PhotoViewer } from '#ionic-native/photo-viewer';
Function call
constructor(public navCtrl: NavController, public navParams: NavParams,
...
private fileTransfer: FileTransfer,
private platform: Platform,
private file: File,
private fileOpener: FileOpener,
private photo: PhotoViewer,
...
) {...}
openAttachment(attachment) {
this.notification.getNotificationCount(this.userID).subscribe(res => this.setNotificationsCountAtStart(res));
this.loader.displayLoader();
const transfer: FileTransferObject = this.fileTransfer.create();
var filename = attachment.substring(attachment.lastIndexOf('/') + 1);
var filePath;
if (this.platform.is('ios')) {
filePath = this.file.documentsDirectory + filename;
} else if (this.platform.is('android')) {
filePath = this.file.externalRootDirectory + 'Download/' + filename;
}
if (attachment.indexOf('.pdf') > -1) {
transfer.download(this.baseurl + attachment, filePath, true).then((entry) => {
let url = entry.toURL();
this.fileOpener.open(url, 'application/pdf')
.then(() => {
console.log('File is opened');
this.loader.hideLoader();
})
.catch(e => console.log('Error opening file', JSON.stringify(e)))
}, (error) => {
// handle error
let toast = this.toast.create({
message: JSON.stringify(error),
duration: 3000,
position: 'bottom'
});
toast.present();
});
} else if (attachment.indexOf('.png') > -1) {
transfer.download(this.baseurl + attachment, filePath, true).then(entry => {
let url = entry.toURL();
this.loader.hideLoader();
this.photo.show(url, filename, {});
})
} else if (attachment.indexOf('.jpg') > -1) {
transfer.download(this.baseurl + attachment, filePath, true).then(entry => {
let url = entry.toURL();
this.loader.hideLoader();
this.photo.show(url, filename, {});
})
} else if (attachment.indexOf('.jpeg') > -1) {
transfer.download(this.baseurl + attachment, filePath, true).then(entry => {
let url = entry.toURL();
this.loader.hideLoader();
this.photo.show(url, filename, {});
})
} else {
this.loader.hideLoader();
}
}
I've changed the AndroidTargetSDK version to 28 in AndroidManifest file. Now its working. I'm running the app from AndroidStudio. So changing the value to 28 makes it working. Hope this works.
Related
I’m downloading a file, and I’ve expected open the file after finished downloading, but this does not work, it’s viewed on a black screen when opening using Linking, and I open this file in the folder where it is and it opens correctly. How can I fix this?
This is the URL of the file in STORAGE:
content://com.android.externalstorage.documents/tree/primary%3ADownload%2FSigaa/document/primary%3ADownload%2FSigaa%2Fhistory.pdf
My code:
const downloadPath = FileSystem.documentDirectory! + (Platform.OS == "android" ? "" : "");
const ensureDirAsync: any = async (dir: any, intermediates = true) => {
const props = await FileSystem.getInfoAsync(dir);
if (props.exists && props.isDirectory) {
return props;
}
let _ = await FileSystem.makeDirectoryAsync(dir, { intermediates });
return await ensureDirAsync(dir, intermediates);
};
const downloadFile = async (fileUrl) => {
if (Platform.OS == "android") {
const dir = ensureDirAsync(downloadPath);
}
let fileName = "history";
const downloadResumable = FileSystem.createDownloadResumable(
fileUrl,
downloadPath + fileName,
{}
);
console.log(downloadPath);
try {
const { uri } = await downloadResumable.downloadAsync();
saveAndroidFile(uri, fileName);
} catch (e) {
console.error("download error:", e);
}
};
const saveAndroidFile = async (fileUri, fileName = "File") => {
try {
const fileString = await FileSystem.readAsStringAsync(fileUri, { encoding: FileSystem.EncodingType.Base64 });
if (local === "") {
const permissions = await StorageAccessFramework.requestDirectoryPermissionsAsync();
if (!permissions.granted) {
return;
}
await AsyncStorage.setItem("local", permissions.directoryUri);
}
try {
await StorageAccessFramework.createFileAsync(local, fileName, "application/pdf")
.then(async (uri) => {
await FileSystem.writeAsStringAsync(uri, fileString, { encoding: FileSystem.EncodingType.Base64 });
Linking.openURL(uri);
alert("Success!");
})
.catch((e) => {});
} catch (e) {
throw new Error(e);
}
} catch (err) {}
};
The black screen when opening:
I solved this by implementing the intent launcher api for android and the sharing api for ios.
To make it work it is important to provide the mimeType of the file (or UTI for IOS). You can extract it manually from the file extension but there's a library for that on RN: react-native-mime-types.
Actually I think any library that doesn't deppend on node core apis shoud work just fine.
Here's the code:
const openFile = async (fileUri: string, type: string) => {
try {
if (Platform.OS === 'android') {
await startActivityAsync('android.intent.action.VIEW', {
data: fileUri,
flags: 1,
type,
});
} else {
await shareAsync(fileUri, {
UTI: type,
mimeType: type,
});
}
} catch (error) {
// do something with error
}
};
I am creating an app with cordova for android using plugin file-transfer. The download is going to a certain folder but I can't read the file despite indicating the correct path follows the code
var uri = encodeURI(mypage);
var fileURL = cordova.file.externalDataDirectory + "teste.ogg";
fileTransfer.download(
uri, fileURL, function(entry) {
console.log("download complete: " + entry.toURL());
$("#audio-teste").attr('src',fileURL)
/*--codigo de teste--*/
var meuFile = cordova.file.externalDataDirectory;
resolveLocalFileSystemURL(meuFile, function(entry) {
var readerN = fileSystem.createReader();
readerN.readEntries(
function (entry) {
var arrayN =[];
for(var i="0"; i < entry.length; ++i){
var entradaN = entry[i].name;
arrayN.push(entradaN);
//console.log(array);
console.log('teste aq' + arrayN);
}
}
)
//console.log(entry);
});
/*----*/
},
function(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 dGVzdHVzZXJuYW1lOnRlc3RwYXNzd29yZA=="
}
}
);
/*----*/
The cordova plugin cordova-plugin-file-transfer is due to be deprecated and its latest npm version fails on iOS.
Therefore these two working functions are purely based on vanilla JS, and thus no need to use extra plugins, besides the standard plugin cordova-plugin-file. Therefore this is compatible with any platform.
https://gist.github.com/jfoclpf/07e52f6bdf9c967449c4bc06af44c94a
I paste here for your convenience:
// for different types of cordovaFileSystem check here:
// https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-file/#where-to-store-files
// or simply type in the console `console.log(cordova.file)`
function downloadFileToDevice (fileurl, filename, cordovaFileSystem, callback) {
var onerror = (err) => {
console.error(`Error downloading from ${fileurl} to cordovaFileSystem ${cordovaFileSystem}`,
err, new Error(err))
if (typeof callback === 'function') { callback(Error(err)) }
}
var blob = null
var xhr = new XMLHttpRequest()
xhr.open('GET', fileurl)
xhr.responseType = 'blob' // force the HTTP response, response-type header to be blob
xhr.onload = () => {
blob = xhr.response // xhr.response is now a blob object
var DataBlob = blob
window.resolveLocalFileSystemURL(cordovaFileSystem, (dirEntry) => {
const sanitizedFilename = filename.replace(/[^a-z0-9\.]/gi, '_').toLowerCase() // sanitize filename
dirEntry.getFile(sanitizedFilename, { create: true }, (file) => {
file.createWriter((fileWriter) => {
fileWriter.write(DataBlob)
if (typeof callback === 'function') { callback(null, cordovaFileSystem + sanitizedFilename) }
}, (err) => { console.error('Error on file.createWriter'); onerror(err) })
}, (err) => { console.error('Error on dirEntry.getFile'); onerror(err) })
}, (err) => { console.error('Error on resolveLocalFileSystemURL'); onerror(err) })
}
xhr.onerror = (err) => { console.error('Error on XMLHttpRequest'); onerror(err) }
xhr.send()
}
An example for downloading a file
downloadFileToDevice('https://example.com/img.jpg',
'myImg.jpg',
cordova.file.cacheDirectory,
(err, localFilePath) => {
if (err) {
console.error('An error occured downloading file:', err)
} else {
console.log('Download file with success: ' + localFilePath)
}
})
Can you try with this? I tried and it works like a charm
My requirement is to upload the filename with special characters also. My code for android device is :
if (self.device.platform == 'Android') {
let permissions = cordova.plugins.permissions;
self.UserUtils.addPermission(permissions.READ_EXTERNAL_STORAGE).then((success) => {
self.fileChooser.open().then((path: any) => {
console.log(path);
console.log("fileChooser successCallback");
(window as any).FilePath.resolveNativePath(path, function (path: any) {
let a = path.split('/');
let fileName = a.pop();
//fileName = fileName.replace(/[&\/\\#,+()$~%#£=!-'":*?<>{}]/g,'_').replace(/_{2,}/g,'_');
let fileObj = self.fileService.getFileNameExt(fileName);
let onlyName = fileObj.onlyName;
let ext = fileObj.ext;
let p = a.join().replace(/,/g, "/");
p = p + "/";
console.log(p, fileName);
self.file.checkFile(p, fileName)
.then(function (suc) {
console.log(suc,"suc");
self.file.readAsBinaryString(p, fileName)
.then(function (success: any) {
console.log("readAsBinaryString sucess");
self._zone.run(() => {
self.attachments[id] = {
docName: onlyName,
docType: ext,
scoreDocument: btoa(success),
// scoreDocument: success,
delete: true
};
});
console.log("Attachment::",self.attachments[id]);
}).catch(function (error) {
console.log("readAsBinaryString fail", error);
});
}).catch(function (err) {
console.log("checkFile false");
console.log(err);
console.log(JSON.stringify(err));
});
}, self.failureCallback);
}).catch(e => console.log(e));
}, (err) => {
console.log(err);
})
}
I have tried the below scenarios:
a. Removed file check File function. With this there is no need to replace special characters but it does not work with Motorola devices. Resolve Native Path gives error.
b. If i replace special characters then check File function resolves to false.
I'm using the following code to download a file (can be a PDF or a DOC) and then opening it using Linking.
const { dirs } = RNFetchBlob.fs;
let config = {
fileCache : true,
appendExt : extension,
addAndroidDownloads : {
useDownloadManager : false,
notification : false,
title : 'File',
description : 'A file.',
path: `${dirs.DownloadDir}/file.${extension}`,
},
};
RNFetchBlob.config(config)
.fetch(
method,
remoteUrl,
APIHelpers.getDefaultHeaders()
)
.then((res) => {
let status = res.info().status;
if (status == 200) {
Linking.canOpenURL(res.path())
.then((supported) => {
if (!supported) {
alert('Can\'t handle url: ' + res.path());
} else {
Linking.openURL(res.path())
.catch((err) => alert('An error occurred while opening the file. ' + err));
}
})
.catch((err) => alert('The file cannot be opened. ' + err));
} else {
alert('File was not found.')
}
})
.catch((errorMessage, statusCode) => {
alert('There was some error while downloading the file. ' + errorMessage);
});
However, I'm getting the following error:
An error occurred while opening the file. Error: Unable to open URL:
file:///Users/abhishekpokhriyal/Library/Developer/CoreSimulator/Devices/3E2A9C16-0222-40A6-8C1C-EC174B6EE9E8/data/Containers/Data/Application/A37B9D69-583D-4DC8-94B2-0F4AF8272310/Documents/RNFetchBlob_tmp/RNFetchBlobTmp_o259xexg7axbwq3fh6f4.pdf
I need to implement the solution for both iOS and Android.
I think the easiest way to do so is by using react-native-file-viewer package.
It allows you to Prompt the user to choose an app to open the file with (if there are multiple installed apps that support the mimetype).
import FileViewer from 'react-native-file-viewer';
const path = // absolute-path-to-my-local-file.
FileViewer.open(path, { showOpenWithDialog: true })
.then(() => {
// success
})
.catch(error => {
// error
});
So, I finally did this by replacing Linking by the package react-native-file-viewer.
In my APIHelpers.js:
async getRemoteFile(filePath, extension, method = 'GET') {
const remoteUrl = `${API_BASE_URL}/${encodeURIComponent(filePath)}`;
const { dirs } = RNFetchBlob.fs;
let config = {
fileCache : true,
appendExt : extension,
addAndroidDownloads : {
useDownloadManager : false,
notification : false,
title : 'File',
description : 'A file.',
path: `${dirs.DownloadDir}/file.${extension}`,
},
};
return new Promise(async (next, error) => {
try {
let response = await RNFetchBlob.config(config)
.fetch(
method,
remoteUrl,
this.getDefaultHeaders()
);
next(response);
} catch (err) {
error(err);
}
});
}
In my Actions.js
export function openDocument(docPath, ext) {
return async (dispatch) => {
dispatch(fetchingFile());
APIHelpers.getRemoteFile(docPath, ext).then(async function(response) {
dispatch(successFetchingFile());
let status = response.info().status;
if (status == 200) {
const path = response.path();
setTimeout(() => {
FileViewer.open(path, {
showOpenWithDialog: true,
showAppsSuggestions: true,
})
.catch(error => {
dispatch(errorOpeningFile(error));
});
}, 100);
} else {
dispatch(invalidFile());
}
}).catch(function(err) {
dispatch(errorFetchingFile(err));
});
}
}
In my Screen.js
import { openDocument } from 'path/to/Actions';
render() {
return <Button
title={'View file'}
onPress={() => this.props.dispatchOpenDocument(doc.filepath, doc.extension)}
/>;
}
.
.
.
const mapDispatchToProps = {
dispatchOpenDocument: (docPath, ext) => openDocument(docPath, ext),
}
Are you downloading it from the web? I can see the pdf path is attached at the end of the error path.
For web URLs, the protocol ("http://", "https://") must be set accordingly!
Try to append appropriate schemes to your path. Check it out from the link mentioned below.
This can be done with 'rn-fetch-blob'
RNFetchBlob.android.actionViewIntent(fileLocation, mimeType);
i know this link: https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-file/#where-to-store-files
but i would like to save the file in Downloads directory. Is this possible to save the file in any path using Ionic? If so, please, share the example.
Here's the code:
downloadImage(image) {
this.platform.ready().then(() => {
const fileTransfer: TransferObject = this.transfer.create();
const imageLocation = `${cordova.file.applicationDirectory}www/assets/img/${image}`;
fileTransfer.download(imageLocation, cordova.file.externalDataDirectory + image).then((entry) => {
const alertSuccess = this.alertCtrl.create({
title: `Download Succeeded!`,
subTitle: `${image} was successfully downloaded to: ${entry.toURL()}`,
buttons: ['Ok']
});
alertSuccess.present();
}, (error) => {
const alertFailure = this.alertCtrl.create({
title: `Download Failed!`,
subTitle: `${image} was not successfully downloaded. Error code: ${error.code}`,
buttons: ['Ok']
});
alertFailure.present();
});
});
}
Basically I want save the file in location that is visible to the user.
the problem was lack of permission. Here is the working code that can download file to downloads directory:
async downloadFile() {
await this.fileTransfer.download("https://cdn.pixabay.com/photo/2017/01/06/23/21/soap-bubble-1959327_960_720.jpg", this.file.externalRootDirectory +
'/Download/' + "soap-bubble-1959327_960_720.jpg");
}
getPermission() {
this.androidPermissions.hasPermission(this.androidPermissions.PERMISSION.READ_EXTERNAL_STORAGE)
.then(status => {
if (status.hasPermission) {
this.downloadFile();
}
else {
this.androidPermissions.requestPermission(this.androidPermissions.PERMISSION.READ_EXTERNAL_STORAGE)
.then(status => {
if(status.hasPermission) {
this.downloadFile();
}
});
}
});
}
To download the File to the Download directory you need to use Cordova File and FileTransfer Plugins.
import { File } from '#ionic-native/file';
import { FileTransfer } from '#ionic-native/file-transfer';
constructor(private transfer: FileTransfer) { }
fileTransfer: FileTransferObject = this.transfer.create();
//Use your File Url and name
downloadFile(file) {
// Some Loading
this.fileTransfer.download(url, this.file.externalRootDirectory +
'/Download/' + file).then(response => {
console.log(response);
this.dismissLoading();
this.presentToast('File has been downloaded to the Downloads folder. View
it..')
})
.catch(err => {
this.dismissLoading();
console.log(err)
});
}
Hope it helps.
import { File } from '#ionic-native/file';
import { FileTransfer } from '#ionic-native/file-transfer';
constructor(private file: File, private transfer: FileTransfer){}
let link = 'url_to_download_file';
let path = '';
let dir_name = 'Download'; // directory to download - you can also create new directory
let file_name = 'file.txt'; //any file name you like
const fileTransfer: FileTransferObject = this.transfer.create();
let result = this.file.createDir(this.file.externalRootDirectory, dir_name, true);
result.then((resp) => {
path = resp.toURL();
console.log(path);
fileTransfer.download(link, path + file_name).then((entry) => {
console.log('download complete: ' + entry.toURL());
}, (error) => {
console.log(error)
});
}, (err) => {
console.log('error on creating path : ' + err);
});
I know this is late, but I've always had issues with the FileTransfer plugin. Maybe it is just me. I've instead had success with the writeFile() method of the File plugin.
I'm still working on iOS, but for Android here is what I have:
import { File } from "#ionic-native/file";
constructor(private fileSystem: File) {}
Then, in whatever function you have the logic to save the file, we have:
let path = this.fileSystem.externalRootDirectory + '/Download/'; // for Android
let filename = 'myNewFile.pdf';
this.fileSystem.writeFile(path, filename, File, { replace: true }).then(() => {
this.toastCtrl.showToast('File has been downloaded. Please check your downloads folder.');
}, (err) => {
alert("Sorry. An error occurred downloading the file: " + err);
}
);
As I said, I'm still looking out for what path to use for iOS. And I'm still wondering how to pop up the notification that usually comes up when a download actually goes to the download folder. But at least I am able to save directly in the download folder of Android.
This code - ionic 3 capacitor - from josh morony takes a photo from the tmp directory and writes to the Document directory in this section using the FileSystem API the retrieves and manipulates the path
Filesystem.writeFile({
data: result.data,
path: fileName,
directory: FilesystemDirectory.Data
})
getFromPhotos() {
let options = {
resultType: CameraResultType.Uri
};
Camera.getPhoto(options).then(
(photo) => {
Filesystem.readFile({
path: photo.path
}).then((result) => {
// let date = new Date(),
// time = date.getTime(),
time = 'bilder',
fileName = time + '.jpeg';
Filesystem.writeFile({
data: result.data,
path: fileName,
directory: FilesystemDirectory.Data
}).then((result) => {
Filesystem.getUri({
directory: FilesystemDirectory.Data,
path: fileName
}).then((result) => {
console.log(result);
let path = result.uri.replace('file://', '_capacitor_');
this.image = this.sanitizer.bypassSecurityTrustResourceUrl(path);
}, (err) => {
console.log(err);
});
}, (err) => {
console.log(err);
});
}, (err) => {
console.log(err);
});
}, (err) => {
console.log(err);
}
);
}
In ionic 3 you have to use the cordova File plugin - please google. It is pretty straight forward to understand: you define the original directory where the file is, the original name of the file, the target directory, and a new name for the file inside that function. The principle is the same.
To download the File to the Download directory you need to use Cordova File Plugin:
import { File } from '#ionic-native/file/ngx';
constructor(
private file: File,
) { }
this.file.writeFile(this.file.externalRootDirectory + '/Download/', user_log.xlsx, blob, { replace: true })
.then(() => {
alert('File has been downloaded. Please check your downloads folder.')
enter code here
},
(err) => {
alert("Sorry. An error occurred downloading the file: " + err);
enter code here
});
})
It works in Ionic 4 as well.