Get subfolders from folder with multiple levels - javascript

I have code which checks all the files in subfolders for a folder. But how can I change it to not only check on subfolder level but also the subfolders of the subfolder and so on?
This is the code i have for the folder and its subfolders:
var fso = new ActiveXObject("Scripting.FileSystemObject");
fso = fso.getFolder(path);
var subfolders = new Object();
subfolders = fso.SubFolders;
var oEnumerator = new Enumerator(subfolders);
for (;!oEnumerator.atEnd(); oEnumerator.moveNext())
{
var itemsFolder = oEnumerator.item().Files;
var oEnumerator2 = new Enumerator(itemsFolder);
var clientFileName = null;
for(;!oEnumerator2.atEnd(); oEnumerator2.moveNext())
{
var item = oEnumerator2.item();
var itemName = item.Name;
var checkFile = itemName.substring(itemName.length - 3);
if(checkFile == ".ac")
{
var clientFileName = itemName;
break;
}
}
}
On each level of subfolders I need to check all the files if it can find a .ac file.

The solution I mentioned in my comment would look something like this (I don't know much about ActiveX, so there are a lot of comments so hopefully you can easily correct any mistakes):
//this is the function that looks for the file inside a folder.
//if it's not there, it looks in every sub-folder by calling itself
function getClientFileName(folder) {
//get all the files in this folder
var files = folder.Files;
//create an enumerator to check all the files
var enumerator = new Enumerator(files);
for(;!enumerator.atEnd(); enumerator.moveNext()) {
//get the file name we're about to check
var file = enumerator.item().Name;
//if the file name is too short skip this one
if (file.length<3) continue;
//check if this file's name matches, if it does, return it
if (file.substring(file.length - 3)==".ac") return file;
}
//if we finished this loop, then the file was not inside the folder
//so we check all the sub folders
var subFolders = folder.SubFolders;
//create an enumerator to check all sub folders
enumerator = new Enumerator(subFolders);
for(;!enumerator.atEnd(); enumerator.moveNext()) {
//get the sub folder we're about to check
var subFolder = enumerator.item();
//try to find the file in this sub folder
var fileName = getClientFileName(subFolder);
//if it was inside the sub folder, return it right away
if (fileName!=null) return fileName;
}
//if we get this far, we did not find the file in this folder
return null;
}
You would then call this function like so:
var theFileYouAreLookingFor = getClientFileName(theFolderYouWantToStartLookingIn);
Again, beware of the above code: I did not test it, nor do I know much about ActiveX, I merely took your code and changed it so it should look in all the sub folders.

What you want is a recursive function. Here's a simple recursive function that iterates thru each file in the provided path, and then makes a recursive call to iterate thru each of the subfolders files. For each file encountered, this function invokes a provided callback (which is where you'd do any of your processing logic).
Function:
function iterateFiles(path, recursive, actionPerFileCallback){
var fso = new ActiveXObject("Scripting.FileSystemObject");
//Get current folder
folderObj = fso.GetFolder(path);
//Iterate thru files in thisFolder
for(var fileEnum = new Enumerator(folderObj.Files); !fileEnum.atEnd(); fileEnum.moveNext()){
//Get current file
var fileObj = fso.GetFile(fileEnum.item());
//Invoke provided perFile callback and pass the current file object
actionPerFileCallback(fileObj);
}
//Recurse thru subfolders
if(recursive){
//Step into each sub folder
for(var subFolderEnum = new Enumerator(folderObj.SubFolders); !subFolderEnum.atEnd(); subFolderEnum.moveNext()){
var subFolderObj = fso.GetFolder(subFolderEnum.item());
//Make recursive call
iterateFiles(subFolderObj.Path, true, actionPerFileCallback);
}
}
};
Usage (here I'm passing in an anonymous function that get's called for each file):
iterateFiles(pathToFolder, true, function(fileObj){
Wscript.Echo("File Name: " + fileObj.Name);
};
Now.. That is a pretty basic example. Below is a more complex implementation of a similar function. In this function, we can recursively iterate thru each file as before. However, now the caller may provide a 'calling context' to the function which is in turn passed back to the callback. This can be powerful as now the caller has the ability to use previous information from it's own closure. Additionally, I provide the caller an opportunity to update the calling context at each recursive level. For my specific needs when designing this function, it was necessary to provide the option of checking to see if each callback function was successful or not. So, you'll see checks for that in this function. I also include the option to perform a callback for each folder that is encountered.
More Complex Function:
function iterateFiles(path, recursive, actionPerFileCallback, actionPerFolderCallback, useFnReturnValue, callingContext, updateContextFn){
var fso = new ActiveXObject("Scripting.FileSystemObject");
//If 'useFnReturnValue' is true, then iterateFiles() should return false IFF a callback fails.
//This function simply tests that case.
var failOnCallbackResult = function(cbResult){
return !cbResult && useFnReturnValue;
}
//Context that is passed to each callback
var context = {};
//Handle inputs
if(callingContext != null){
context.callingContext = callingContext;
}
//Get current folder
context.folderObj = fso.GetFolder(path);
//Do actionPerFolder callback if provided
if(actionPerFolderCallback != null){
var cbResult = Boolean(actionPerFolderCallback(context));
if (failOnCallbackResult(cbResult)){
return false;
}
}
//Iterate thru files in thisFolder
for(var fileEnum = new Enumerator(context.folderObj.Files); !fileEnum.atEnd(); fileEnum.moveNext()){
//Get current file
context.fileObj = fso.GetFile(fileEnum.item());
//Invoke provided perFile callback function with current context
var cbResult = Boolean(actionPerFileCallback(context));
if (failOnCallbackResult(cbResult)){
return false;
}
}
//Recurse thru subfolders
if(recursive){
//Step into sub folder
for(var subFolderEnum = new Enumerator(context.folderObj.SubFolders); !subFolderEnum.atEnd(); subFolderEnum.moveNext()){
var subFolderObj = fso.GetFolder(subFolderEnum.item());
//New calling context that will be passed into recursive call
var newCallingContext;
//Provide caller a chance to update the calling context with the new subfolder before making the recursive call
if(updateContextFn != null){
newCallingContext = updateContextFn(subFolderObj, callingContext);
}
//Make recursive call
var cbResult = iterateFiles(subFolderObj.Path, true, actionPerFileCallback, actionPerFolderCallback, useFnReturnValue, newCallingContext, updateContextFn);
if (failOnCallbackResult(cbResult)){
return false;
}
}
}
return true; //if we made it here, then all callbacks were successful
};
Usage:
//Note: The 'lib' object used below is just a utility library I'm using.
function copyFolder(fromPath, toPath, overwrite, recursive){
var actionPerFileCallback = function(context){
var destinationFolder = context.callingContext.toPath;
var destinationPath = lib.buildPath([context.callingContext.toPath, context.fileObj.Name]);
lib.createFolderIfDoesNotExist(destinationFolder);
return copyFile(context.fileObj.Path, destinationPath, context.callingContext.overwrite);
};
var actionPerFolderCallback = function(context){
var destinationFolder = context.callingContext.toPath;
return lib.createFolderIfDoesNotExist(destinationFolder);
};
var callingContext = {
fromPath : fromPath,
toPath : lib.buildPath([toPath, fso.GetFolder(fromPath).Name]), //include folder in copy
overwrite : overwrite,
recursive : recursive
};
var updateContextFn = function(currentFolderObj, previousCallingContext){
return {
fromPath : currentFolderObj.Path,
toPath : lib.buildPath([previousCallingContext.toPath, currentFolderObj.Name]),
overwrite : previousCallingContext.overwrite,
recursive : previousCallingContext.recursive
}
}
return iterateFiles(fromPath, recursive, actionPerFileCallback, null, true, callingContext, updateContextFn);
};
I know this question is old but I stumbled upon it and hopefully my answer will help someone!

Related

Move Files in google script

i use the function MoveFiles() to copy file into other folder.
But when i ran it, i try to delete the file in the original folder. After deleted it, i saw that the file that i moved also deleted.
How to make the file that i moved not be deleted too?
Tqvm
function MoveFiles() {
var SourceFolder = DriveApp.getFolderById('1WIZxuF_r9I-510Kfw9N0AImcS1Uf63dC');
var SourceFiles = DriveApp.getFolderById('1QfFl5JIfOYaTXZyFpuBNSMzBdBrXLll9').getFiles();
var DestFolder = DriveApp.getFolderById('1_03PnkJlt6mTo5bAExUMOdZVVkzMAUsA');
while (SourceFiles.hasNext()) {
var file = SourceFiles.next();
DestFolder.addFile(file);
SourceFolder.removeFile(file);
}
}
Try switching the code line for delete and add. According to this related SO post:
I've found that I needed to reverse the last two lines (so the removeFile is done first) otherwise the removeFile actually just removes it from the folder it was just added to and not from the original parent.
I've tested it and actually get the correct result, here is my code snippet:
function myFunction() {
var folder = DriveApp.getFolderById('sourceID');
var destinationFolder = "destinationID";
var contents = folder.getFiles();
while (contents.hasNext()){
var file = contents.next();
moveFiles(file.getId(),destinationFolder);
}
}
function moveFiles(sourceFileId, targetFolderId) {
var file = DriveApp.getFileById(sourceFileId);
file.getParents().next().removeFile(file);
DriveApp.getFolderById(targetFolderId).addFile(file);
}
Hope this helps.

Pass array to another function

I am trying to change ownership of files in Google Drive, where my service account isn't owner of the file.
function getDriveFiles(folder, path) {
var folder = DriveApp.getFolderById("0B23heXhtbThYaWdxzMc");
var path = "";
var files = [];
var fileIt = folder.getFiles();
while ( fileIt.hasNext() ) {
var f = fileIt.next();
if (f.getOwner().getEmail() != "service#domain.com")
files.push({owner: f.getOwner().getEmail(), id: f.getId()});
}
return files;
}
So my array looks like this:
var files = [
{owner=jens#domain.com, id=CjOqUeno3Yjd4VEFrYzg},
{owner=jens#domain.com, id=CjOqUYWxWaVpTQ2tKc3c},
{owner=jens#domain.com, id=CjOqUNTltdHo2NllkcWs},
{owner=jens#domain.com, id=CjOqUVTRRMnU2Y0ZJYms},
{owner=jack#domain.com, id=CjOqUXzBmeE1CT0VLNkE},
{owner=aurora#domain.com, id=CjfKj4ur7YcttORkXTn8D2rvGE},
{owner=aurora#domain.com, id=CjOqUY3RFUFlScDBlclk}
]
Next function that i need to pass this array to is batchPermissionChange which will batch change the ownership to my service account. However i would like it to run batchPermissionChange per user. So if e.g jens#domain.com have 4 files, i don't want the batchPermissionChange function to be triggered 4 times, i would like it to trigger it one time with jens#domain.com, and include his four fileID's.
function batchPermissionChange(ownerEmail, filesArray){
Do batch job Google... https://www.googleapis.com/batch
}
Question
How do i run the function batchPermissionChange(ownerEmail, filesArray) with for e.g jens#domain.com with his 4 fileId's? I could loop through the array, like, 'for each item in array run batchPermissionChange', but that will trigger the batch-function 4 times for the user jens#domain.com.
When you retrieve the list of files, instead of pushing all the files into a single array, you can create a map of arrays, with the keys in the map being the owners, and the arrays being the list of files for that owner.
function getDriveFiles(folder, path) {
var folder = DriveApp.getFolderById("0B23heXhtbThYaWdxzMc");
var path = "";
var files = {};
var fileIt = folder.getFiles();
while (fileIt.hasNext()) {
var f = fileIt.next();
var owner = f.getOwner().getEmail();
var id = f.getId();
if (owner != "service#domain.com") {
// if the owner doesn't exist yet, add an empty array
if (!files[owner]) {
files[owner] = [];
}
// push the file to the owner's array
files[owner].push(id);
}
}
return files;
}
The files object will end up looking something like this:
{
'jens#domain.com': ['CjOqUeno3Yjd4VEFrYzg', 'CjOqUYWxWaVpTQ2tKc3c', 'CjOqUNTltdHo2NllkcWs', 'CjOqUVTRRMnU2Y0ZJYms'],
'jack#domain.com': ['CjOqUXzBmeE1CT0VLNkE'],
'aurora#domain.com': ['CjfKj4ur7YcttORkXTn8D2rvGE', 'CjOqUY3RFUFlScDBlclk']
}
Now, in the area of your code where you want to call batchPermissionChange, do it like this:
for(var ownerEmail in files) {
if(files.hasOwnProperty(ownerEmail)) {
// NOTE: I'm not sure what the first parameter should be for this, but
// this shows how to send the array of files for just one user at a
// time, so change the first parameter if I got it wrong.
batchPermissionChange(ownerEmail, files[ownerEmail]);
}
}

A property not initialized... yet it should be?

I have a list containing folders, and I'm trying to get the count of the total number of files in these folders.
I manage to retrieve a ListItemCollection containing my folders. Then it starts being... picky.
ctx is my ClientContext, and collection my ListItemCollection.
function countFiles()
{
var enumCollection = collection.getEnumerator();
while(enumCollection.moveNext())
{
currentItem = enumCollection.get_current();
var folder = currentItem.get_folder();
if (folder === 'undefined')
return;
ctx.load(folder, 'ItemCount');
ctx.executeQueryAsync(Function.createDelegate(this, function()
{
totalCount += folder.get_itemCount();
}), Function.createDelegate(this, onQueryFailed));
}
}
So it works... half of the time. If I have 6 items in my collection, I get 3 or 4 "The property or field 'ItemCount' has not been initialized" exceptions, and obviously my totalCount is wrong. I just can't seem to understand why, since the executeQueryAsync should not happen before the folder is actually loaded.
I'm very new to Javascript, so it may look horrid and be missing some essential code I didn't consider worthy of interest, feel free to ask if it is so.
Referencing closure variables (like folder in this case) from an asynchronous callback is generally a big problem. Thankfully it's easy to fix:
function countFiles()
{
function itemCounter(folder) {
return function() { totalCount += folder.get_itemCount(); };
}
var enumCollection = collection.getEnumerator();
while(enumCollection.moveNext())
{
var folder = enumCollection.getCurrent().get_folder();
if (folder === undefined) // not a string!
return;
ctx.load(folder, 'ItemCount');
ctx.executeQueryAsync(itemCounter(folder), Function.createDelegate(this, onQueryFailed));
}
}
(You don't need that .createDelegate() call because the function doesn't need this.)
Now, after that, you face the problem of knowing when that counter has been finally updated. Those asynchronous callbacks will eventually finish, but when? You could keep a separate counter, one for each query you start, and then decrement that in the callback. When it drops back to zero, then you'll know you're done.
Since SP.ClientContext.executeQueryAsync is an async function it is likely that the loop could be terminated before the first call to callback function completes, so the behavior of specified code could be unexpected.
Instead, i would recommend another and more clean approach for counting files (including files located under nested folders) using SharePoint JSOM.
How to count the total number of files in List using JSOM
The following function allows to count the number of list items in List:
function getItemsCount(listTitle, complete){
var ctx = SP.ClientContext.get_current();
var list = ctx.get_web().get_lists().getByTitle(listTitle);
var items = list.getItems(createQuery());
ctx.load(items);
ctx.executeQueryAsync(
function() {
complete(items.get_count());
},
function() {
complete(-1);
}
);
function createQuery()
{
var query = new SP.CamlQuery();
query.set_viewXml('<View Scope="RecursiveAll"><Query><Where><Eq><FieldRef Name="FSObjType" /><Value Type="Integer">0</Value></Eq></Where></Query></View>');
return query;
}
}
Usage
getItemsCount('Documents', function(itemsCount){
console.log(String.format('Total files count in Documents library: {0}',itemsCount));
});

this._get is not a function - javascript oop and prototypes

I'm using something similar to NodeJS called bondi, it's build on the Firefox js engine.. Basically i'm getting this error and I believe it's due to the way i'm referencing "this" in the .Get function below.
Basically there is a tool called SFtpClient. It has the method of "Get", to list the contents of a folder, but I want to change the prototype for this with a drop in include file. I need to change it so that it
a/ retries several times when it fails, and b/ it has a recursive folder listing function.
So I used the prototype to change it - moved .Get to ._Get.
Can anyone see why I would be getting the error:
Jan 23 04:51:34 beta bondi: === this._Get is not a function --- Prio(6) Result(0x0) File(/home/nwo/approot/include/sftpclientenh
when I run the code below?
Thanks
SFtpClient.prototype._Get = SFtpClient.prototype.Get;
SFtpClient.prototype.Get = function(Folder, Retries){
//defaults
if(!Retries) Retries = 5;
if(!Folder) Folder = "~/";
//vars
var FileListing = [];
var connect = function(){
//TODO JRF 19.01.2012 : re-enable this when bondi is fixed
// this.HomeDirectory.replace(/\/?$/, "/");
FileListing = this._Get(Folder);
return true;
}
var i = 1;
do{
var res = false;
try {
res = connect();
}catch(e){
Debug.LogInfo(e.message);
}
i++;
Server.Sleep(i*2000);
} while(res==false && i < Retries);
return FileListing;
}
Try res = connect.call(this) instead of res = connect().

Chrome Extension with Database API interface

I want to update a div with a list of anchors that I generate from a local database in chrome. It's pretty simple stuff, but as soon as I try to add the data to the main.js file via a callback everything suddenly becomes undefined. Or the array length is set to 0. ( When it's really 18. )
Initially, I tried to install it into a new array and pass it back that way.
Is there a setting that I need to specify in the chrome manifest.json in order to allow for communication with the database API? I've checked, but all I've been able to find was 'unlimited storage'
The code is as follows:
window.main = {};
window.main.classes = {};
(function(awe){
awe.Data = function(opts){
opts = opts || new Object();
return this.init(opts);
};
awe.Data.prototype = {
init:function(opts){
var self = this;
self.modified = true;
var db = self.db = openDatabase("buddy","1.0","LocalDatabase",200000);
db.transaction(function(tx){
tx.executeSql("CREATE TABLE IF NOT EXISTS listing ( name TEXT UNIQUE, url TEXT UNIQUE)",[],function(tx,rs){
$.each(window.rr,function(index,item){
var i = "INSERT INTO listing (name,url)VALUES('"+item.name+"','"+item.url+"')";
tx.executeSql(i,[],null,null);
});
},function(tx,error){
});
});
self._load()
return this;
},
add:function(item){
var self = this;
self.modified = true;
self.db.transaction(function(tx){
tx.executeSql("INSERT INTO listing (name,url)VALUES(?,?)",[item.name,item.url],function(tx,rs){
//console.log('success',tx,rs)
},function(tx,error){
//console.log('error',error)
})
});
self._load()
},
remove:function(item){
var self = this;
self.modified = true;
self.db.transaction(function(tx){
tx.executeSql("DELETE FROM listing where name='"+item.name+"'",[],function(tx,rs){
//console.log('success',tx,rs)
},function(tx,error){
//console.log('error',tx,error);
});
});
self._load()
},
_load:function(callback){
var self = this;
if(!self.modified)
return;
self.data = new Array();
self.db.transaction(function(tx){
tx.executeSql('SELECT name,url FROM listing',[],function(tx,rs){
console.log(callback)
for(var i = 0; i<rs.rows.length;i++)
{
callback(rs.rows.item(i).name,rs.rows.item(i).url)
// var row = rs.rows.item(i)
// var n = new Object()
// n['name'] = row['name'];
// n['url'] = row['url'];
}
},function(tx,error){
//console.log('error',tx,error)
})
})
self.modified = false
},
all:function(cb){
this._load(cb)
},
toString:function(){
return 'main.Database'
}
}
})(window.main.classes);
And the code to update the list.
this.database.all(function(name,url){
console.log('name','url')
console.log(name,url)
var data = []
$.each(data,function(index,item){
try{
var node = $('<div > '+item.name + '</div>');
self.content.append(node);
node.unbind();
node.bind('click',function(evt){
var t = $(evt.target).attr('href');
chrome.tabs.create({
"url":t
},function(evt){
self._tab_index = evt.index
});
});
}catch(e){
console.log(e)
}
})
});
From looking at your code above, I notice you are executing "self._load()" at the end of each function in your API. The HTML5 SQL Database is asynchronous, you can never guarantee the result. In this case, I would assume the result will always be 0 or random because it will be a race condition.
I have done something similar in my fb-exporter extension, feel free to see how I have done it https://github.com/mohamedmansour/fb-exporter/blob/master/js/database.js
To solve a problem like this, did you check the Web Inspector and see if any errors occurs in the background page. I assume this is all in a background page eh? Try to see if any error occurs, if not, I believe your encountering a race condition. Just move the load within the callback and it should properly call the load.
Regarding your first question with the unlimited storage manifest attribute, you don't need it for this case, that shouldn't be the issue. The limit of web databases is 5MB (last I recall, it might have changed), if your using a lot of data manipulation, then you use that attribute.
Just make sure you can guarantee the this.database.all is running after the database has been initialized.

Categories

Resources