Let's say I have this minimal XML file on Google Drive.
<?xml version="1.0" encoding="UTF-8"?>
<MyCounter>
<counter>137</counter>
</MyCounter>
Using Google Script, I want to:
parse the XML
add 1 to the counter
update the file.
I'm at step 2 at the moment. I can delete the old file and create a new one with the same name and updated content. I prefer to update the existing one instead, so it will maintain the unique ID, and I can access the file with said ID instead of searching for it via file name.
You want to update a text file in your Google Drive without changing the file ID.
You want to add 1 to 137 of <counter>137</counter> in the file.
You want to achieve above using Google Apps Script.
If my understanding is correct, how about this sample script? I think that there are several answers for your situation. So please think of this as just one of them.
Flow:
Retrieve data from the text file.
Add 1 to number of <counter>{number}</counter> using replace().
Update the file using setContent().
By this flow, the file can be updated without changing file ID.
Sample script:
var fileId = "#####"; // Please set fileId here.
var file = DriveApp.getFileById(fileId);
var str = file.getBlob().getDataAsString();
var r = str.replace(/<counter>(\d+)<\/counter>/, function(_, p) {
return "<counter>" + (Number(p) + 1) + "</counter>";
});
file.setContent(r);
Note:
If you have already prepared the script for adding the counter, please use it.
References:
replace()
setContent()
Related
I have a custom form using a "managed_file" which uploads to temp folder. Programmatically, I then load that file and move it to its permanent storage (overwriting any existing file with the* name) e.g.
// Upload file
$upfile = $this->entityTypeManager->getStorage('file')->load($fid);
// Source and destination
$sourceUri = $this->fileSystem->realpath($upfile->getFileUri());
$destinationUri = $this->fileSystem->realpath(\Drupal::config('system.file')->get('default_scheme') . "://") . '/x/y/z/XYZ_NEW.pdf';
// Move and overwrite
$this->fileSystem->move($sourceUri, $destinationUri, FileSystemInterface::EXISTS_REPLACE);
All of this works (i.e. the file physically is moved into the correct place with correct name); however, the file displayed in the listings (i.e. /admin/content/files) still shows the incorrect temporary folder as the URI.
Basically the file in the listings page seems to be showing the original filepath and name* of a previously successfully moved* file.
If I load this file with incorrect URI, i.e. using the incorrect temp path, the data loads, but then will not have a file info (as it doesn't exist. Also clicking the filename by browser under listings will show page not found and the URL showing the old URL i.e. /system/temporary?file=XYZ.pdf).
If I load this file by correct URI, i.e. using the correct destination path, file not found - same if I go to the path directly in the browser.
It appears the managed file doesn't know it was moved. How to resolve this bug?
The docs for FileSystem::move say "Moves a file to a new location without database changes or hook invocation."
So you are going to need to update the file entity with the new values..
Try this, untested:
// Upload file
$upfile = $this->entityTypeManager->getStorage('file')->load($fid);
// Source and destination
$sourceUri = $this->fileSystem->realpath($upfile->getFileUri());
$destinationUri = $this->fileSystem->realpath(\Drupal::config('system.file')->get('default_scheme') . "://") . '/x/y/z/XYZ_NEW.pdf';
// Move and overwrite
$newFileName = $this->fileSystem->move($sourceUri, $destinationUri, FileSystemInterface::EXISTS_REPLACE);
// Set the new file path on the file entity.
$upfile->setFileUri($newFileName);
// Set the file to permanent if needed.
$upfile->setPermanent();
// Save entity with changes.
$upfile->save();
I did not test this though.
You can check the functions on the file entity in the docs here
It turns out the class based methods do not update the database
https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21File%21FileSystem.php/function/FileSystem%3A%3Amove/8.9.x
The procedural version does
https://api.drupal.org/api/drupal/core%21modules%21file%21file.module/function/file_move/8.9.x
I am currently using version2 of API. Now, I want to switch to version3. I want to set the parentId in this version. What changes should I make in below line of code to do so?
I don't think this function was changed because it wasn't mentioned in the Migration Guide for v3. You can see the snippet here in Inserting a file in a folder:
String folderId = "0BwwA4oUTeiV1TGRPeTVjaWRDY1E";
File fileMetadata = new File();
fileMetadata.setName("photo.jpg");
fileMetadata.setParents(Collections.singletonList(folderId));
java.io.File filePath = new java.io.File("files/photo.jpg");
I've been trying to figure out why part of my Google App script doesn't work, but I've failed to come up with an answer.
The script is downloading an attachment, CSV, from an email in Gmail and stores in with a specific name in a specific folder - this works perfectly fine.
But then I want to edit the CSV, and this is where I run into problems.
var newFolderIterator = DriveApp.getFoldersByName(destinationFolderName)
var newFolderId, myFileName, myFileId
while(newFolderIterator.hasNext()) {
newFolderId = newFolderIterator.next().getId()
var newFileList = DriveApp.getFolderById(newFolderId).getFiles()
while(newFileList.hasNext()) {
myFileName = newFileList.next()
myFileId = myFileName.getId()
var myFileURL = myFileName.getUrl()
Logger.log(myFileName.getId() + " " + myFileName.getName()) //Logs the ID and Name
Logger.log(myFileURL) //Logs the URL
var ss = SpreadsheetApp.openById(myFileName.getId()) //Error, cannot find the ID (error message: perhaps it's missing?)
}
}
I've tried using the openByURL as well, with the same error message.
Probably really easy to fix, any hints and tips is appreciated.
Thanks
The problem here is you are uploading a CSV but attempting to open it with SpreadsheetApp. SpreadsheetApp can only open Google Sheets documents, and your CSV is not automatically converted.
The CSV must first be converted to a Google Sheets document before you can access it with SpreadsheetApp.
You may be able to do this on upload, but I haven't tried this personally. This question looks relevant:
How to automatically import data from uploaded CSV or XLS file into Google Sheets
How do I write a Google Apps Script that deletes files?
This finds files:
var ExistingFiles = DocsList.find(fileName);
But DocsList.deleteFile does not exist to delete a file.
Is there a way to move those files to another Folder or to Trash?
The other workaround I would consider is to be able to override an existing file with the same name.
Currently when I want to create a file with a name already used in MyDrive then it creates a second file with the same name. I would like to keep 1 file (the new one is kept and the old one is lost).
There are 3 services available to delete a file.
DriveApp - Built-in to Apps Script
Advanced Drive Service - Built-in to Apps Script but must be enabled. Has more capability than DriveApp
Google Drive API - Not built-in to Apps Script, but can be used from Apps Script using the Drive REST API together with UrlFetchApp.fetch(url,options)
The DocsList service is now deprecated.
The Advanced Drive Service can be used to delete a file without sending it to the trash. Seriously consider the risk of not being able to retrieve the deleted file. The Advanced Drive Service has a remove method which removes a file without sending it to the trash folder. Advanced services have many of the same capabilities as the API's, without needing to make an HTTPS GET or POST request, and not needing an OAuth library.
function delteFile(myFileName) {
var allFiles, idToDLET, myFolder, rtrnFromDLET, thisFile;
myFolder = DriveApp.getFolderById('Put_The_Folder_ID_Here');
allFiles = myFolder.getFilesByName(myFileName);
while (allFiles.hasNext()) {//If there is another element in the iterator
thisFile = allFiles.next();
idToDLET = thisFile.getId();
//Logger.log('idToDLET: ' + idToDLET);
rtrnFromDLET = Drive.Files.remove(idToDLET);
};
};
This combines the DriveApp service and the Drive API to delete the file without sending it to the trash. The Drive API method .remove(id) needs the file ID. If the file ID is not available, but the file name is, then the file can first be looked up by name, and then get the file ID.
In order to use DriveAPI, you need to add it through the Resources, Advanced Google Services menu. Set the Drive API to ON. AND make sure that the Drive API is turned on in your Google Cloud Platform. If it's not turned on in BOTH places, it won't be available.
Now you may use the following if the file is as a spreadsheet, doc etc.:
DriveApp.getFileById(spreadsheet.getId()).setTrashed(true);
or if you already have the file instead of a spreadsheet, doc etc. you may use:
file.setTrashed(true);
This code uses the DocsList Class which is now deprecated.
try this :
function test(){
deleteDocByName('Name-of-the-file-to-delete')
}
function deleteDocByName(fileName){
var docs=DocsList.find(fileName)
for(n=0;n<docs.length;++n){
if(docs[n].getName() == fileName){
var ID = docs[n].getId()
DocsList.getFileById(ID).setTrashed(true)
}
}
}
since you can have many docs with the same name I used a for loop to get all the docs in the array of documents and delete them one by one if necessary.
I used a function with the filename as parameter to simplify its use in a script, use test function to try it.
Note : be aware that all files with this name will be trashed (and recoverable ;-)
About the last part of your question about keeping the most recent and deleting the old one, it would be doable (by reading the last accessed date & time) but I think it is a better idea to delete the old file before creating a new one with the same name... far more logical and safe !
Though the The service DocsList is now deprecated, as from the Class Folder references, the settrashed method is still valid:
https://developers.google.com/apps-script/reference/drive/folder#settrashedtrashed
So should work simply this:
ExistingFiles.settrashed(true);
Here is another way to do it without the need of Drive API. (based on Allan response).
function deleteFile(fileName, folderName) {
var myFolder, allFiles, file;
myFolder = DriveApp.getFoldersByName(folderName).next();
allFiles = myFolder.getFilesByName(fileName);
while (allFiles.hasNext()) {
file = allFiles.next();
file.getParents().next().removeFile(file);
}
}
Here is a slightly modified version using the above. This will backup said file to specified folder, also remove any old previous backups with the same name so there are no duplicates.
The idea is here to backup once per day, and will retain 1 month of backups in your backup folder of choice. Remember to set your trigger to daily in your Apps Script.
https://gist.github.com/fmarais/a962a8b54ce3f53f0ed57100112b453c
function archiveCopy() {
var file = DriveApp.getFileById("original_file_id_to_backup");
var destination = DriveApp.getFolderById("backup_folder_name");
var timeZone = Session.getScriptTimeZone();
var formattedDate = Utilities.formatDate(new Date(),timeZone,"dd"); // 1 month backup, one per day
var name = SpreadsheetApp.getActiveSpreadsheet().getName()+"_"+formattedDate;
// remove old backup
var allFiles = destination.getFilesByName(name);
while (allFiles.hasNext()) {
var thisFile = allFiles.next();
thisFile.setTrashed(true);
};
// make new backup
file.makeCopy(name,destination);
}
In my Grails application I need to create a file in current system in which I need to save information fetched from table in database. How to do this from within controller action? I don't have any idea of it.
I have created file as
File file=new File("file name.txt")
file.createNewFile();
then I have wrote values of MySQL database table fields in it as:
file<<patient.id
file<<patient.name
.
.
.
it stores data like continuous text but I want to have a .doc file in which data should get stored in table. I found Apache's POI for creating doc file but I am not getting how it works and how I should use it.
Not sure exactly what you want to store in a file but below is an example of how to easly write a String to a file using Apache-commons-io Which should be included in grails
import org.apache.commons.io.FileUtils;
class SomeController{
def writeToFile = {
def data = getSomeStringData();
def fileStore = new File("./path/to/files/ControllerOutput_${new Date()}.txt");
fileStore.createNewFile();
FileUtils.writeStringToFile(fileStore, data);
println("your file was created # {fileStore.absolutePath} and is ${fileStore.length()} bytes");
}
}
Does this help? If not, you need to explain exactly what your looking for.
This is a comment to Michael's answer (unfortunately I still don't have the reputation to reply on answers).
If you're struggling around the problem how to specifiy the relative path from within your controller's context, this might help you:
So if you have following folder you want to read/write files from/into"..
/myproject/web-app/temp/
you can access the file like this:
import org.codehaus.groovy.grails.commons.ApplicationHolder as AH
// getResource references to the web-app folder as root folder
Resource resource = AH.getApplication().getParentContext().getResource("/temp/myfile.txt)