Basically, in Google Drive API, I’m trying to read a specified folder within the drive.
E.g. root/Temp/TestFiles/AreInHere
I want to read all files in ‘AreInHere’, and I can do this using a query specifying the id for my desired folder. E.g. ‘’’AreInHere’ in parents’.
This works fine and does as id expect, it also returns all the sub-directories in my folder but doesn’t reclusively get any files with those sub-directories?
I have looked at the API reference and all the documentation for searching but have has no luck in finding anything OOTB.
Any help or advice is much appreciated.
Thanks in advance
Unfortunately, with Drive API there is no way to automatically list files contained within child folders
Instead, you need to use a recursive function that dynamically finds all subfolders and subfolders of subfolders - no matter what the number of levels is .
What helps to make your life easier is specifying the id of the parent dynamically as a query parameter q and querying for the mimeType of the file.
You don't specify your language, but e.g. in Apps Script you could do something like this:
function myFunction() {
var id = "SPECIFY HERE THE ID OF THE PARENT FOLDER";
iterate(id);
}
function iterate(id){
var q = "'" + id + "' in parents"
var files= Drive.Files.list({"q": q}).items;
if (files.length>0){
for ( var i = 0; i < files.length; i++){
Logger.log(files[i].title);
Logger.log(files[i].mimeType);
if(files[i].mimeType=="application/vnd.google-apps.folder"){
id = files[i].id;
iterate(id);
}
}
}
}
Related
I wrote a script to import stock data from a csv file stored in Google Drive to an existing google sheet.
In one function I'm doing this for multiple csv files. Unfortunately I get "Exceeded maximum execution time" sometimes, but not all the time.
Do you have an idea how I can boost performance on this:
//++++++++++++++ SPY +++++++++++++++++++
var file = DriveApp.getFilesByName("SPY.csv").next();
var csvData = Utilities.parseCsv(file.getBlob().getDataAsString());
//Create new temporary sheet
var activeSpreadsheet = SpreadsheetApp.getActiveSpreadsheet();
var yourNewSheet = activeSpreadsheet.getSheetByName("SPY-Import");
if (yourNewSheet != null) {
activeSpreadsheet.deleteSheet(yourNewSheet);
}
yourNewSheet = activeSpreadsheet.insertSheet();
yourNewSheet.setName("SPY-Import");
//Import
var sheet = SpreadsheetApp.getActiveSheet();
sheet.getRange(1, 1, csvData.length, csvData[0].length).setValues(csvData);
//Copy from temporary sheet to destination
var spreadsheet = SpreadsheetApp.getActive();
spreadsheet.getRange('A:B').activate();
spreadsheet.setActiveSheet(spreadsheet.getSheetByName('SPY'), true);
spreadsheet.getRange('A2').activate();
spreadsheet.getRange('\'SPY-Import\'!A:B').copyTo(spreadsheet.getActiveRange(),
SpreadsheetApp.CopyPasteType.PASTE_NORMAL, false);
//Delete temporary sheet
// Get Spreadsheet Object
var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
// Get target sheet object
var sheet = spreadsheet.getSheetByName("SPY-Import");
// Delete
spreadsheet.deleteSheet(sheet);
Thanks in advance!
I believe your situation and goal as follows.
You have several CSV files like SPY.csv.
Your Spreadsheet has the several sheet corresponding to each CSV file like SPY.
You want to put the values from the CSV data to the Spreadsheet.
You want to put the values of the column "A" and "B" of the CSV data.
In your current situation, you copied the script in your question several times and run them by changing the csv filename and sheet name.
You want to reduce the process cost of your script. I understood your goal like this.
Modification points:
SpreadsheetApp.getActiveSpreadsheet() is used several times. And, activate() is used several times.
I think that in your case, SpreadsheetApp.getActiveSpreadsheet() can be declared one time, and activate() is not required to be used.
In order to copy the CSV data to Spreadsheet, the CSV data is put to a temporal sheet and the required values are copied to the destination sheet.
In this case, I think that the CSV data is directly put to the destination sheet by processing on the array.
I think that above points lead to the reduction of process cost. When above points are reflected to your script, it becomes as follows.
Modified script:
Please copy and paste the following script and prepare the variable of obj. When you run the script, the CSV data is retrieved and processed, and then, the values are put to the Spreadsheet.
function myFunction() {
var obj = [
{filename: "SPY.csv", sheetname: "SPY"},
{filename: "###.csv", sheetname: "###"},
,
,
,
];
var ss = SpreadsheetApp.getActiveSpreadsheet();
obj.forEach(({filename, sheetname}) => {
var file = DriveApp.getFilesByName(filename);
if (file.hasNext()) {
var sheet = ss.getSheetByName(sheetname);
if (sheet) {
// sheet.clearContents(); // Is this requierd in your situation?
var csv = DriveApp.getFileById(file.next().getId()).getBlob().getDataAsString();
var values = Utilities.parseCsv(csv).map(([a, b]) => [a, b]);
sheet.getRange(2, 1, values.length, 2).setValues(values);
}
}
});
}
Note:
Please use this script with enabling V8
I'm not sure about your CSV data. So when Utilities.parseCsv(csv) cannot be used, please use the delimiter.
In this modification, Spreadsheet service is used. If above modified script occurs the same error of Exceeded maximum execution time, please tell me. At that time, I would like to propose the sample script using Sheets API.
References:
Spreadsheet Service
parseCsv(csv)
String mySQLString = "select * from document where documentTitle like '%test%' ";
SearchSQL sql = new SearchSQL(mySQLString);
IndependentObjectSet s = search.fetchObjects(sql, 10, null, true);
Document doc;
PageIterator iterator = s.pageIterator();
iterator.nextPage();
for (Object object : iterator.getCurrentPage()) {
doc = (Document) object;
Properties properties = doc.getProperties();
//I am trying to get an absolute or relative path here for every document.
// for eg: /objectstorename/foldername/filename like this.
}
I have tried searching propeties and class descriptions in document . but can't able to find the path. ?
To do it all in one single query (as you are trying to do in your code) you can create a join with the ReferentialContainmentRelationship table. The property Head of this table points to the document, the property Tail points to the folder the document is filled in and the property ContainmentName is the name the document has in the folder. Use the following code to construct the document path:
SearchSQL searchSQL = new SearchSQL("SELECT R.ContainmentName, R.Tail, D.This FROM Document AS D WITH INCLUDESUBCLASSES INNER JOIN ReferentialContainmentRelationship AS R WITH INCLUDESUBCLASSES ON D.This = R.Head WHERE DocumentTitle like '%test%'");
SearchScope searchScope = new SearchScope(objectStore);
RepositoryRowSet objects = searchScope.fetchRows(searchSQL, null, null, null);
Iterator<RepositoryRow> iterator = objects.iterator();
while (iterator.hasNext()) {
RepositoryRow repositoryRow = iterator.next();
Properties properties = repositoryRow.getProperties();
Folder folder = (Folder) properties.get("Tail").getEngineObjectValue();
String containmentName = properties.get("ContainmentName").getStringValue();
System.out.println(folder.get_PathName() + "/" + containmentName);
}
Paths constructed this way can also be used to fetch the object from the object store. The query code can be optimized by using a property filter as the third argument of the fetchRows() method. Don't know how this behaves if the document is filed in multiple folders.
I suggest you explore the "Creating DynamicReferentialContainmentRelationship Objects" section of FileNet documentation:
https://www.ibm.com/support/knowledgecenter/SSNW2F_5.5.0/com.ibm.p8.ce.dev.ce.doc/containment_procedures.htm#containment_procedures__fldr_creating_a_drcr
A FileNet Ddocument can be assigned to multiple Folders, so you can have several logical "Paths" for a given document.
At end, you should get something like "Folder.get_PathName() + DynamicReferentialContainmentRelationship.get_Name()" to display the full pathname.
As described by samples in FileNet documentation, a relationship object (e.g. DynamicReferentialContainmentRelationship) controls the relation of document/folder:
myRelationshipObject.set_Head(myDocument);
myRelationshipObject.set_Tail(myFolder);
Also, keep in mind that a FileNet Document can be also a "unfiled" document, so there is no actual "pathname" or folder "relationship" to be retrieved.
tl;dr from FileNet Content Engine - Database Table for Physical path
Documents are stored among the directories at the leaf level using a hashing algorithm to evenly distribute files among these leaf directories.
I have two Google Docs (D1 and D2). I would like to copy the content of D1 into D2. The fileIds of these documents are stored in the database which cannot be modified due to the limitation of the Architecture. Hence, I don't have an option of deleting D2 and creating a copy of D1, because, this will result in a new fileId. I am using Google Drive V3 API (java) to interact with Google Drive.
How can I update the content of Google Doc from another Google Doc without creating a new file?
Note: These are Google Docs, not any other format like MS-Word, PDF etc
Google drive api is a file directory api only. You will need to download the file to your system edit the file there and then upload it again.
There is no api that allows for edditing a Google doc on google drive itself.
When you download the file you will be able to choose which format to export it as Downloading Google Documents then you can make your changes before uploading again. upload Files are in two parts the actual metadata of the file. Name, description file resource and the actual data of the file. You will only need to insert the metadata once. Then you can upload the file data when ever you change it this way you will only have one file and not duplicate files just keep the same file id.
Note App Script
Google app scripts does allow for some editing of Google doc files. I know this is not what you asked for but it may help you automate your process please see Google app script
As many have mentioned in the other answers, Google Drive API does not provide the capability to copy the content of a Google Doc. The only way to achieve this is by using Google Apps Script. Following is the code to do the same:
function test() {
var sourceFileId = 'fileId';
var targetFileId = 'fileId';
var sourceDocument = DocumentApp.openById(sourceFileId);
var targetDocument = DocumentApp.openById(targetFileId);
var sourceBody = sourceDocument.getBody();
var targetBody = targetDocument.getBody();
var sourceHeader = sourceDocument.getHeader();
var targetHeader = targetDocument.getHeader();
var sourceFooter = sourceDocument.getFooter();
var targetFooter = targetDocument.getFooter();
copyContent(sourceHeader, targetHeader);
copyContent(sourceFooter, targetFooter);
copyContent(sourceBody, targetBody);
}
var copyContent = function(source, target) {
target.clear();
for(var i = 0; i < source.getNumChildren(); i++) {
var child = source.getChild(i).copy();
if (child.getType() === DocumentApp.ElementType.TABLE) {
target.appendTable(child);
} else if (child.getType() === DocumentApp.ElementType.LIST_ITEM) {
target.appendListItem(child);
} else if (child.getType() === DocumentApp.ElementType.PARAGRAPH) {
target.appendParagraph(child);
}
}
}
Note: This script does not cover all the sections and element types.
In Drive, the content aka media of a file is separate from the metadata of a file.
So, in pseudo code, what you need to do is:-
. Download content of D1
. Use the downloaded content in a content upload to D2.
The file id of D2 remains unchanged.
I'm totally new to Alfresco and their Javascript API so please bear that in mind...
I want to be able to view a list of groups for every user in Alfresco repository.
This is the code I have at the moment:
var gens = search.luceneSearch("TYPE:\"{http://www.alfresco.org/model/content/1.0}person\"");
var logFile = space.childByNamePath("log_user_groups.csv");
if (logFile == null) {
logFile = space.createFile("log_user_groups.csv");
}
logFile.content = "";
for (var i=0; i<gens.length;i++) {
logFile.content += gens[i].properties["cm:userName"]+"\n";
var groupes= people.getContainerGroups(gens[i]);
for (var j=0; j<groupes.length;j++) {
logFile.content += "\t"+groupes[j].properties.shortName +"\t";
logFile.content += "\t"+groupes[j].properties.fullName +"\t";
logFile.content += "\t"+groupes[j].properties.displayName +"\n";
}
}
The file is created with the user name shown correctly. However the group properties 'shortName', 'fullName' and 'displayName' are all null. In fact I printed out all the properties of the 'groupes' object and every field of the object is 'undefined'.
Does any body know what I am doing wrong?
Any help would be greatly appreciated.
Norm.
The easiest way would be to turn it on its head. Instead, for each group ask what groups and what users it contains. At the end, invert it.
You'll want to start with the Root Groups. The groups JS object in Alfresco will give you these, and others. It's implemented by ScriptAuthorityService, so you'll likely want to look at the JavaDocs
First up, get the root groups
var rootGroups = groups.getAllRootGroups() ;
For each group, get all the users in the group (direct and inherited) with getAllUsers(), and store those somewhere. Now, get all the child groups with getChildGroups(). Process each of these in the same way, recursing as needed.
I needed something similar (a complete list of groups) so I did this:
var temp = [];
function addGroups (groups)
{
for each (group in groups)
{
temp.push(group.getDisplayName());
addGroups(group.getChildGroups());
}
}
addGroups(groups.getAllRootGroups());
This works to a point. The problem is that getDisplayName() returns a very non-pretty group name. Normally when dealing with documents and displaying a group name associated with a user I would do people.getContainerGroups() and use group.properties["cm:authorityName"] to get a displayable name (as mentioned above), however the groups I receive from getAllRootGroups() do not have properties (group.properties is undefined).
Does anyone have any idea why the group list returned this way wouldn't have the same properties as those returned by people.getContainerGroups()?
I guess you're using the wrong properties name.
You need the following:
Full Name: groupes[j].properties["usr:authorityName"]
Display Name: groupes[j].properties["usr:authorityDisplayName"]
Short Name: I don't know :) maybe groupes[j].properties["usr:authorityShortName"]
You can also just get the NodeRef id.
Then login to Alfresco explorer. Then go to the administration console --> Node Browser
Paste the id (it should be something like workspace://spacesStore//biglongUUID).There you can see al the properties related to the group.
Or you could just loop the groupes[k].properties map and print all the properties.
My main question here is dealing with the pramas map when having a one-to-many relationship managed within one dynamic form, as well as best practices for dealing with one-to-many when editing/updating a domain object through the dynamic form. The inputs for my questions are as follows.
I have managed to hack away a form that allows me to create the domain objects shown below in one Dynamic form, since there is no point in having a separate form for creating phone numbers and then assigning them to a contact, it makes sense to just create everything in one form in my application. I managed to implement something similar to what I have asked in my Previous Question (thanks for the people who helped out)
class Contact{
String firstName
String lastName
// ....
// some other properties
// ...
static hasMany = [phones:Phone]
static mapping = {
phones sort:"index", cascade: "all-delete-orphan"
}
}
class Phone{
int index
String number
String type
Contact contact
static belongsTo = [contact:Contact]
}
I basically managed to get the values from the 'params' map and parse them on my own and create the domain object and association manually. I.e. i did not use the same logic that is used in the default scaffolding, i.e.
Contact c = new Contact(params)
etc...., i just looped through all the params and hand crafted my domain objects and saved them and everything works out fine.
My controller has code blocks that look like this (this is stripped down, just to show a point)
//create the contact by handpicking params values
def cntct = new Contact()
cntct.firstName = params.firstName
cntct.lastName = params.lastName
//etc...
//get array of values for number,type
def numbers = params['phone.number']
def types = params['phone.type']
//loop through one of the arrays and create the phones
numbers.eachWithIndex(){ num, i ->
//create the phone domain object from
def phone = new Phone()
phone.number = num
phone.type = types[i]
phone.index = i
cntct.addToPhones(phone)
}
//save
My questions are as follows:
What is the best practice of handeling such a situation, would using Command objects work in this case, if yes where can i found more info about this, all the examples I have found during my search deal with one-to-one relationships, I couldn't find an example for one-to-many?
What is the best way to deal with the relatiohsips of the phones in this case, in terms of add/removing phones when editing the contact object. I mean the creation logic is simple since I have to always create new phones on save, but when dealing with updating a contact, the user might have removed a phone and/or editing an exiting one and/or added some new phones. Right now what I do is just delete all the phones a contact has and re-create them according to what was posted by the form, but I feel that's not the best way to do it, I also don't think looping over the existing ones and comparing with the posted values and doing a manual diff is the best way to do it either, is there a best practice on how to deal with this?
Thanks, hopefully the questions are clear.
[edit] Just for more information, phone information can be added and deleted dynamically using javascript (jquery) within the form [/edit]
disclaimer: i do not know if the following approach works when using grails. Let me know later.
See better way for dynamic forms. The author says:
To add LineItems I have some js that calculates the new index and adds that to the DOM. When deleting a LineItem i have to renumber all the indexes and it is what i would like to avoid
So what i do
I have a variable which stores the next index
var nextIndex = 0;
When the page is loaded, i perform a JavaScript function which calculates how many child The collection has and configure nextIndex variable. You can use JQuery or YUI, feel free.
Adding a child statically
I create a variable which store the template (Notice {index})
var child = "<div>"
+= "<div>"
+= "<label>Name</label>"
+= "<input type="text" name=\"childList[{index}].name\"/>"
+= "</div>"
+= "</div>"
When the user click on the Add child button, i replace {index} - by using regex - by the value stored in the nextIndex variable and increment by one. Then i add to the DOM
See also Add and Remove HTML elements dynamically with Javascript
Adding a child dinamically
Here you can see The Paolo Bergantino solution
By removing
But i think it is the issue grow up when deleting. No matter how many child you remove, does not touch on the nextIndex variable. See here
/**
* var nextIndex = 3;
*/
<input type="text" name="childList[0].name"/>
<input type="text" name="childList[1].name"/> // It will be removed
<input type="text" name="childList[2].name"/>
Suppose i remove childList1 What i do ??? Should i renumber all the indexes ???
On the server side i use AutoPopulatingList. Because childList1 has been removed, AutoPopulatingList handles it as null. So on the initialization i do
List<Child> childList = new AutoPopulatingList(new ElementFactory() {
public Object createElement(int index) throws ElementInstantiationException {
/**
* remove any null value added
*/
childList.removeAll(Collections.singletonList(null));
return new Child();
}
});
This way, my collection just contains two child (without any null value) and i do not need to renumber all the indexes on the client side
About adding/removing you can see this link where i show a scenario wich can gives you some insight.
See also Grails UI plugin
Thanks,
Your answer brought some insight for me to do a wider search and I actually found a great post that covers all the inputs in my question. This is just a reference for anyone reading this. I will write a blog entry on how I implemented my case soon, but this link should provide a good source of ino with a working exmaple.
http://www.2paths.com/2009/10/01/one-to-many-relationships-in-grails-forms/
Most of the time I use ajax to manage such problem.
So when the user clicks add new phone I get the template UI from the server for manageability purpose ( the UI just same GSP template that I use to edit, update the phone), so this way you are not mixing your UI with your js code, whenever you want to change the UI you have to deal only with our GSP code.
Then after getting the UI I add it to the page using jquery DOM manipulation. Then after filling the form when they hit add(save) the request is sent to the server via ajax and is persisted immediately.
When the user clicks edit phone the same UI template is loaded from the server filled with existing phone data, then clicking update will update the corresponding phone immediately via ajax, and same thing applies to delete operation.
But one day I got an additional scenario for the use case that says, "until I say save contact no phone shall be saved on the backend, also after adding phones to the contact on the ui if navigate away to another page and come back later to the contact page the phones I added before must be still there." ugh..
To do this I started using the Session, so the above operations I explained will act on the phone list object I stored on the session instead of the DB. This is simple perform all the operation on the phonesInSession but finally dont forget to do this(delete update):
phonesToBeDeleted = phonesInDB - phonesInSession
phonesToBeDeleted.each{
contact.removeFromPhones(it)
it.delete()
}
I know I dont have to put a lot of data in session but this is the only solution I got for my scenario.
If someone has got similar problem/solution please leave a comment.
First, in all your input fields names you add an #:
<input type="text" name="references[#].name"/>
Second, add call a function before submitting:
<g:form action="save" onsubmit="replaceAllWildCardsWithConsecutiveNumbers();">
Third, this is the code for the function that you call before submitting the form:
function replaceAllWildCardsWithConsecutiveNumbers(){
var inputs = $('form').find("[name*='#']");
var names = $.map(inputs, function(el) { return el.name });
var uniqueNames = unique(names);
for (index in uniqueNames) {
var uniqueName = uniqueNames[index];
replaceWildCardsWithConsecutiveNumbers("input", uniqueName);
replaceWildCardsWithConsecutiveNumbers("select", uniqueName);
}
}
function unique(array){
return array.filter(function(el, index, arr) {
return index === arr.indexOf(el);
});
}
function replaceWildCardsWithConsecutiveNumbers(inputName, name){
counter = 0;
$(inputName + "[name='" + name + "']").each(function (i, el) {
var curName = $(this).attr('name');
var newName = curName.replace("#", counter);
$(this).attr('name', newName);
counter += 1;
});
}
Basically, what the code for replaceAllWildCardsWithConsecutiveNumbers() does, is to create a list for all input (or select) elements whose name contains an #. Removes the duplicates. And then iterates over them replacing the # with a number.
This works great if you have a table and you are submitting the values to a command object's list when creating a domain class for the first time. If you are updating I guess you'll have to change the value of counter to something higher.
I hope this helps someone else since I was stuck on this issue for a while myself.