Skip to content

Commit

Permalink
Bugfix IdleLoop
Browse files Browse the repository at this point in the history
addresses issues mentioned in #20, #51 and #61
  • Loading branch information
smiley22 committed Jan 20, 2014
1 parent 6d44844 commit a61b141
Show file tree
Hide file tree
Showing 9 changed files with 115 additions and 57 deletions.
4 changes: 2 additions & 2 deletions IImapClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@

namespace S22.Imap {
/// <summary>
/// Enables applications to communicate with a mail server using the
/// Internet Message Access Protocol (IMAP).
/// Enables applications to communicate with a mail server using the Internet Message Access
/// Protocol (IMAP).
/// </summary>
public interface IImapClient : IDisposable {
/// <summary>
Expand Down
38 changes: 38 additions & 0 deletions IdleErrorEventArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System;

namespace S22.Imap {
/// <summary>
/// Provides data for IMAP idle error events.
/// </summary>
public class IdleErrorEventArgs : EventArgs {
/// <summary>
/// Initializes a new instance of the IdleErrorEventArgs class.
/// </summary>
/// <param name="exception">The exception that causes the event.</param>
/// <param name="client">The instance of the ImapClient class that raised the event.</param>
/// <exception cref="ArgumentNullException">The exception parameter or the client parameter
/// is null.</exception>
internal IdleErrorEventArgs(Exception exception, ImapClient client) {
exception.ThrowIfNull("exception");
client.ThrowIfNull("client");
Exception = exception;
Client = client;
}

/// <summary>
/// The exception that caused the error event.
/// </summary>
public Exception Exception {
get;
private set;
}

/// <summary>
/// The instance of the ImapClient class that raised the event.
/// </summary>
public ImapClient Client {
get;
private set;
}
}
}
File renamed without changes.
85 changes: 48 additions & 37 deletions ImapClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@

namespace S22.Imap {
/// <summary>
/// Enables applications to communicate with a mail server using the
/// Internet Message Access Protocol (IMAP).
/// Enables applications to communicate with a mail server using the Internet Message Access
/// Protocol (IMAP).
/// </summary>
public class ImapClient : IImapClient
{
Expand Down Expand Up @@ -114,11 +114,19 @@ public event EventHandler<IdleMessageEventArgs> MessageDeleted {
}
}

/// <summary>
/// The event that is raised when an I/O exception occurs in the idle-thread.
/// </summary>
/// <remarks>
/// An I/O exception can occur if the underlying network connection has been reset or the
/// server unexpectedly closed the connection.
/// </remarks>

This comment has been minimized.

Copy link
@NathanBaulch

NathanBaulch Jun 17, 2016

Any particular reason why this event wasn't also added to the IImapClient interface?

public event EventHandler<IdleErrorEventArgs> IdleError;

/// <summary>
/// This constructor is solely used for unit testing.
/// </summary>
/// <param name="stream">A stream to initialize the ImapClient instance
/// with.</param>
/// <param name="stream">A stream to initialize the ImapClient instance with.</param>
internal ImapClient(Stream stream) {
this.stream = stream;
Authed = true;
Expand Down Expand Up @@ -402,9 +410,8 @@ string Authenticate(string tag, string username, string password,
"mechanism is not supported by the server.");
}
while (!m.IsCompleted) {
// Annoyingly, Gmail OAUTH2 issues an untagged capability response during
// the SASL authentication process. As per spec this is illegal, but we
// should still deal with it.
// Annoyingly, Gmail OAUTH2 issues an untagged capability response during the SASL
// authentication process. As per spec this is illegal, but we should still deal with it.
while (response.StartsWith("*"))
response = GetResponse();
// Stop if the server response starts with our tag.
Expand Down Expand Up @@ -463,8 +470,8 @@ string GetTag() {
/// prior to sending.</param>
void SendCommand(string command) {
ts.TraceInformation("C -> " + command);
// We can safely use UTF-8 here since it's backwards compatible with ASCII
// and comes in handy when sending strings in literal form (see RFC 3501, 4.3).
// We can safely use UTF-8 here since it's backwards compatible with ASCII and comes in handy
// when sending strings in literal form (see RFC 3501, 4.3).
byte[] bytes = Encoding.UTF8.GetBytes(command + "\r\n");
lock (writeLock) {
stream.Write(bytes, 0, bytes.Length);
Expand Down Expand Up @@ -502,7 +509,10 @@ string GetResponse(bool resolveLiterals = true) {
using (var mem = new MemoryStream()) {
lock (readLock) {
while (true) {
byte b = (byte)stream.ReadByte();
int i = stream.ReadByte();
if (i == -1)
throw new IOException("The stream could not be read.");
byte b = (byte)i;
if (b == CarriageReturn)
continue;
if (b == Newline) {
Expand Down Expand Up @@ -716,7 +726,6 @@ void SelectMailbox(string mailbox) {
string tag = GetTag();
string response = SendCommandGetResponse(tag + "SELECT " +
Util.UTF7Encode(mailbox).QuoteString());
// Fixme: evaluate untagged data?
while (response.StartsWith("*"))
response = GetResponse();
if (!IsResponseOK(response, tag))
Expand Down Expand Up @@ -748,8 +757,7 @@ public IEnumerable<string> ListMailboxes() {
string tag = GetTag();
string response = SendCommandGetResponse(tag + "LIST \"\" \"*\"");
while (response.StartsWith("*")) {
Match m = Regex.Match(response,
"\\* LIST \\((.*)\\)\\s+\"([^\"]+)\"\\s+(.+)");
Match m = Regex.Match(response, "\\* LIST \\((.*)\\)\\s+\"([^\"]+)\"\\s+(.+)");
if (m.Success) {
string[] attr = m.Groups[1].Value.Split(' ');
bool add = true;
Expand All @@ -758,14 +766,13 @@ public IEnumerable<string> ListMailboxes() {
if (a.ToLower() == @"\noselect")
add = false;
}
// Names _should_ be enclosed in double-quotes but not all servers
// follow through with this, so we don't enforce it in the above regex.
// Names _should_ be enclosed in double-quotes but not all servers follow through with
// this, so we don't enforce it in the above regex.
string name = Regex.Replace(m.Groups[3].Value, "^\"(.+)\"$", "$1");
try {
name = Util.UTF7Decode(name);
} catch {
// Include the unaltered string in the result if UTF-7 decoding
// failed for any reason.
// Include the unaltered string in the result if UTF-7 decoding failed for any reason.
}
if (add)
mailboxes.Add(name);
Expand Down Expand Up @@ -832,8 +839,7 @@ public void Expunge(string mailbox = null) {
/// <include file='Examples.xml' path='S22/Imap/ImapClient[@name="GetMailboxInfo"]/*'/>
public MailboxInfo GetMailboxInfo(string mailbox = null) {
AssertValid();
// This is not a cheap method to call, it involves a couple of round-trips
// to the server.
// This is not a cheap method to call, it involves a couple of round-trips to the server.
lock (sequenceLock) {
PauseIdling();
if (mailbox == null)
Expand Down Expand Up @@ -1001,7 +1007,7 @@ public IEnumerable<uint> Search(SearchCondition criteria, string mailbox = null)
if (!response.StartsWith("+")) {
ResumeIdling();
throw new NotSupportedException("Please restrict your search " +
"to ASCII-only characters", new BadServerResponseException(response));
"to ASCII-only characters.", new BadServerResponseException(response));
}
response = SendCommandGetResponse(line);
}
Expand Down Expand Up @@ -1732,9 +1738,8 @@ public void DeleteMessages(IEnumerable<uint> uids, string mailbox = null) {
string tag = GetTag();
string response = SendCommandGetResponse(tag + "UID STORE " + set +
@" +FLAGS.SILENT (\Deleted \Seen)");
while (response.StartsWith("*")) {
while (response.StartsWith("*"))
response = GetResponse();
}
ResumeIdling();
if (!IsResponseOK(response, tag))
throw new BadServerResponseException(response);
Expand Down Expand Up @@ -1777,8 +1782,7 @@ public IEnumerable<MessageFlag> GetMessageFlags(uint uid, string mailbox = null)
PauseIdling();
SelectMailbox(mailbox);
string tag = GetTag();
string response = SendCommandGetResponse(tag + "UID FETCH " + uid +
" (FLAGS)");
string response = SendCommandGetResponse(tag + "UID FETCH " + uid + " (FLAGS)");
List<MessageFlag> flags = new List<MessageFlag>();
while (response.StartsWith("*")) {
Match m = Regex.Match(response, @"FLAGS \(([\w\s\\$-]*)\)");
Expand Down Expand Up @@ -1832,9 +1836,8 @@ public void SetMessageFlags(uint uid, string mailbox, params MessageFlag[] flags
string tag = GetTag();
string response = SendCommandGetResponse(tag + "UID STORE " + uid +
@" FLAGS.SILENT (" + flagsString.Trim() + ")");
while (response.StartsWith("*")) {
while (response.StartsWith("*"))
response = GetResponse();
}
ResumeIdling();
if (!IsResponseOK(response, tag))
throw new BadServerResponseException(response);
Expand Down Expand Up @@ -1875,9 +1878,8 @@ public void AddMessageFlags(uint uid, string mailbox, params MessageFlag[] flags
string tag = GetTag();
string response = SendCommandGetResponse(tag + "UID STORE " + uid +
@" +FLAGS.SILENT (" + flagsString.Trim() + ")");
while (response.StartsWith("*")) {
while (response.StartsWith("*"))
response = GetResponse();
}
ResumeIdling();
if (!IsResponseOK(response, tag))
throw new BadServerResponseException(response);
Expand Down Expand Up @@ -1918,9 +1920,8 @@ public void RemoveMessageFlags(uint uid, string mailbox, params MessageFlag[] fl
string tag = GetTag();
string response = SendCommandGetResponse(tag + "UID STORE " + uid +
@" -FLAGS.SILENT (" + flagsString.Trim() + ")");
while (response.StartsWith("*")) {
while (response.StartsWith("*"))
response = GetResponse();
}
ResumeIdling();
if (!IsResponseOK(response, tag))
throw new BadServerResponseException(response);
Expand Down Expand Up @@ -1950,8 +1951,7 @@ void StartIdling() {
if (idling)
return;
if (!Supports("IDLE"))
throw new InvalidOperationException("The server does not support the " +
"IMAP4 IDLE command");
throw new InvalidOperationException("The server does not support the IMAP4 IDLE command.");
lock (sequenceLock) {
// Make sure the default mailbox is selected.
SelectMailbox(null);
Expand All @@ -1963,7 +1963,7 @@ void StartIdling() {
}
// Setup and start the idle thread.
if (idleThread != null)
throw new ApplicationException("idleThread is not null");
throw new ApplicationException("idleThread is not null.");
idling = true;
idleThread = new Thread(IdleLoop);
idleThread.IsBackground = true;
Expand Down Expand Up @@ -2069,7 +2069,7 @@ void ResumeIdling() {
}
// Setup and start the idle thread.
if (idleThread != null)
throw new ApplicationException("idleThread is not null");
throw new ApplicationException("idleThread is not null.");
idleThread = new Thread(IdleLoop);
idleThread.IsBackground = true;
idleThread.Start();
Expand Down Expand Up @@ -2112,7 +2112,19 @@ void IdleLoop() {
return;
// Otherwise we should let it bubble up.
// FIXME: Raise an error event?
throw;
// Shutdown idleThread.
// Stop Timer.
// Set idling to false.
idleThread = null;
idling = false;
noopTimer.Stop();
try {
IdleError.Raise(this, new IdleErrorEventArgs(e, this));
} catch {
}
Console.WriteLine("Shutting down IdleLoop");
return;

}
}
}
Expand Down Expand Up @@ -2198,8 +2210,7 @@ IEnumerable<MailboxQuota> GetQuota(string mailbox = null) {
string response = SendCommandGetResponse(tag + "GETQUOTAROOT " +
Util.UTF7Encode(mailbox).QuoteString());
while (response.StartsWith("*")) {
Match m = Regex.Match(response,
"\\* QUOTA \"(\\w*)\" \\((\\w+)\\s+(\\d+)\\s+(\\d+)\\)");
Match m = Regex.Match(response, "\\* QUOTA \"(\\w*)\" \\((\\w+)\\s+(\\d+)\\s+(\\d+)\\)");
if (m.Success) {
try {
MailboxQuota quota = new MailboxQuota(m.Groups[2].Value,
Expand Down
4 changes: 2 additions & 2 deletions Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,5 @@
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("3.4.0.3")]
[assembly: AssemblyFileVersion("3.4.0.3")]
[assembly: AssemblyVersion("3.5.0.0")]
[assembly: AssemblyFileVersion("3.5.0.0")]
8 changes: 3 additions & 5 deletions S22.Imap.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@
<Compile Include="Bodystructure\ContentType.cs" />
<Compile Include="Bodystructure\Reader.cs" />
<Compile Include="FetchOptions.cs" />
<Compile Include="IdleEvents.cs" />
<Compile Include="IdleErrorEventArgs.cs" />
<Compile Include="IdleMessageEventArgs.cs" />
<Compile Include="IImapClient.cs" />
<Compile Include="ImapClient.cs" />
<Compile Include="Exceptions.cs" />
Expand Down Expand Up @@ -125,9 +126,6 @@
</Target>
-->
<Target Name="AfterBuild">
<MSBuild Condition=" '$(TargetFrameworkVersion)' == 'v4.0'"
Projects="$(MSBuildProjectFile)"
Properties="TargetFrameworkVersion=v3.5"
RunEachTargetSeparately="true" />
<MSBuild Condition=" '$(TargetFrameworkVersion)' == 'v4.0'" Projects="$(MSBuildProjectFile)" Properties="TargetFrameworkVersion=v3.5" RunEachTargetSeparately="true" />
</Target>
</Project>
11 changes: 11 additions & 0 deletions Tests/SequenceSetTest.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using S22.Imap;

namespace S22.Imap.Test {
/// <summary>
Expand Down Expand Up @@ -68,6 +69,16 @@ public void RangesAndUIDs() {
Assert.AreEqual("1,3:5,7", Util.BuildSequenceSet(list));
}

/// <summary>
/// Ensures a single UID is properly converted.
/// </summary>
[TestMethod]
[TestCategory("BuildSequenceSet")]
public void SingleUID() {
var list = new List<uint>() { 4 };
Assert.AreEqual("4", Util.BuildSequenceSet(list));
}

/// <summary>
/// Passing null to Util.BuildSequenceSet should raise an ArgumentNullException.
/// </summary>
Expand Down
9 changes: 6 additions & 3 deletions Tests/Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,6 @@
<DefineConstants Condition=" $(DefineConstants.Contains(';NET')) ">$(DefineConstants.Remove($(DefineConstants.LastIndexOf(";NET"))));$(TargetFrameworkVersion.Replace("v", "NET").Replace(".", ""))</DefineConstants>
</PropertyGroup>
<ItemGroup>
<Reference Include="S22.Imap">
<HintPath>..\bin\Debug\S22.Imap.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Numerics" Condition=" '$(TargetFrameworkVersion)' != 'v3.5' " />
</ItemGroup>
Expand Down Expand Up @@ -90,6 +87,12 @@
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\S22.Imap.csproj">
<Project>{369c32a5-e099-4bd5-bbbf-51713947ca99}</Project>
<Name>S22.Imap</Name>
</ProjectReference>
</ItemGroup>
<Choose>
<When Condition="'$(VisualStudioVersion)' == '10.0' And '$(IsCodedUITest)' == 'True'">
<ItemGroup>
Expand Down
Loading

0 comments on commit a61b141

Please sign in to comment.