Google Apps Script To Copy Entire Google Drive File Structure; How To Avoid Timeouts? - recursion

My organization is switching to a Google Business account, and everyone needs to transfer their Drive files to their new accounts. Drive will not allow transfer of ownership between these accounts, so I've created a script to copy files and folders from the old account to the new account. (The old account's contents have been moved into a folder shared with the new account.)
Here's what I have so far:
function copyDrive() {
var originFolder = DriveApp.getFolderById(originFolderID);
var destination = DriveApp.getFolderById(destinationID);
copyFiles(originFolder, destination);
};
function copyFiles(passedFolder, targetFolder) {
var fileContents = passedFolder.getFiles();
var file;
var fileName;
while(fileContents.hasNext()) {
file = fileContents.next();
fileName = file.getName();
file.makeCopy(fileName, targetFolder);
}
copySubFolders(passedFolder, targetFolder);
};
function copySubFolders(passedFolder, targetFolder) {
var folderContents = passedFolder.getFolders();
var folder;
var folderName;
while(folderContents.hasNext()) {
folder = folderContents.next();
folderName = folder.getName();
var subFolderCopy = targetFolder.createFolder(folderName);
copyFiles(folder, subFolderCopy);
}
};
Please pardon any inelegance; I am new at this. The script actually works great and preserves the folder structure, but it times out after copying ~150 files and folders. I've been looking into how to use continuation tokens, and I've read this post closely. I think I'm stuck on a conceptual level, because I'm not sure how the continuation tokens will interact with the recursive functions I've set up. It seems like I will end up with a stack of my copySubFolders function, and they will each need their own continuation tokens. Of course they all use the same variable name for their iterators, so I really have no idea how to set that up.
Any thoughts? Sorry for posting such a helpless newbie question; I hope it will at least be an interesting problem for someone.

I think I have solved the conceptual problem, though I am getting
We're sorry, a server error occurred. Please wait a bit and try again. (line 9, file "Code")
when I try to execute it.
Basically, I set it up to only try to copy one top-level folder at a time, and for each one of those it uses the recursive functions I had before. It should save continuation tokens for that first level of folders and any files in the root folder so it can pick up in the next execution where it left off. This way, the tokens are not involved in my recursive stack of functions.
function copyDrive() {
var originFolder = DriveApp.getFolderById(originFolderID);
var destination = DriveApp.getFolderById(destinationID);
var scriptProperties = PropertiesService.getScriptProperties();
var fileContinuationToken = scriptProperties.getProperty('FILE_CONTINUATION_TOKEN');
var fileIterator = fileContinuationToken == null ?
originFolder.getFiles() : DriveApp.continueFileIterator(fileContinuationToken);
var folderContinuationToken = scriptProperties.getProperty('FOLDER_CONTINUATION_TOKEN');
var folderIterator = folderContinuationToken == null ?
originFolder.getFolders() : DriveApp.continueFolderIterator(folderContinuationToken);
try {
var rootFileName;
while(fileIterator.hasNext()) {
var rootFile = fileIterator.next();
rootFileName = rootFile.getName();
rootFile.makeCopy(rootFileName, destination);
}
var folder = folderIterator.next();
var folderName = folder.getName();
var folderCopy = folder.makeCopy(folderName, destination);
copyFiles(folder, folderCopy);
} catch(err) {
Logger.log(err);
}
if(fileIterator.hasNext()) {
scriptProperties.setProperty('FILE_CONTINUATION_TOKEN', fileIterator.getContinuationToken());
} else {
scriptProperties.deleteProperty('FILE_CONTINUATION_TOKEN');
}
if(folderIterator.hasNext()) {
scriptProperties.setProperty('FOLDER_CONTINUATION_TOKEN', folderIterator.getContinuationToken());
} else {
scriptProperties.deleteProperty('FOLDER_CONTINUATION_TOKEN');
}
};
function copyFiles(passedFolder, targetFolder) {
var fileContents = passedFolder.getFiles();
var file;
var fileName;
while(fileContents.hasNext()) {
file = fileContents.next();
fileName = file.getName();
file.makeCopy(fileName, targetFolder);
}
copySubFolders(passedFolder, targetFolder);
};
function copySubFolders(passedFolder, targetFolder) {
var subFolderContents = passedFolder.getFolders();
var subFolder;
var subFolderName;
while(folderContents.hasNext()) {
subFolder = subFolderContents.next();
subFolderName = subFolder.getName();
var subFolderCopy = targetFolder.createFolder(folderName);
copyFiles(subFolder, subFolderCopy);
}
};

I know you would like a easy, programmatic way to do this, but it may be easiest to install Google Drive for Desktop and have them right-click, copy, paste.
The idea:
Create a single folder in which the user puts every item of their Drive. (I see you have already done that.)
Share that folder with their new account. (I see you have already done that, as well.)
Sign into their new account with Drive for Desktop.
Copy the folder in Drive for Desktop and paste it right back in. Ownership gets transferred to the new account.
Just a thought.

