I have searched and found several examples of how to do this, but I can't make them work - well part of it doesn't work.
I can perform the file upload, but the following attempt to change properties fail.
I'm attempting to upload a file from a base64 payload - this part works - but when I afterwards attempt to edit the properties (custom column) associated with the file, the code fails.
Here is the code (simplified for readability):
(note that props is a collection of custom objects (FileProperty) with a name and a value attribute).
using (ClientContext context = new ClientContext("<sharepoint_server_url>"))
{
context.Credentials = new SharePointOnlineCredentials(<usr>,<secure_pwd>);
using (System.IO.MemoryStream ms = new System.IO.MemoryStream(Convert.FromBase64String(<base64_content>)))
{
File.SaveBinaryDirect(context, <relative_path>, ms, true);
}
// file is uploaded - so far so good!
// attempt to edit properties of the file.
if (props != null)
{
if (props.Count > 0)
{
File newFile = context.Web.GetFileByServerRelativeUrl(<relative_path>);
context.Load(newFile);
context.ExecuteQuery();
newFile.CheckOut();
ListItem item = newFile.ListItemAllFields;
foreach (FileProperty fp in props)
{
item[fp.name] = fp.value;
}
item.Update();
newFile.CheckIn(string.Empty, CheckinType.OverwriteCheckIn);
}
}
}
This code throws an exception in the part where I try to update the properties.
Message: The file was not found.
Can anyone tell me what is wrong with this example or provide another example on how to do this?
Also, a question - is there a way to address a file by a unique ID which is the same regardless of where in the SharePoint server the file is located or moved to?
I hope someone can help me out - thanks :)
Ok, I found a solution to my problem. I don't know why this works better, it just does.
For all I know, I'm doing the exact same thing, just in another way - maybe someone else who knows more about SharePoint than me (which isn't much) can explain why this works while the first example I posted doesn't.
Previous to the code shown, I ensure that <site_url> doesn't end with "/" and that <library_name> doesn't start or end with "/" and that <file_name> doesn't start or end with "/".
With the code below I can uplaod a file and update properties, in my case i changed "Title" and a custom column "CustCulomnA" and it workes.
using (ClientContext context = new ClientContext(<site_url>))
{
context.Credentials = new SharePointOnlineCredentials(<usr>, <secure_pwd>);
FileCreationInformation fci = new FileCreationInformation()
{
Url = <file_name>,
Content = Convert.FromBase64String(<base64_content>),
Overwrite = true
};
Web web = context.Web;
List lib = web.Lists.GetByTitle(<library_name>);
lib.RootFolder.Files.Add(fci);
context.ExecuteQuery();
response.message = "uploaded";
if (props != null)
{
if (props.Count > 0)
{
File newFile = context.Web.GetFileByUrl(<site_url> +"/"+ <library_name> + "/" + <file_name>);
context.Load(newFile);
context.ExecuteQuery();
newFile.CheckOut();
ListItem item = newFile.ListItemAllFields;
foreach (FileProperty fp in props)
{
item[fp.name] = fp.value;
}
item.Update();
newFile.CheckIn(string.Empty, CheckinType.OverwriteCheckIn);
context.ExecuteQuery();
Make sure the file server relative url is valid in this case.
For example if the complete url is:
https://zheguo.sharepoint.com/sites/test/Shared%20Documents/test.jpg
Then relative url should be
/sites/test/Shared%20Documents/test.jpg
And you can also use GetFileByUrl method, passing the complete file url like this:
clientContext.Credentials = new SharePointOnlineCredentials(userName, securePassword);
Web web = clientContext.Web;
clientContext.Load(web);
clientContext.ExecuteQuery();
File file = web.GetFileByUrl("https://zheguo.sharepoint.com/sites/test/Shared%20Documents/test.jpg");
clientContext.Load(file);
clientContext.ExecuteQuery();
file.CheckOut();
ListItem item = file.ListItemAllFields;
item["Title"] = "Test";
item.Update();
file.CheckIn(string.Empty, CheckinType.OverwriteCheckIn);
clientContext.ExecuteQuery();
}
In my user interface (angularjs) I create new row. Each row have file upload button. I want to upload all files together with metadata and save each row in one call. The complex object which I post to Nodejs API is somewhat like below
var activity = {
"Id" : 1,
"Name" : "Test",
"Steps" : [
{
"StepId":1,
"FileUrl": {fileObject} // this property if bound with the file upload directive 'ng-file-upload' by Daniel Farid
"Description" : "Save this file"
},
{
"StepId":2,
"FileUrl": {fileObject} // this property if bound with the file upload directive 'ng-file-upload' by Daniel Farid
"Description" : "Save this file2"
}
]
}
This JSON will be posted to Node js API. On Nodejs side I am using multer to save the uploaded files to server. I get all the files in API using multer's .any() method, but I get the posted object without Steps[x].FileUrl property.
The file object that has the information about the field name in which this file was added. Below is the info I see in debugger.
Array[2]
length:2
[0]:Object
destination:"C:\DeleteThis\"
encoding:"7bit"
fieldname:"Steps[0][FileUrl]"
filename:"ed13d2a61cb38c43f1f46a221855a896"
mimetype:"image/png"
originalname:"deploy.png"
path:"C:\DeleteThis\ed13d2a61cb38c43f1f46a221855a896"
size:2347
[1]:Object
Now what I want to do it, since My complex object that is posted does not have Steps[0].FileUrl property, I want to iterate for each file (i.e. req.files) and use fieldname to create this property and assign the originalName as value to it.
How I am trying to do it
var deployment = req.body;
if(req.files){
var app = _config.getApplicationConfig(req.body.ApplicationId);
req.files.forEach(function(f){
//Move file to the deployment folder.
_utils.createDirIfNotExist(app.packageDir);
var newPath = _utils.DetermineFileName(f.originalname, app.packageDir);
_fs.renameSync(f.path, path.join(app.packageDir,newPath));
var newFileName = path.basename(newPath);
//set the file url to corresponding field
var evalExp = "deployment." + f.fieldname; //I get evalExpression as "deployment.Steps[0][FileUrl]"
eval(evalExp); //Here it fails saying FileUrl is not defined
evalExp = "deployment." + f.fieldname + "= \"" + newFileName.toString() + "\"";
eval(evalExp);
});
}
Does anyone know how can as assign the property to an object at run time?
I have found solution to this as below
I have wrote a function that converts the [] notation to . notation ie. myobj[myprop] to myobj.myprop
var convertToDotNotation = function (keyPath) {
var bracketSyntaxRegex = new RegExp(/\[([^0-9])\w+\]/g); //matches the javascript property defined in [] syntax but not an array
var matches = keyPath.match(bracketSyntaxRegex)
if(matches && matches.length > 0){
matches.forEach(function(p){
//replace '[' with '.' and ']' with emptyspace
var dotSyntax = p.replace("[",".").replace("]","");
keyPath = keyPath.replace(p,dotSyntax);
});
}
return keyPath;
}
This will give me the '.' notation which can dynamically create the property and set the value
var newFileName = "MyFile.pdf";
var evalExp = "deployment[0].[FileUrl]" ;
var temp = convertToDotNotation(evalExp);
eval(temp + "= \"" + newFileName + "\"");
Hope it helps someone.
I am trying to integrate an Array containing a list of image file types that can be used in my Extendscript, this is what I have:
DFgroup.DFBtn.onClick = function(){
DFdefault = new Folder(DARKinputFolder); //stores the current folder in case of cancel.
DFdefault = Folder.selectDialog('****Please Select The Folder Where Your DARK FRAMES Are Located****',DARKinputFolder);
if ( DFdefault != null ) {
DARKinputFolder = DFdefault.fsName.toString()
DFgroup.DFlabel.text = DARKinputFolder;
if(DFdefault){DARKinputFolder = DFdefault}; //if cancel is hit, this statement becomes false, and myFolder remains untouched.
//if it's true, then the selected folder from the holdFolder is transferred to myfolder.
var FILE_TYPE, FTlen i;
FILE_TYPE = [".orf", ".tif", ".tiff", ".jpg", ".jpeg"];
FTlen = FILE_TYPE.length;
for (i = 0; i < FTlen; i++){
var SEARCH_MASK = "*" + FILE_TYPE[i];}
var DARKfileList = DARKinputFolder.getFiles(SEARCH_MASK) ;
//if (DARKfileList.length <= 0)
//{
//var FILE_TYPE = ".jpg"; // The type of files that this script works on -- you can change
//var SEARCH_MASK = "*" + FILE_TYPE; // Image file filter to find only those files
//var DARKfileList = DARKinputFolder.getFiles(SEARCH_MASK);
//}
if (DARKfileList.length == 0)
{
alert('The DARK FRAME folder you have chosen is either empty or does not contain image files of the correct type.\r\rPlease locate & select the correct image folder.','DARK FRAME folder error');
DFgroup.DFlabel.text = 'No DARK FRAMES folder has been selected...';
DFdefault = '';
DFgroup.DFlabelQ.text = '';
}
else{
DFgroup.DFlabelQ.text = '[' + DARKfileList.length + ']';
var DFCurrFolder = (new File(DARKinputFolder)).fsName;
DFgroup.DFlabel.helpTip = 'Location of files to process:\r' + DFCurrFolder;
FolderChecker()
}}
} //opens the folder browse UI using you main folder.
When a user hits the 'DFBtn' an open folder dialog appears and they can choose a folder that contains images.
This folder path along with the number of images contained within it are retrieved and output as text label on a dialog box.
The problem is that through testing and choosing a folder that does contain an image and that image is of the allowed type, the script goes straight to the 'Alert' message stating that the "folder you have chosen is either empty....." etc..
Now if I alter a small section of the code as:
if (DARKfileList.length <= 0)
{
var FILE_TYPE, FTlen, i;
FILE_TYPE = [".orf", ".tif", ".tiff", ".jpg", ".jpeg"];
FTlen = FILE_TYPE.length;
for (i = 0; i < FTlen; i++){
var SEARCH_MASK = "*" + FILE_TYPE[i];}
var DARKfileList = DARKinputFolder.getFiles(SEARCH_MASK) ;}
The code now no longer goes straight to the 'Alert' message even if I deliberately choose a folder that is empty.
UPDATE
To aid in finding out what is going on I have added an extra line of code:
var FILE_TYPE, FTlen i;
FILE_TYPE = [".orf", ".tif", ".tiff", ".jpg", ".jpeg"];
FTlen = FILE_TYPE.length;
for (i = 0; i < FTlen; i++){
var SEARCH_MASK = "*" + FILE_TYPE[i];}
var DARKfileList = DARKinputFolder.getFiles(SEARCH_MASK);
alert (SEARCH_MASK);
By just adding this line alert (SEARCH_MASK); straight away I had an insight to what is occurring.
Yes the FILE_TYPE Array is being iterated through but then the SEARCH_MASK is formed using just the last FILE_TYPE in the array, so in the case above the SEARCH_MASK is always created as:
*.jpeg
Therefore it will only be looking for files of that type within the chosen folder.
This is not what I am trying to achieve.
I want to know if the chosen folder contains ANY files of ANY of the types listed in the array.
For instance, the chosen folder could contain all *.tif files or even one of each file type.
How can I achieve what I require the code to do?
Regards..,
UPDATE 2 BOILED DOWN SCRIPT
Here's a boiled down script as requested:
var DARKfileList = '';
var AllowedFileTypes = Array( "orf", "tif", "tiff", "jpg", "jpeg" );
var dlgM = new Window('dialog','***###***###',undefined,{closeButton:false});
dlgM.center();
var DARKinputFolder = new Folder('/C/'); //create a folder to have the dialog start on.
var DFdefault = new Folder(); //create a holding folder for canceling.
DFg = dlgM.add('panel {text:"DARK FRAMES",preferredSize:[450,15]}');
DFg.margins = [5,10,5,10];
DFg.alignChildren = 'left';
DFgroup = DFg.add('group');
DFgroup.DFBtn = DFgroup.add('button{text:"Select Folder...",preferredSize:[100,25]}');
DFgroup.DFBtn.helpTip = 'Please select your DARK FRAMES folder.';
DFgroup.DFlabel = DFgroup.add( 'statictext', undefined, DARKinputFolder, { truncate:'middle' } );
DFgroup.DFlabel.helpTip = 'Location of files to process';
DFgroup.DFlabel.preferredSize.width = '275';
DFgroup.DFlabel.text = 'No DARK FRAMES folder has been selected...';
DFgroup.DFlabelQ = DFgroup.add( 'statictext', undefined, '');
DFgroup.DFlabelQ.helpTip = 'Number of DARK FRAMES to process';
DFgroup.DFlabelQ.preferredSize.width = '40';
DFgroup.DFlabelQ.text = '';
BTNg = dlgM.add('group {alignment: "center"}');
BTNg.ExitBtn = BTNg.add('button{text:"Exit",preferredSize:[105,25]}');
BTNg.ProcessBtn = BTNg.add('button{text:"Process",preferredSize:[105,25]}');
BTNg.ProcessBtn.enabled = false;
DFgroup.DFBtn.onClick = function(){
DFdefault = new Folder(DARKinputFolder); //stores the current folder in case of cancel.
DFdefault = Folder.selectDialog('****Please Select The Folder Where Your DARK FRAMES Are Located****',DARKinputFolder);
if ( DFdefault != null ) {
DARKinputFolder = DFdefault.fsName.toString()
DFgroup.DFlabel.text = DARKinputFolder;
if(DFdefault){DARKinputFolder = DFdefault}; //if cancel is hit, this statement becomes false, and myFolder remains untouched.
//if it's true, then the selected folder from the holdFolder is transferred to myfolder.
var FTlen, i;
FTlen = AllowedFileTypes.length;
for (i = 0; i < FTlen; i++){
var SEARCH_MASK = "*." + AllowedFileTypes[i]};
var DARKfileList = DARKinputFolder.getFiles(SEARCH_MASK) ;
if (DARKfileList.length == 0)
{
alert('The DARK FRAME folder you have chosen is either empty or does not contain image files of the correct type.\r\rPlease locate & select the correct image folder.','DARK FRAME folder error');
DFgroup.DFlabel.text = 'No DARK FRAMES folder has been selected...';
DFdefault = '';
DFgroup.DFlabelQ.text = '';
}
else{
DFgroup.DFlabelQ.text = '[' + DARKfileList.length + ']';
var DFCurrFolder = (new File(DARKinputFolder)).fsName;
DFgroup.DFlabel.helpTip = 'Location of files to process:\r' + DFCurrFolder;
FolderChecker()
}}
} //opens the folder browse UI using you main folder.
BTNg.ExitBtn.onClick = function(){
dlgM.close();
}
function FolderChecker(){
if (DFdefault.exists){
BTNg.ProcessBtn.enabled = true;
}
}
BTNg.ProcessBtn.onClick = function(){
// THIS IS WHERE IMAGE PROCESSING STARTS -- REMOVED THIS HUGE SECTION FOR BREVITY
}
dlgM.show();
UPDATE 3 Final Issue
As per my comment about the need to check for illegal file types etc, here's what I have so far:
if (DARKfileList.length == 0)
{
alert('empty folder!','DARK FRAME folder error');
DFgroup.DFlabel.text = 'No DARK FRAMES folder has been selected...';
DFdefault = '';
DFgroup.DFlabelQ.text = '';
}
else if (DARKfileList.length > 1)
{
alert('too many file types!','DARK FRAME folder error');
DFgroup.DFlabel.text = 'No DARK FRAMES folder has been selected...';
DFdefault = '';
DFgroup.DFlabelQ.text = '';
}
else{
DFgroup.DFlabelQ.text = '[' + DARKfileList.length + ']';
var DFCurrFolder = (new File(DARKinputFolder)).fsName;
DFgroup.DFlabel.helpTip = 'Location of files to process:\r' + DFCurrFolder;
FolderChecker()
}
Now this works perfectly if the chosen folder contains only one file & that file is not in the FILE_TYPE array as DARKfileList.length returns zero. As Required
Similarly if the folder contains more than one of any file no matter what type the check fails. As Required
However, if a chosen folder contains one allowed FILE_TYPE & one file type that is not in the FILE_TYPE array the check fails. Needs Sorting Out
I need something along the lines of:
if (DARKfileList != FILE_LIST)
{
alert('Illegal file type!);
DFgroup.DFlabel.text = 'No DARK FRAMES folder has been selected...';
DFdefault = '';
DFgroup.DFlabelQ.text = '';
}
The problem is that each iteration you overriding the resulting search mask.
You need to merge all the resulting into one array, and then to check if your array is empty:
var DARKfileList = [], FILE_TYPE, FTlen, i, SEARCH_MASK;
FILE_TYPE = ["orf", "tif", "tiff", "jpg", "jpeg"];
FTlen = FILE_TYPE.length;
for (i = 0; i < FTlen; i++) {
SEARCH_MASK = "*." + FILE_TYPE[i];
DARKfileList = DARKfileList.concat(DARKinputFolder.getFiles(SEARCH_MASK));
}
alert('found: ' + DARKfileList.length + ' files');
Alternative: getFiles can get regex as parameter, so if you familiar with regex you can simply use the following regex:
/.+\.(?:jpe?g|tiff?|orf)$/i
Regex explanation: match in the filename any character, then dot character, and then one of the extension option and then the i flag used to ignore case sensitive. In your example you get only files that the extension is in lowercase.
To add another extensions to the regex, just add pipe(|) after orf and then your extension. ex: to add pdf extension:
/.+\.(?:jpe?g|tiff?|orf|pdf)$/i
Usage of regex in your code:
DARKfileList = DARKinputFolder.getFiles(/.+\.(?:jpe?g|tiff?|orf|pdf)$/i);
You can also use array to store all of your extensions and use it like:
var regex = new RegExp('.+\.(?:' + FILE_TYPE.join('|')+ ')$','i');
DARKfileList = DARKinputFolder.getFiles(regex);
I currently loop through the list of google drive folders and return them all with no issues using the following...
FilesResource.ListRequest listRequest = drive.Files.List();
listRequest.Q = "mimeType='application/vnd.google-apps.folder'";
listRequest.PageSize = 1000;
listRequest.Fields = "nextPageToken, files(mimeType, id, name, parents)";
FileList listResults = null;
do
{
if (taskInfo.Cancel.IsCancellationRequested) return;
listResults = listRequest.Execute();
IList<Google.Apis.Drive.v3.Data.File> gObjects = listResults.Files;
if (gObjects != null && gObjects.Count > 0)
{
Debug.WriteLine("Got folder batch of " + gObjects.Count + " folders.");
}
} while (listRequest.PageToken != null);
The only issue I have is I would really like to know if there was a query of some type I could use to ONLY return the COUNT of folders on my Google Drive BEFORE I start listing them in a batch. Is there one?
Try to use Google Drive API Search functionality. So if you're looking for folders you can try:
mimeType = 'application/vnd.google-apps.folder' then perform a count.
This is an idomism question regarding groovy - what is the simplest way to check the last char of a String and if present not append it to a modified String?
For example in Java you could do some like this;
String url = "http://www.google.com"
//check to see if url already has / at the end
if (url.charAt(url.size()-1)=='/')
url =url + "Media"
else
url = "/Media"
def url = 'http://test.com/'
url += url?.endsWith('/') ? 'Media' : '/Media'
//This should work as well but it would throw an
//ArrayIndexOutOfBoundException if url is empty ('')
//url += url[-1] == '/' ? 'Media' : '/Media'
println url