CDO, C# and file-locking problem.

  • Thread starter Thread starter matt dittman
  • Start date Start date
M

matt dittman

I have created a windows service that reads emails from a
drop directory and moves them to the appropriate mail
folder every 15 seconds. I can move, rename and delete
the files as needed, up until the
CDO.DropDirectory.GetMessages() method is called. At
this point, the files are locked until I shut down the
service. After processing and delivery, I need to be
able to delete all the files in the drop directory.

I can delete them via Windows Explorer, but when using my
C# service and the DropDirectory.GetMessages().DeleteAll
() method I receive the error "One or more messages could
not be deleted" and none of the messages are deleted.
Attempts to use other methods to delete the files, such
as FileInfo objects, also result in no file deletion and
errors being thrown.

However, when I shut down the service, it does finally
clear out the folder. It seems like the CDO object only
marks the files for deletion and locks them until it can,
which seems to be only when the CDO object is permanently
destroyed. Is there any way to force the CDO object to
immediately DeleteAll() messages?
 
Hi Matt,
From your description, you try to use getmessages and deleteAll to delete
all the message in the drop directory.
Am I right?

As you guessed before, you need update after using deleteall method.

Here is the reason:
Each time a message is opened and a property is viewed, the message becomes
locked and other application will recieve an error when they attempt to
open the message.

I suggest you calling msg.datasource.save to update. Please let me know if
it works. Thanks!


Rhett Gong[MS]
Microsoft Online Partner Support

This posting is provided "AS IS" with no warranties, and confers no rights.
Please reply to newsgroups only. Thanks.
 
That didn't work. When I tried to loop through all
messages after reading the properties (to deliver them) I
got an Access Denied message.

However, I finally did get it working by calling
GC.Collect() before I attempted to DeleteAll().

There seems to be no other way to explicitly release the
messages.
 
Hello Matt,

I reviewed the complete thread and would like to jump in to provide some
information.

Without your complete source code, I have not clear understanding at this
time. To keep us on the same page about the issue, I assume the issue as
follows. If I made any misunderstanding, please let me know.

====
You initialize DropDirectory object to open drop directory and call
GetMessages and get reference to all messages collection under the
directory. After that, enumerate all messages under the directory in the
collection to deal with them one by one, such as rename, move, delivery,
reply or others. After that, you and then perform deletion by call
deleteall().

At the time, you encournter the error: One or more messages could not be
deleted.
===

I wonder if all messages are closed before you call deleteall()? For
example, if you want to move or copy some messages into another directory,
you may open a file stream to open a message file. Did you make sure you
close it after completing operation?

Here is simple VB sample to do similiar thing. I know you are working with
C# and I just provided it for your reference.

Dim iDropDir as New CDO.DropDirectory

Dim iMsgs as CDO.IMessages

Dim iMsg as CDO.Message

Dim iStream as ADODB.Stream



Dim strTo as String

Dim ToRecipients as Variant

Dim strEmailName as String

Dim strAccountName as String

Dim strFileName as String

Dim strMailboxDir as String



Set iMsgs = iDropDir.GetMessages



For Each iMsg in iMsgs

strFileName = iMsgs.FileName(iMsg)



' trim to get the short file name

' from the full path