You're going to need to store an array of folder iterators and file iterators since each folder could have a nested array of folders. If you're reusing the same folder iterator as in the accepted solution, you won't be able to resume on more top level folders.
Take a look at my answer here for a template that you can use to recursively iterate over all the files in a drive with resume functionality built-in.

Related

How should I get and set a folder's group and user permission settings via Core Service?

I can get strings representing group and user permissions for a given folder with the following.
Code
// assumes Core Service client "client"
var folderData = client.Read("tcm:5-26-2", new ReadOptions()) as FolderData;
var accessControlEntryDataArray =
folderData.AccessControlList.AccessControlEntries;
Console.WriteLine(folderData.Title);
foreach (var accessControlEntryData in accessControlEntryDataArray)
{
Console.WriteLine("{0} has {1}",
accessControlEntryData.Trustee.Title,
accessControlEntryData.AllowedPermissions.ToString());
}
Output
Some Folder
Everyone has Read
Editor has None
Chief Editor has None
Publication Manager has None
Interaction Manager has None
T2011-CB-R2\areyes has All
[scope] Editor 020 Create has Read, Write
T2011-CB-R2\local1 has Read, Write, Delete
[rights] Author - Content has None
Seems like the four possible values for `AllowedPermissions are:
None
Read
Read, Write
Read, Write, Delete
All
This works great for my use case to create a folder permissions report. I can .Replace() these to a familiar notation (e.g. rw-- or rwdl).
But is manipulating these string values the right approach to set permissions as well? I'd imagine I'd want objects or maybe enums instead. Could someone point me in the right direction?
Also I noticed I get some, but not all non-applicable groups set as None. I don't specifically need them here, but I'm curious at what determines whether those get returned--did I miss something in my code?
Rights and Permissions are enums, indeed. You can set using the method below. If you want to set multiple rights you should do something like "Rights.Read | Rights.Write"
Keep in mind that this method will return you object that you have to save \ update \ create after
public static OrganizationalItemData SetPermissionsOnOrganizationalItem(
OrganizationalItemData organizationalItem,
TrusteeData trustee,
Permissions allowedPermissions,
Permissions deniedPermissions = Permissions.None)
{
if (organizationalItem.AccessControlList == null)
{
organizationalItem.AccessControlList
= new AccessControlListData
{AccessControlEntries = new AccessControlEntryData[0]};
}
var entries = organizationalItem.AccessControlList
.AccessControlEntries.ToList();
// First check if this trustee already has some permissions
var entry = entries.SingleOrDefault(
ace => ace.Trustee.IdRef == trustee.Id);
if (entry != null)
{
// Remove this entry
entries.Remove(entry);
}
entries.Add(new AccessControlEntryData
{
AllowedPermissions = allowedPermissions,
DeniedPermissions = deniedPermissions,
Trustee = new LinkToTrusteeData { IdRef = trustee.Id }
});
organizationalItem.AccessControlList.AccessControlEntries
= entries.ToArray();
return organizationalItem;
}

Flex mobile : how to know it is the very first time running the application

I googled but didn't find a post for Flex mobile..
All I want for now is display an user agreement popup from TabbedViewNavigatorApplication when the user uses the app for the first time
var agreementView: UserAgreement = new UserAgreement();
PopUpManager.addPopUp(agreementView, this,true);
PopUpManager.centerPopUp(agreementView);
but maybe more later.
Please help..
What i did in my desktop air app;
I guess this will work at a mobile app also.
Make sure you have write access;
open yourproject-app.mxml scroll down to the end of the document. In the section, uncomment the following permission:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
Now you can create files like for example an sqlite database.
At the applicationcomplete call the function checkFirstRun:
// functions to check if the application is run for the first time (also after updates)
// so important structural changes can be made here.
public var file:File;
public var currentVersion:Number;
private function checkFirstRun():void
{
//get the current application version.
currentVersion=getApplicationVersion();
//get versionfile
file = File.applicationStorageDirectory;
file= file.resolvePath("Preferences/version.txt");
if(file.exists)
{
checkVersion();
}
else
{
firstRun(); // create the version file.
}
}
public function getApplicationVersion():Number
{
var appXML:XML = NativeApplication.nativeApplication.applicationDescriptor;
var ns:Namespace = appXML.namespace();
var versionnumber:Number =Number(appXML.ns::versionNumber);
return versionnumber;
}
private function checkVersion():void
{
var stream:FileStream= new FileStream();
stream.open(file,FileMode.READ);
var prevVersion:String = stream.readUTFBytes(stream.bytesAvailable);
stream.close();
if(Number(prevVersion)<currentVersion)
{
// if the versionnumber inside the file is older than the current version we go and run important code.
// like alternating the structure of tables inside the sqlite database file.
runImportantCode();
//after running the important code, we set the version to the currentversion.
saveFile(currentVersion);
}
}
private function firstRun():void
{
// at the first time, we set the file version to 0, so the important code will be executed.
var firstVersion:Number=0;
saveFile(firstVersion);
// we also run the checkversion so important code is run right after installing an update
//(and the version file doesn't exist before the update).
checkFirstRun();
}
private function saveFile(currentVersion:Number):void
{
var stream:FileStream=new FileStream();
stream.open(file,FileMode.WRITE);
stream.writeUTFBytes(String(currentVersion));
stream.close();
}
private function runImportantCode():void
{
// here goes important code.
// make sure you check if the important change previously has been made or not, because this code is run after each update.
}
Hope this helps.
Greets, J.
Some you need to store whether the user has agreed to the agreement or not. IF they haven't agreed, then show it.
One way to do this would be to store a value in a shared object. Another way to do this would be to use a remote service and store such data in a central repository. I assume you'll want the second; so you can do some form of tracking against the number of users using your app.

How do you change the Repository link on Alfresco Share?

In Alfresco Share, when you click the "Repository" icon in the toolbar, you're taken to:
/share/page/repository
I would like to change this link to take the user to their home folder, example:
/share/page/repository#filter=path|/User%2520Homes/g/gi/gillespie/patrick.j.gillespie
I figured this would be a simple change, however, I'm pulling my hair out trying to figure out how to change the link. Does anyone know what I edit to change that link?
Update: So I can update the link via the share-config-custom.xml file, changing this line:
<item type="link" id="repository">/repository</item>
But I'm not sure how to get the folder path information in there. Does anyone have any ideas?
This was surprisingly difficult, so I figured I'd post what I did in case anyone else has the same problem.
If you're not using hashed home folders, you can provide this functionality by simply updating your share-config.xml or share-config-custom.xml like so:
<item type="link" id="repository">/repository#filter=path|/User%2520Homes/{userid}</item>
However, if you're using hashed home folders, things become a little tricky. Instead of modifying any XML files, you'll instead create a web script to get the user's home folder path and then modify the share template file to replace the links to the repository. Below is a sample web script and the modifications to the template file that are needed in order to do this.
hfp.get.desc.xml
<webscript>
<shortname>Home Folder Path</shortname>
<description>Description here.</description>
<format default="json">argument</format>
<url>/demo/get-home-folder-path</url>
<authentication>user</authentication>
</webscript>
hfp.get.js
This script traverses up the node tree and puts together the folder's path.
var myPerson = person;//used people.getPerson("patrick.j.gillespie") for testing
var currentNode = myPerson.properties.homeFolder;
var myDir = currentNode.properties["{http://www.alfresco.org/model/content/1.0}name"];
var count = 0;
while (count < 100) { // prevent an infinite loop
currentNode = currentNode.parent;
if (currentNode === undefined || currentNode === null) {break;}
if (currentNode.properties === undefined) {break;}
myDir = currentNode.properties["{http://www.alfresco.org/model/content/1.0}name"] + "/" + myDir;
count++;
}
if (count === 100) { //something went wrong
myDir = "";
}
model.homeFolder = myDir;
hfp.get.json.ftl
{
"homeFolder": "${homeFolder}"
}
Finally, you'll need to modify the "alfresco-template.ftl" file, located in "share/WEB-INF/classes/alfresco/templates/org/alfresco/include". Near the bottom of the file, add the code below. It will call the above web script to fetch the home folder path, and then update the Repository links with the home folder link.
<script type="text/javascript">
var callback = {
success: function(res) {
var data = YAHOO.lang.JSON.parse(res.responseText);
var homeFolder = "";
var hfIndex = data.homeFolder.indexOf("/User Homes/");
if (hfIndex !== -1) {
homeFolder = data.homeFolder.substr(hfIndex+12);
}
var repoLinks = $("a[title='Repository']");
for (var ii = 0; ii < repoLinks.length; ii++) {
repoLinks.get(ii).href = "/share/page/repository#filter=path|/User%2520Homes/" + homeFolder;
}
}
};
var sUrl = Alfresco.constants.PROXY_URI + "demo/get-home-folder-path";
var postData = "";
var getData = "";
var request = YAHOO.util.Connect.asyncRequest('GET', sUrl+getData, callback, postData);
</script>
There may possibly be a better way, but I was unable to find it. I'll probably refine it later, and this should mostly just be used as a starting point, but I figured I'd post it in case anyone had a similar problem.
Although not super elegant, I guess you can append parameters or anchors using a Javascript onclick handler on the link. Not quite sure which webscript renders the toolbar, but that one may be a good place to put the customization.

Flex 3 & Air 2: Automatically detect when files on a directory are updated

A crazy idea just dropped from the sky and hit me in the head xD. I was wondering if it is possible to make and App capable of listening when the user "adds" new files to a directory.
Example:
The User opens up our Application.
The user adds new files on the desktop (using the Microsoft Explorer).
Our application automatically detects that new files have been added and executes a function or whatever.
Sound interesting right?
Maybe, this could be done using a programming language like Visual Basic and open the executable with the NativeProcess api and listen for an stdOut event... (:
Anyone got and idea to share with us? :)
Thanks
Lombardi
AIR can handle this natively...
the FileSystemList class fires an event directoryChange whenever a file in the watched directory changes.
You can even use it to watch for drives being mounted (I think Christian Cantrell showed that one off)
Ok, I think I'm getting closer, check out this solution! :)
private var CheckDelay:Timer = new Timer(5000, 0);
private function InitApp():void
{
CheckDelay.addEventListener(TimerEvent.Timer, CheckForNewFiles, false, 0, true);
CheckDelay.start();
}
private function CheckForNewFiles(event:TimerEvent):void
{
var FS:FileStream = new FileStream();
var Buffer:File = File.applicationStorageDirectory.resolvePath("FilesBuffer.cmd");
FS.open(Buffer, FileMode.Write);
FS.writeUTFBytes("cd " + File.desktopDirectory.nativePath + "\r\n" +
"dir /on /b > " + File.applicationStorageDirectory.resolvePath("FileList.txt").nativePath);
FS.close();
var Process:NativeProcess = new NativeProcess();
var NPI:NativeProcessStartupInfo = NativeProcessStartupInfo(); // What a large name! xD
NPI.executable = Buffer;
Process.start(NPI);
Process..addEventListener(NativeProcessExitEvent.EXIT, ReadFileList, false, 0, true);
}
private function ReadFileList(event:Event):void
{
var FS:FileStream = new FileStream();
var Buffer:File = File.applicationStorageDirectory.resolvePath("FilesBuffer.cmd");
FS.open(Buffer, FileMode.Read);
var FileData:String = FS.readUTFBytes(FS.bytesAvailable);
FS.close();
var FileArray:Array = FileData.split("\r\n");
var TempArray:ArrayCollection = new ArrayColletion();
var TempFile:File;
for(var i:int = 0;i<FileArray.length;i++){
TempFile = new File(FileArray[i]);
TempArray.addItem(TempFile);
}
}
At the end we got an Array (TempArray) that we could use on a datagrid (for example) with colums like: "extension, File Name, FilePath, etc.."
The files are updated every 5 seconds.
And, why we use all that code instead of a simple "File.getDirectoryListing()"? Because we are updating our application every 5 seconds, if why use getDirectoryListing(), our application will take much more RAM and also, the cmd command is much faster... :)
If you have a better idea, please share it with us! Thank you! :D
1 excellent solution for Windows: use Visual Studio, build the .net app found here http://msdn.microsoft.com/en-us/library/system.io.filesystemwatcher.aspx
In Adobe AIR use the native process to listen for change events dispatched by .net

FLEX: getting a folder size

I'm triying to get a folder size by doing:
var FolderFile:File = new File("file:///SomePath/Folder");
var FolderSize: FolderFile.size;
But this gives me a value of 0, how can I get the folder size? is there anyway to do this?
Tranks
No, there's no way to do it automagically. Getting the size of the directory is a complex and potentially painfully slow operation. There could be 10s of thousands of files in a directory, or a directory could be located on a (slow?) network, not to mention tape storage and similar scenarios.
The file systems themselves don't store directory size information, and the only way to know it is to calculate it file-by-file, there's no quick/easy shortcut. So, you will have to rely on the solution you posted above, and, yes, it is going to be slow.
I want to know the size of the folder (like 10mb). Sorry for the second line, I write it wrong, it's:
var Foldersize:Number = FolderFile.size;
I just made a new class wich executes this function:
public function GetFolderSize(Source:Array):Number
{
var TotalSizeInteger:Number = new Number();
for(var i:int = 0;i<Source.length;i++){
if(Source[i].isDirectory){
TotalSizeInteger += this.GetFoldersize(Source[i].getDirectoryListing());
}
else{
TotalSizeInteger += Source[i].size;
}
}
return TotalSizeInteger;
}
In "Source" you pass the FolderFile.getDirectoryListing(), something like this:
var CC:CustomClass = new CustomClass();
var FolderSize:Number = CustomClass.GetFolderSize(FolderFile.getDirectoryListing());
But this is a very slow method, is there a more quick and easy way to know the folder size?
Sorry for my grammar, i'm just learning english.
Thanks

Resources