I have an NativeScript 6.8 Javascript app that downloads newer data files. I'm discovering that on iOS I cannot create files within the app folder. (At least, in release builds; in debug builds I can.) I can change my code to read data files from the Documents folder, but how can I pre-populate the Documents folder at build time with the original data files? I'd rather not copy all the data files at run time.
Or, have I misinterpreted the restriction that files cannot be created in the app folder (or subfolders) in iOS release builds?
Live-updating files on iOS is more involved than one might expect. So, yes, you need to access the live-updated files from the Documents folder, not back the files up to iCloud, and handle numerous timing conditions, such as the live-update running just before what would seem to be the initial copy of the file to the Documents folder (seems unlikely, but I've seen it happen while testing).
I've included the function I developed, below. For context, when I find a file online to be live-updated, I use an appSetting to save the file's date as a string (storing as a value loses precision).
The function isn't perfect, but for now, it gets the job done. I call this from app.js in a NativeScript 6.8 JavaScript project.
/**
* Copy files in app/files folder to Documents folder so they can be updated
*/
async function copyFilesToDocuments() {
let filesCopied = appSettings.getBoolean("copyFilesToDocuments", false);
if (!filesCopied) { // only copy files on first invocation
let filesFolder = fs.knownFolders.currentApp().getFolder("files"); // Folder object
let documentsFolder = fs.knownFolders.documents(); // Folder object
let fileEntities = await filesFolder.getEntities();
for (entity of fileEntities) {
let sourceFile = fs.File.fromPath(entity.path);
let targetFilePath = fs.path.join(documentsFolder.path, entity.name);
let targetDate = parseInt(appSettings.getString(entity.name, "0"), 10); // live-update date or 0
if (fs.Folder.exists(targetFilePath) && targetDate > global.dataDate ) { // if file has been live-updated
console.log("app.js copyFilesToDocuments: file '" + entity.name + "' skipped to avoid overwrite. ");
continue; // don't overwrite newer file
}
appSettings.remove(entity.name); // remove any live-update timestamp
let targetFile = fs.File.fromPath(targetFilePath);
let content = await sourceFile.read();
try {
await targetFile.write(content);
if (platform.isIOS) {
// Prevent file from being backed up to iCloud
// See https://stackoverflow.com/questions/58363089/using-nsurlisexcludedfrombackupkey-in-nativescript
// See https://stackoverflow.com/questions/26080120/cfurlcopyresourcepropertyforkey-failed-because-passed-url-no-scheme
NSURL.fileURLWithPath(targetFilePath).setResourceValueForKeyError(true, NSURLIsExcludedFromBackupKey);
}
// console.log("app.js copyFilesToDocuments file copied: " + entity.name);
} catch(e) {
console.warn("app.js copyFilesToDocuments error: " + e);
//TODO: app will fail at this point with some files not found :-(
} // end catch
} // end for
appSettings.setBoolean("copyFilesToDocuments", true);
} // end files not yet copied
} // end copyFilesToDocuments
Related
My google drive storage is running out of space. So, in order to free some space, I created a new google account and transferred the ownership of one of the largest folders in my original drive (~7 GB) to the new account's drive.
The problem is, that after 3 days of waiting, the folder is still consuming the storage of the original account's drive. I made sure that the new account is the owner of the folder, but the problem is still there.
Any ideas?
The problem is that changing the ownership of folder inside google drive does not change it for the subfolders and files, you have to change them separately or write an app script to search for file and subfolders owned by you and change the ownership.
Run the script below as your account, if you are using Google Workspace and you are the admin and need to do it to other accounts, then you need to get a service account first and run the code below.
Note:
This way makes a lot of noise with notifications on both sides, the old owner and the new one.
function ChangeFileOwnership(folder){
// New owner
var new_owner = '<Add new owner email>';
if (folder == null) {
var folderObj = DriveApp.getFolderById('<ID of parent folder>');
return ChangeFileOwnership(folderObj);
}
var fileIt = folder.searchFiles('"me" in owners');
while (fileIt.hasNext()) {
var file = fileIt.next();
Logger.log("Changing ownership of " + file.getName() + " to " + new_owner);
// Set the owner to be the new owner
try {
file.addEditor(new_owner);
}
catch(err){
Logger.log(err.message);
}
try {
file.setOwner(new_owner);
}
catch(err){
Logger.log(err.message);
}
}
// Get all the sub-folders and iterate
var folderIt = folder.getFolders();
while (folderIt.hasNext()) {
fs = ChangeFileOwnership(folderIt.next());
}
}
Hi I have piece of gulp snippet that works great and copies entire folders when the source and destination folders are on the same drive. If I try to run it to copy files from another drive say D:/path/to/project with an absolute path to the folder, the code runs without any errors but no files are copied.
`So the path = '../../myproject/' works while 'd:/path/to/Project' does not.
Any idea how this can be achieved in gulp?
Thanks.
UPDATED WITH CODE
Of the two paths, the first one, targeting folder in drive f, fails to copy while the 2nd one does so successfully. Has to be tried one at a time, commenting the other.
subB = 'src/webtest'
subC = 'pro_miles/eb_aws'
var pathsToProj = [
['f:/pro_miles/eb_aws/**/*.*', '/src/'+subC, 'src/'+subC]
// ['../../pro_miles/eb_aws/**/*.*', '/src/'+subC, 'src/'+subC]
];
`// gulp.task('copyFiles', function (cb) {`
tasks = function copyFiles(cb) {
var paths = new Array();
for (const pathToProj of pathsToProj) {
paths.push(gulp.src(pathToProj[0], {base: pathToProj[1]})
.pipe(gulp.dest(pathToProj[2])));
};
cb();
return paths
};
gulp.task('default', gulp.series(tasks) );
I need to create PDF-file (done) and save it to Files application, so user can access it anytime outside of my app. I tried rn-fetch-blob and react-native-fs packages and they worked fine for Android, but for iOS they can only create files to internal app storage (so files are not created in external storage, i.e. not in iOS Files application).
What options do I have to save my created files to Files application? I know that it is possible, i.g. Slack app allows saving files to Files app.
You have to either add the UISupportsDocumentBrowser key and set to true, or both the UIFileSharingEnabled and LSSupportsOpeningDocumentsInPlace keys in your Info.plist file
When the user will open a document from your app's Documents directory, through Files app, they would be editing the document in place. The changes are saved to your app's Documents directory.
Refer this for more.
you can try with react-native-fs to save and download in local directory.
var url = path
let fileName = path.split("/").pop()
// console.log('fileName', fileName)
var ext = this.extention(url);
ext = "." + ext[0];
const localFile = ${RNFS.LibraryDirectoryPath}/${fileName};
// console.log('localFile', localFile)
const options = {
fromUrl: path,
toFile: localFile,
fileCache: true
};
if (await RNFS.exists(localFile)) {
FileViewer.open(localFile, { displayName: this.props.route.params.conversationSubject });
} else {
await RNFS.downloadFile(options).promise
.then((res) => {
// console.log('res', res)
FileViewer.open(localFile);
})
.then((res) => {
// success
// console.log("success", res);
})
.catch(error => {
// error
console.log("Attachment open error: ", error);
});
}
I've added new functionality to react-native-share module to store a file only in Files app. It's not merged yet but you can use my fork version.
https://github.com/gevorg94/react-native-share/tree/share-files-app.
Replace "react-native-share": "X.X.X" with "react-native-share": "git+https://github.com/gevorg94/react-native-share.git#share-files-app",.
You can find usage in PR https://github.com/react-native-community/react-native-share/pull/681
I've created a Cordova application that fetches images from a server and saves them to an iPad. However, when trying to display the images in the application, the images will not load. One example of such a file path could be:
file:///var/mobile/Containers/data/Application/FC87E925-9753-4D9F-AE27-54FCF9B0451E/Documents/-media-3405-company.png
However, when inspecting the cordova.file.applicationDirectory variable, I find another path, e.g. (note that the UUID differ even though I'm inspecting both variables in the same run)
file:///var/containers/Bundle/Application/D8266D08-18A4-4293-B78A-B4597FC0C6B8/salesApp.app/
Accordingly to the documentation, the correct path "should" be: (however, that does not work either)
file:///var/mobile/Applications/UUID/Documents/-media-3405-company.png
Here is the code I use to load the images, which are correctly saved to the device
const downloadFile = (url, fileName, callback) => {
window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, (fs) => {
fs.root.getFile(fileName, {
create: true,
exclusive: false
}, (fileEntry) => {
const fileURL = fileEntry.toURL()
const fileTransfer = new FileTransfer()
fileTransfer.download(
url,
fileURL,
(entry) => {
const file = entry.toURL() // <--- HERE
content.pushObject('Downloaded ' + entry + ' (' + fileName + ') ' + file)
callback(file)
},
(error) => {
content.pushObject('error ' + error.code + '(' + fileName + ')')
if (error.code === FileTransferError.CONNECTION_ERR) {
downloadFile(url, fileName) // Try again
} else {
decrement(url) // Ignore this file
}
}
)
}, (error) => {
alert(2)
})
}, () => {
alert(3)
})
}
Update: Inspecting the value for cordova.file.documentsDirectory, I found that it returns a path similar to: file:///var/mobile/Containers/Data/Application/{UUID}/Documents/.
Update: The following code will return two different UUIDs:
alert(cordova.file.applicationDirectory); // file:///var/containers/Bundle/Application/54E0F914-C45B-4B8F-9067-C13AF1967760/salesApp.app/
alert(cordova.file.documentsDirectory); // file:///var/mobile/Containers/Data/Application/73806E90-90B4-488C-A68A-2715C3627489/Documents/
When inspecting the path for entry.toURL() i get the same UUIDs as the one returned in cordova.file.documentsDirectory.
When you claim that "images will not load", then you should provide the code used to load the images.
The code you provided is to download the images and it works fine.
As you didn't provide the code used to load the images I have tried two things and both of them worked
Open the file on InAppBrowser. I installed the cordova-plugin-inappbrowser and opened the file like this window.open(file,'_blank');
Display the file on a img tag. I created a img tag in my index.html <img id="downloaded" src=""/> and on my callback I assign the file obtained to the src document.getElementById("downloaded").src = file;
Both of them worked.
So you should provide your code used to load the images because the problem might be there.
The path you are getting on the download is ok.
You are getting different UUIDs because the docs are outdated. Before Xcode 6/iOS8 the app sandbox had the Bundle container and the Data container inside the same folder (the one the docs mention with a common UUID), but since Xcode 6/iOS8 the app files (Bundle container) are in a path and the App data files are in another one (Data container).
But that shouldn't be a problem for you.
Answer that talks about the file structure changes
I get this error
The process cannot access the file '..\Images\Temp\6574_1212665562989_1419270107_30610848_6661938_n.jpg' because it is being used by another process.
When I tried this:
try
{
var file = Request.Files["FileProfilePicture"];
file.SaveAs(Server.MapPath("~/Images/Temp/" + file.FileName));
Bitmap imageOrj = new Bitmap(System.Web.HttpContext.Current.Server.MapPath("~/Images/Temp/" + file.FileName));
Image imageBig = ResizeImage.Resize(imageOrj, 100, 100);
imageBig.Save(System.Web.HttpContext.Current.Server.MapPath("~/Images/ProfilePicBig/" + file.FileName));
Image imageSmall = ResizeImage.Resize(imageOrj, 50, 50);
imageSmall.Save(System.Web.HttpContext.Current.Server.MapPath("~/Images/ProfilePicSmall/" + file.FileName));
string[] files = System.IO.Directory.GetFiles(Server.MapPath("~/Images/Temp/"));
foreach (string pathFile in files)
{
System.IO.File.Delete(pathFile);
}
return RedirectToAction("Index", "Author");
}
catch (Exception e)
{
ModelState.AddModelError("", "Kullanıcı bilgileri güncellenirken bir hata oluştu. Lütfen daha sonra tekrar deneyin." + e.Message);
}
How can I fix it. Or another better way to keep images as a temp. Should I keep files in a temp folder?
Thanks
Did you make sure you do not have the file in temp open somewhere else?
This error also occurs when you had an error in a previous run and the file has not been closed. You can try a different file name in this case or just delete the unclosed file from the OS side.
I hope this helps...
Otherwise, I usually use a similar way of doing temp files...
Edit: By the comments, it seems that the resolution for the issue above was the following: whenever handled in a try-catch block, the file objects might not get closed down if it's not handled in the catch node.
In this specific case the imageOrj object caused the problem, so it's advised to use an imageOrj.Dispose() after the Bitmap editing is finished.
You don't need to save the temp file. You can create the bitmap in memory and populate it with the request stream. There is no need to save it to the disk.
You seem to delete more files than you create in your deletion loop. You are attempting to delete every single file under ~/Images/Temp/, this might create conflicts (ie data races between different requests). Delete only the files you just created.