strFileName = Right(strFileName, Len(strFileName) -
InStrRev(strFileName,"\")
)



' Get the To recipients...and assume they are all local accounts

strTo = iMsg.To

ToRecipients = Split(strTo,",")

Dim j, lpos, rpos, posdiff

' loop through recipients and get account names for each

' Each address will be in either the "Name" <[email protected]> or

' simply <[email protected]>

' We get the account name by getting the string

' between "<" and "@"

For j = LBound(ToRecipients) to UBound(ToRecipients)

strEmailName = ToRecipients(j)

lpos = InStr(strEmailName,"<")

rpos = InStr(strEmailName,"@")

posdiff = rpos - lpos - 1

strAccountName = Mid(strEmailName,lpos + 1, posdiff)



' For the purposes of this example,

' each account's mailbox directory resides in the

' directory c:\mailboxes. For user Joe, their account

' directory would be c:\mailboxes\joe

strMailboxDir = "c:\mailboxes\" & strAccountName

' Get the message stream

Set iStream = iMsg.GetStream



' write the stream to the user's mailbox directory

' the file name is the same as the one in the drop directory

iStream.SaveToFile strMailboxDir & "\" & strFileName

iStream.Close

Set iStream = Nothing

Next j

Next iMsg



' once we're done, delete the picked up messages

' this deletes the files from the file system as well.

iMsgs.DeleteAll



Set iMsgs = Nothing

Set iDropDir = Nothing


If my information doesn't help, please provide a complete repro sample. I
may investigate it by checking its souce code. Thank you!

Regards,
Justin Wan
Microsoft Partner Online Support

This posting is provided "AS IS" with no warranties, and confers no rights.
 
Everything is being closed. Also, I have tried
processing the mails, then not performing a DeleteAll()
until before the next mail processing 15 seconds later
and it still hangs up. It works as it is as long as I
include the GC.Collect() call.

Source code:


using System;
using System.Data;
using System.IO;
using System.Text;
using CDO;
using System.Configuration;
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace MailService
{
/// <summary>
/// Contains methods used for copying e-mail
messages from a drop folder to
/// the appropriate recipients' inboxes.
/// </summary>
public class SmtpReceive:IDisposable
{
private String _dropDirectory =
@"C:\Inetpub\mailroot\Drop";
private String _tempDirectory =
@"C:\Inetpub\mailroot\Temp";
private String _badmailDirectory =
@"C:\Inetpub\mailroot\Badmail";
private String _mailboxDirectoryPrefix =
@"C:\Inetpub\mailroot\Mailbox\";
private String _mailboxDirectorySuffix =
@"\inbox";
private String _emailSuffix
= "@myDomain.net";
private EventLog log;
private String _machineName = "MYMACHINE";

// Pointer to an external unmanaged
resource.
private IntPtr handle;
// Track whether Dispose has been called.
private bool disposed = false;


/// <summary>
/// Default constructor.
/// </summary>
public SmtpReceive()
{
this.handle = handle;
log = new EventLog("Application",
_machineName, "MailService.exe:SMTPReceive");
}
/// <summary>
/// Checks to see if the specified path
exists; if not, create it.
/// </summary>
/// <param name="directoryPath">Path to
be validated/created.</param>
private void ValidateDirectory(String
directoryPath)
{
try
{
DirectoryInfo di = new
DirectoryInfo(directoryPath);
if(!di.Exists)

Directory.CreateDirectory(directoryPath);
}
catch(Exception ex)
{
log.WriteEntry("Error in
ValidateDirectory: " + ex.Message);
}
}
/// <summary>
/// Gets all the email addresses in the
drop directory and delivers them
/// to the recipients.
/// </summary>
public void Receive()
{
ValidateDirectory(_tempDirectory);
ValidateDirectory
(_badmailDirectory);
//move emails to a temp directory
so we know that no more will be added
//during processing:
FileMove(_dropDirectory,
_tempDirectory);
ProcessMails();
DeleteTempFiles();
}
private void ProcessMails()
{
CDO.DropDirectory drop = new
CDO.DropDirectory();
CDO.IMessages imsg;
imsg = drop.GetMessages
(_tempDirectory);
try
{
for(int i=1; i <=
imsg.Count; i++)
{
string fileName =
imsg.get_FileName(i);
string[]
toAddresses = null;
string[]
ccAddresses = null;
string[]
bccAddresses = null;
try
{
//get
email addresses to send to

toAddresses = imsg.To.Split(Convert.ToChar
(","));

ccAddresses = imsg.CC.Split(Convert.ToChar
(","));

bccAddresses = imsg.BCC.Split(Convert.ToChar
(","));
//get rid
of extra whitespace and other unneeded characters

FixArrayStrings(ref toAddresses);

FixArrayStrings(ref ccAddresses);

FixArrayStrings(ref bccAddresses);
}
catch
{
//bad
mail, drop in BadMail directory
string
badMailPath = _badmailDirectory + @"\" + Path.GetFileName
(fileName);
FileInfo
fi1 = new FileInfo(fileName);
FileInfo
fi2 = new FileInfo(badMailPath);
// Create
the file and clean up handles.
using
(FileStream fs = fi1.Create()) {}
//Ensure
that the target does not exist.
fi2.Delete
();
//Copy
the file.
fi1.CopyTo
(badMailPath,true);
}

//copy email file
to imail folders
//Send "To":
if(toAddresses !=
null)
{
foreach
(string s in toAddresses)
{

CopyMail(fileName, s);
}
}
if(ccAddresses !=
null)
{

//Send "CC":
foreach
(string s in ccAddresses)
{

CopyMail(fileName, s);
}
}
if(bccAddresses !
= null)
{

//Send "BCC":
foreach
(string s in bccAddresses)
{

CopyMail(fileName, s);
}
}
}
}
catch(Exception ex)
{
log.WriteEntry("Error in
ProcessMails: " + ex.Message);
}
finally
{
int iRefCount =
Marshal.ReleaseComObject( drop );
Marshal.ReleaseComObject
(imsg);
/*
* THIS IS THE CALL
TO GC.Collect() THAT SOLVES THE PROBLEM:
*/
GC.Collect();
}
}
/// <summary>
/// Delete temporary files after
processing
/// </summary>
private void DeleteTempFiles()
{
CDO.DropDirectory drop = new
CDO.DropDirectory();
try
{
drop.GetMessages
(_tempDirectory).DeleteAll();
}
catch(Exception ex)
{
log.WriteEntry("Error in
DeleteTempFiles: " + ex.Message);
}
finally
{
int iRefCount =
Marshal.ReleaseComObject( drop );
}
}
/// <summary>
/// Copy file from drop directory to
recipient's inbox
/// </summary>
/// <param name="fileName">Name of the
file to copy</param>
/// <param name="recipient">Email address
of the recipient</param>
private void CopyMail(string fileName,
string recipient)
{
try
{
string userID =
recipient.Substring(0, (recipient.Length -
_emailSuffix.Length));
string suffix =
recipient.Substring(recipient.Length -
_emailSuffix.Length);
if(suffix != _emailSuffix)
return;

string mailbox =
_mailboxDirectoryPrefix + userID +
_mailboxDirectorySuffix;
string inboxPath =
Path.Combine(mailbox, Path.GetFileName(fileName));
FileInfo fi1 = new
FileInfo(fileName);

//check to see if mailbox
exists, create if not
ValidateDirectory
(mailbox);
fi1.CopyTo(inboxPath,
true);
fi1 = null;
}
catch(Exception ex)
{
log.WriteEntry("Error in
CopyMail: " + ex.Message);
}
}
/// <summary>
/// Trims all whitespace from each string
in the array, converts all characters
/// to lowercase and extracts the e-mail
address from the field.
/// </summary>
/// <param name="arr">The array to be
cleansed.</param>
private void FixArrayStrings(ref string[]
arr)
{
try
{
if((arr.Length == 1) &&
(arr[0].Length == 0))
{
arr = null;
return;
}
for(int i = 0; i <
arr.Length; i++)
{
arr = arr
.Trim();
arr = arr
.ToLower();
arr = arr
.Remove(0, arr.IndexOf("<") + 1);
arr = arr
.Remove(arr.IndexOf(">"), arr.Length - arr
.IndexOf(">"));
}
}
catch(Exception ex)
{
log.WriteEntry("Error in
FixArrayStrings: " + ex.Message);
}
}
/// <summary>
/// Moves files from one directory to
another
/// </summary>
/// <param name="srcdir">The source
directory</param>
/// <param name="destdir">The destination
directory</param>
private void FileMove(string srcdir,
string destdir)
{
try
{
DirectoryInfo dir;
FileInfo[] files;
string tmppath;

//determine if the
destination directory exists, if not create it
ValidateDirectory
(destdir);

dir = new DirectoryInfo
(srcdir);

//if the source dir
doesn't exist, throw
if (! dir.Exists)
{
throw new
ArgumentException("source dir doesn't exist -> " +
srcdir);
}

//get all files in the
current dir
files = dir.GetFiles();

//loop through each file
foreach(FileInfo file in
files)
{
//create the path
to where this file should be in destdir

tmppath=Path.Combine(destdir,
file.Name);

//copy file to
dest dir
file.MoveTo
(tmppath);
}
//cleanup
files = null;
dir = null;
}
catch(Exception ex)
{
log.WriteEntry("Error in
FileMove: " + ex.Message);
}
}
#region IDisposable Members

/// <summary>
/// Dispose
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);

}
private void Dispose(bool disposing)
{
// Check to see if Dispose has
already been called.
if(!this.disposed)
{
CloseHandle(handle);
handle =
IntPtr.Zero;
}
disposed = true;
}

[System.Runtime.InteropServices.DllImport
("Kernel32")]
private extern static Boolean CloseHandle
(IntPtr handle);

/// <summary>
/// Destructor
/// </summary>
~SmtpReceive()
{
Dispose(false);
}
#endregion
}
}
 
Back
Top