Browse files

Bugfix IdleLoop

addresses issues mentioned in #20, #51 and #61
  • Loading branch information...
1 parent 6d44844 commit a61b14113c3d203a0003d288d7c6daa44bf9b3b8 @smiley22 committed Jan 20, 2014
View
4 IImapClient.cs
@@ -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>
View
38 IdleErrorEventArgs.cs
@@ -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;
+ }
+ }
+}
View
0 IdleEvents.cs → IdleMessageEventArgs.cs
File renamed without changes.
View
85 ImapClient.cs
@@ -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
{
@@ -115,10 +115,18 @@ public class ImapClient : IImapClient
}
/// <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>
+ public event EventHandler<IdleErrorEventArgs> IdleError;

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+
+ /// <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;
@@ -402,9 +410,8 @@ public class ImapClient : IImapClient
"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.
@@ -463,8 +470,8 @@ public class ImapClient : IImapClient
/// 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);
@@ -502,7 +509,10 @@ public class ImapClient : IImapClient
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) {
@@ -716,7 +726,6 @@ public class ImapClient : IImapClient
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))
@@ -748,8 +757,7 @@ public class ImapClient : IImapClient
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;
@@ -758,14 +766,13 @@ public class ImapClient : IImapClient
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);
@@ -832,8 +839,7 @@ public class ImapClient : IImapClient
/// <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)
@@ -1001,7 +1007,7 @@ public class ImapClient : IImapClient
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);
}
@@ -1732,9 +1738,8 @@ public class ImapClient : IImapClient
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);
@@ -1777,8 +1782,7 @@ public class ImapClient : IImapClient
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\\$-]*)\)");
@@ -1832,9 +1836,8 @@ public class ImapClient : IImapClient
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);
@@ -1875,9 +1878,8 @@ public class ImapClient : IImapClient
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);
@@ -1918,9 +1920,8 @@ public class ImapClient : IImapClient
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);
@@ -1950,8 +1951,7 @@ public class ImapClient : IImapClient
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);
@@ -1963,7 +1963,7 @@ public class ImapClient : IImapClient
}
// 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;
@@ -2069,7 +2069,7 @@ public class ImapClient : IImapClient
}
// 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();
@@ -2112,7 +2112,19 @@ public class ImapClient : IImapClient
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;
+
}
}
}
@@ -2198,8 +2210,7 @@ public class ImapClient : IImapClient
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,
View
4 Properties/AssemblyInfo.cs
@@ -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")]
View
8 S22.Imap.csproj
@@ -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" />
@@ -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>
View
11 Tests/SequenceSetTest.cs
@@ -1,6 +1,7 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
+using S22.Imap;
namespace S22.Imap.Test {
/// <summary>
@@ -69,6 +70,16 @@ public class SequenceSetTest {
}
/// <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>
[TestMethod]
View
9 Tests/Tests.csproj
@@ -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>
@@ -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>
View
13 Util.cs
@@ -64,8 +64,7 @@ internal static class Util {
/// <returns>true if the value parameter occurs within this string, or if value is the empty
/// string (""); otherwise, false.</returns>
/// <exception cref="ArgumentNullException">The value parameter is null.</exception>
- internal static bool Contains(this string str, string value,
- StringComparison comparer) {
+ internal static bool Contains(this string str, string value, StringComparison comparer) {
return str.IndexOf(value, comparer) >= 0;
}
@@ -136,8 +135,7 @@ internal static void Raise<T>(this EventHandler<T> @event, object sender, T args
/// <param name="reader">Extension method for BinaryReader.</param>
/// <param name="bigEndian">Set to true to interpret the short value as big endian value.</param>
/// <returns>The 16-byte unsigned short value read from the underlying stream.</returns>
- internal static ushort ReadUInt16(this BinaryReader reader,
- bool bigEndian) {
+ internal static ushort ReadUInt16(this BinaryReader reader, bool bigEndian) {
if (!bigEndian)
return reader.ReadUInt16();
int ret = 0;
@@ -219,8 +217,7 @@ internal static void Raise<T>(this EventHandler<T> @event, object sender, T args
case "B":
return encoding.GetString(Util.Base64Decode(text));
default:
- throw new FormatException("Encoding not recognized " +
- "in encoded word: " + word);
+ throw new FormatException("Encoding not recognized in encoded word: " + word);
}
}
@@ -335,7 +332,7 @@ internal static void Raise<T>(this EventHandler<T> @event, object sender, T args
/// </summary>
/// <param name="s">The UTF-7 encoded string to decode.</param>
/// <returns>A UTF-16 encoded "standard" C# string</returns>
- /// <exception cref="FormatException">Thrown if the input string is not a proper UTF-7 encoded
+ /// <exception cref="FormatException">The input string is not a properly UTF-7 encoded
/// string.</exception>
/// <remarks>IMAP uses a modified version of UTF-7 for encoding international mailbox names. For
/// details, refer to RFC 3501 section 5.1.3 (Mailbox International Naming Convention).</remarks>
@@ -361,7 +358,7 @@ internal static void Raise<T>(this EventHandler<T> @event, object sender, T args
builder.Append(Encoding.BigEndianUnicode.GetString(buffer));
} catch (Exception e) {
throw new FormatException(
- "The input string is not in the correct Format", e);
+ "The input string is not in the correct Format.", e);
}
} else {
if (c == '&' && reader.Peek() == '-')

0 comments on commit a61b141

Please sign in to comment.