I am in the process of writing a custom event receiver. The basic flow is as follows:
Document is added to Library
Based on metadata of document, we check to see if a folder within another document library exists.
If the folder does not exist, it is created.
The newly added document is copied to the folder residing in another document library.
I have got myself to the point, where I can copy newly added files, from one document library to another when they are added. However I cannot figure out how to copy to a specific directory (by name) within a document library. Any help would be greatly received.
Here is my code so far:
SPFile sourceFile = properties.ListItem.File;
SPFile destFile; // Copy file from source library to destination
using (Stream stream = sourceFile.OpenBinaryStream())
{
var destLib = (SPDocumentLibrary) properties.ListItem.Web.Lists[listName];
destFile = destLib.RootFolder.Files.Add(sourceFile.Name, stream);
stream.Close();
}
// Update item properties
SPListItem destItem = destFile.Item;
SPListItem sourceItem = sourceFile.Item;
// Copy meta data
destItem["Title"] = sourceItem["Title"];
//...
//... destItem["FieldX"] = sourceItem["FieldX"];
//...
destItem.UpdateOverwriteVersion();
Answer
//Ensure folder here
var destFolder = destLib.RootFolder.SubFolders["name"];
destFile = destFolder.Files.Add(sourceFile.Name, stream);
Related
Hello everyone and thanks for your help in advance. I am trying to use iText to merge all Pdf files contained within a directory. Here is my code:
public class MergeFiles
{
public MergeFiles(string targetDirectory) {
string dest = targetDirectory + #"\Merged.pdf";
PdfDocument pdfDoc = new PdfDocument(new PdfWriter(dest));
PdfMerger merger = new PdfMerger(pdfDoc);
string[] fileEntries = Directory.GetFiles(targetDirectory);
foreach (string fileName in fileEntries) {
//PdfMerger merger = new PdfMerger(pdfDoc);
PdfDocument newDoc = new PdfDocument(new PdfReader(fileName));
merger.Merge(newDoc, 1, newDoc.GetNumberOfPages());
newDoc.Close();
};
pdfDoc.Close();
}
}
This code is resulting in the error "System.IO.IOException: The process cannot access the file 'E:\Merged.pdf' because it is being used by another process." however, I am not sure why. Any help would be appreciated.
After these two lines:
string dest = targetDirectory + #"\Merged.pdf";
PdfDocument pdfDoc = new PdfDocument(new PdfWriter(dest));
A new (empty) file with name "Merged.pdf" is created in your target directory, with file stream opened in writing mode to write the result of the merging process.
Then, you are getting the list of file in target directory with string[] fileEntries = Directory.GetFiles(targetDirectory);. This array already includes your newly created Merged.pdf file.
Eventually the code tries to merge the resultant file into itself, which obviously fails.
To avoid this error, either collect the files to merge before creating the target document (but make sure there is no existing "Merged.pdf" file in the target directory already):
string[] fileEntries = Directory.GetFiles(targetDirectory);
string dest = targetDirectory + #"\Merged.pdf";
PdfDocument pdfDoc = new PdfDocument(new PdfWriter(dest));
// The rest of the code
Or, simply remove the target file from fileEntries array manually before merging the files.
I'm trying to work with text files in the apps folder.
Here's my GoogleApiClient constructor:
googleApiClient = new GoogleApiClient.Builder(this)
.AddApi(DriveClass.API)
.AddScope(DriveClass.ScopeFile)
.AddScope(DriveClass.ScopeAppfolder)
.UseDefaultAccount()
.AddConnectionCallbacks(this)
.EnableAutoManage(this, this)
.Build();
I'm connecting with:
googleApiClient.Connect()
And after:
OnConnected()
I need to list all files inside the app folder. Here's what I got so far:
IDriveFolder appFolder = DriveClass.DriveApi.GetAppFolder(googleApiClient);
IDriveApiMetadataBufferResult result = await appFolder.ListChildrenAsync(googleApiClient);
Which is giving me the files metadata.
But after that, I don't know how to read them, edit them or save new files. They are text files created with my app's previous version (native).
I'm following the google docs for drive but the Xamarin API is a lot different and has no docs or examples. Here's the API I'm using: https://components.xamarin.com/view/googleplayservices-drive
Edit:
Here is an example to read file contents from the guide:
DriveFile file = ...
file.open(mGoogleApiClient, DriveFile.MODE_READ_ONLY, null)
.setResultCallback(contentsOpenedCallback);
First I can't find anywhere in the guide what "DriveFile file = ..." means. How do I get this instance? DriveFile seems to be a static class in this API.
I tried:
IDriveFile file = DriveClass.DriveApi.GetFile(googleApiClient, metadata.DriveId);
This has two problems, first it complains that GetFile is deprecated but doesn't say how to do it properly. Second, the file doesn't have an "open" method.
Any help is appreciated.
The Xamarin binding library wraps the Java Drive library (https://developers.google.com/drive/), so all the guides/examples for the Android-based Drive API work if you keep in mind the Binding's Java to C# transformations:
get/set methods -> properties
fields -> properties
listeners -> events
static nested class -> nested class
inner class -> nested class with an instance constructor
So you can list the AppFolder's directory and files by recursively using the Metadata when the drive item is a folder.
Get Directory/File Tree Example:
await Task.Run(() =>
{
async void GetFolderMetaData(IDriveFolder folder, int depth)
{
var folderMetaData = await folder.ListChildrenAsync(_googleApiClient);
foreach (var driveItem in folderMetaData.MetadataBuffer)
{
Log.Debug(TAG, $"{(driveItem.IsFolder ? "(D)" : "(F)")}:{"".PadLeft(depth, '.')}{driveItem.Title}");
if (driveItem.IsFolder)
GetFolderMetaData(driveItem.DriveId.AsDriveFolder(), depth + 1);
}
}
GetFolderMetaData(DriveClass.DriveApi.GetAppFolder(_googleApiClient), 0);
});
Output:
[SushiHangover.FlightAvionics] (D):AppDataFolder
[SushiHangover.FlightAvionics] (F):.FlightInstrumentationData1.json
[SushiHangover.FlightAvionics] (F):.FlightInstrumentationData2.json
[SushiHangover.FlightAvionics] (F):.FlightInstrumentationData3.json
[SushiHangover.FlightAvionics] (F):AppConfiguration.json
Write a (Text) File Example:
using (var contentResults = await DriveClass.DriveApi.NewDriveContentsAsync(_googleApiClient))
using (var writer = new OutputStreamWriter(contentResults.DriveContents.OutputStream))
using (var changeSet = new MetadataChangeSet.Builder()
.SetTitle("AppConfiguration.txt")
.SetMimeType("text/plain")
.Build())
{
writer.Write("StackOverflow Rocks\n");
writer.Write("StackOverflow Rocks\n");
writer.Close();
await DriveClass.DriveApi.GetAppFolder(_googleApiClient).CreateFileAsync(_googleApiClient, changeSet, contentResults.DriveContents);
}
Note: Substitute a IDriveFolder for DriveClass.DriveApi.GetAppFolder to save a file in a subfolder of the AppFolder.
Read a (text) File Example:
Note: driveItem in the following example is an existing text/plain-based MetaData object that is found by recursing through the Drive contents (see Get Directory/File list above) or via creating a query (Query.Builder) and executing it via DriveClass.DriveApi.QueryAsync.
var fileContexts = new StringBuilder();
using (var results = await driveItem.DriveId.AsDriveFile().OpenAsync(_googleApiClient, DriveFile.ModeReadOnly, null))
using (var inputStream = results.DriveContents.InputStream)
using (var streamReader = new StreamReader(inputStream))
{
while (streamReader.Peek() >= 0)
fileContexts.Append(await streamReader.ReadLineAsync());
}
Log.Debug(TAG, fileContexts.ToString());
I am trying to import Attachments/Annotations to CRM Dynamics, I am doing this using the SDK.
I am not using the data import wizard.
I am not individually creating Annotation entities, instead I am using Data Import Feature programmatically.
I mostly leveraged the DataImport sample from the SDK sample code (SDK\SampleCode\CS\DataManagement\DataImport).
Import import = new Import()
{
ModeCode = new OptionSetValue((int)ImportModeCode.Create),
Name = "Data Import"
};
Guid importId = _serviceProxy.Create(import);
_serviceProxy.Create(
new ColumnMapping()
{
ImportMapId = new EntityReference(ImportMap.EntityLogicalName, importMapId),
ProcessCode = new OptionSetValue((int)ColumnMappingProcessCode.Process),
SourceEntityName = sourceEntityName,
SourceAttributeName = sourceAttributeName,
TargetEntityName = targetEntityName,
TargetAttributeName = targetAttributeName
});
I am getting an error "The reference to the attachment could not be found".
The documentation says the crm async service will find the physical file on disk and upload it, my question is where does the async service look for attachment files?
I tried to map documentbody field to the full path of the attachment on the desk, but that still didn't work.
The answer below was provided before the question edits clarifying the use of the import wizard instead of the SDK. The answer below is specific to using the SDK.
When you are attaching files to an Annotation (Note) record in CRM via the SDK, you do use the documentbody attribute (along with mimetype), but you have to first convert it base64.
Something like this:
var myFile = #"C:\Path\To\My\File.pdf";
// Do checks to make sure file exists...
// Convert to Base64.
var base64Data = Convert.ToBase64String(System.IO.File.ReadAllBytes(myFile));
var newNote = new Entity("annotation");
// Set subject, regarding object, etc.
// Add the data required for a file attachment.
newNote.Attributes.Add("documentbody", base64Data);
newNote.Attributes.Add("mimetype", "text/plain"); // This mime type seems to work for all file types.
orgService.Create(newNote);
I found the solution in an obscure blog post, I think the documentation is misleading or unclear, the way this whole thing works, makes having the files available on the server disk for the async to process, odd.
To follow the same principle, all contents should be sent like the csv file itself while being linked to the same import.
To solve this we need create individual special Internal ImportFile for each physical attachment, and link it to the import that has the attachments record details.
As you see below with linking the attachments ImportFile using the ImportId and then setting the two properties (ProcessCode and FileTypeCode), it all worked in the end.
Suffice to say using this method is much more efficient and quicker than individually creating Annotation records.
foreach (var line in File.ReadLines(csvFilesPath + "Attachment.csv").Skip(1))
{
var fileName = line.Split(',')[0].Replace("\"", null);
using (FileStream stream = File.OpenRead(attachmentsPath + fileName))
{
byte[] byteData = new byte[stream.Length];
stream.Read(byteData, 0, byteData.Length);
stream.Close();
string encodedAttachmentData = System.Convert.ToBase64String(byteData);
ImportFile importFileAttachment = new ImportFile()
{
Content = encodedAttachmentData,
Name = fileName,
ImportMapId = new EntityReference(ImportMap.EntityLogicalName, importMapId),
UseSystemMap = true,
ImportId = new EntityReference(Import.EntityLogicalName, importId),
ProcessCode = new OptionSetValue((int)ImportFileProcessCode.Internal),
FileTypeCode = new OptionSetValue((int)ImportFileFileTypeCode.Attachment),
RecordsOwnerId = currentUserRef
};
_serviceProxy.Create(importFileAttachment);
}
idx++;
}
I have a windows service (and verified the code by creating a similar WinForms application) where the NotifyFilter doesn't work. As soon as I remove that line of code, the service works fine and I can see the event-handler fire in the WinForms application.
All I'm doing is dropping a text file into the input directory for the FileSystemWatcher to kick off the watcher_FileChanged delegate. When I have the _watcher.NotifyFilter = NotifyFilters.CreationTime; in there, it doesn't work. When I pull it out, it works fine.
Can anyone tell me if I'm doing something wrong with this filter?
Here is the FSW code for the OnStart event.
protected override void OnStart(string[] args)
{
_watcher = new FileSystemWatcher(#"C:\Projects\Data\Test1");
_watcher.Created += new FileSystemEventHandler(watcher_FileChanged);
_watcher.NotifyFilter = NotifyFilters.CreationTime;
_watcher.IncludeSubdirectories = false;
_watcher.EnableRaisingEvents = true;
_watcher.Error += new ErrorEventHandler(OnError);
}
private void watcher_FileChanged(object sender, FileSystemEventArgs e)
{
// Folder with new files - one or more files
string folder = #"C:\Projects\Data\Test1";
System.Console.WriteLine(#"C:\Projects\Data\Test1");
//Console.ReadKey(true);
// Folder to delete old files - one or more files
string output = #"C:\Temp\Test1\";
System.Console.WriteLine(#"C:\Temp\Test1\");
//Console.ReadKey(true);
// Create name to call new zip file by date
string outputFilename = Path.Combine(output, string.Format("Archive{0}.zip", DateTime.Now.ToString("MMddyyyy")));
System.Console.WriteLine(outputFilename);
//Console.ReadKey(true);
// Save new files into a zip file
using (ZipFile zip = new ZipFile())
{
// Add all files in directory
foreach (var file in Directory.GetFiles(folder))
{
zip.AddFile(file);
}
// Save to output filename
zip.Save(outputFilename);
}
DirectoryInfo source = new DirectoryInfo(output);
// Get info of each file into the output directory to see whether or not to delete
foreach (FileInfo fi in source.GetFiles())
{
if (fi.CreationTime < DateTime.Now.AddDays(-1))
fi.Delete();
}
}
I've been having trouble with this behavior too. If you step through the code (and if you look at MSDN documenation, you'll find that NotifyFilter starts off with a default value of:
NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.LastWrite
So when you say .NotifyFilter = NotifyFilters.CreationTime, you're wiping out those other values, which explains the difference in behavior. I'm not sure why NotifyFilters.CreationTime is not catching the new file... seems like it should, shouldn't it!
You can probably just use the default value for NotifyFilter if it's working for you. If you want to add NotifyFilters.CreationTime, I'd recommend doing something like this to add the new value and not replace the existing ones:
_watcher.NotifyFilter = _watcher.NotifyFilter | NotifyFilters.CreationTime;
I know this is an old post but File Creation time is not always reliable. I came across a problem where a Log file was being moved to an archive folder and a new file of the same name was created in it's place however the file creation date did not change, in fact the meta data was retained from the previous file (the one that was moved to the archive) .
Windows has this cache on certain attributes of a file, file creation date is included. You can read the article on here: https://support.microsoft.com/en-us/kb/172190.
When the first time my app starts on a windows phone, I want to get some files(xml/images) from project folders and write them to the isolated storage .
How do I detect that my app is running for the first time?
How do I access file in project folders?
Here is another way to read files from your visual studio project. The following shows how to read a txt file but can be used for other file as well. Here the file is in the same directory as the .xaml.cs file.
var res = App.GetResourceStream(new Uri("test.txt", UriKind.Relative));
var txt = new StreamReader(res.Stream).ReadToEnd();
make sure your file is marked as Content.
If you mean project folders as in the folders in your visual studio project, I usually right click on the files and set the build action to 'Embedded Resource'. At runtime, you can read the data from the embedded resource like so:
// The resource name will correspond to the namespace and path in the file system.
// Have a look at the resources collection in the debugger to figure out the name.
string resourcePath = "assembly namespace" + "path inside project";
Assembly assembly = Assembly.GetExecutingAssembly();
string[] resources = assembly .GetManifestResourceNames();
List<string> files = new List<string>();
if (resource.StartsWith(resourcePath))
{
StreamReader reader = new StreamReader(assembly.GetManifestResourceStream(resource), Encoding.Default);
files.Add(reader.ReadToEnd());
}
To read the images you would need something like this to read the stream:
public static byte[] ReadAllBytes(Stream input)
{
byte[] buffer = new byte[32 * 1024];
using (MemoryStream ms = new MemoryStream())
{
int read;
while ((read = input.Read(buffer, 0, buffer.Length)) > 0)
{
ms.Write(buffer, 0, read);
}
return ms.ToArray();
}
}