6225 lines
245 KiB
C#
6225 lines
245 KiB
C#
//
|
|
// ImapFolder.cs
|
|
//
|
|
// Author: Jeffrey Stedfast <jestedfa@microsoft.com>
|
|
//
|
|
// Copyright (c) 2013-2020 .NET Foundation and Contributors
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
// THE SOFTWARE.
|
|
//
|
|
|
|
using System;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Globalization;
|
|
using System.Threading.Tasks;
|
|
using System.Collections.Generic;
|
|
|
|
using MimeKit;
|
|
|
|
using MailKit.Search;
|
|
|
|
namespace MailKit.Net.Imap {
|
|
/// <summary>
|
|
/// An IMAP folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// An IMAP folder.
|
|
/// </remarks>
|
|
/// <example>
|
|
/// <code language="c#" source="Examples\ImapExamples.cs" region="DownloadMessages"/>
|
|
/// </example>
|
|
/// <example>
|
|
/// <code language="c#" source="Examples\ImapExamples.cs" region="DownloadBodyParts"/>
|
|
/// </example>
|
|
public partial class ImapFolder : MailFolder, IImapFolder
|
|
{
|
|
bool supportsModSeq;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="MailKit.Net.Imap.ImapFolder"/> class.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>Creates a new <see cref="ImapFolder"/>.</para>
|
|
/// <para>If you subclass <see cref="ImapFolder"/>, you will also need to subclass
|
|
/// <see cref="ImapClient"/> and override the
|
|
/// <see cref="ImapClient.CreateImapFolder(ImapFolderConstructorArgs)"/>
|
|
/// method in order to return a new instance of your ImapFolder subclass.</para>
|
|
/// </remarks>
|
|
/// <param name="args">The constructor arguments.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <paramref name="args"/> is <c>null</c>.
|
|
/// </exception>
|
|
public ImapFolder (ImapFolderConstructorArgs args)
|
|
{
|
|
if (args == null)
|
|
throw new ArgumentNullException (nameof (args));
|
|
|
|
InitializeProperties (args);
|
|
}
|
|
|
|
void InitializeProperties (ImapFolderConstructorArgs args)
|
|
{
|
|
DirectorySeparator = args.DirectorySeparator;
|
|
EncodedName = args.EncodedName;
|
|
Attributes = args.Attributes;
|
|
FullName = args.FullName;
|
|
Engine = args.Engine;
|
|
Name = args.Name;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the IMAP command engine.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Gets the IMAP command engine.
|
|
/// </remarks>
|
|
/// <value>The engine.</value>
|
|
internal ImapEngine Engine {
|
|
get; private set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the encoded name of the folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Gets the encoded name of the folder.
|
|
/// </remarks>
|
|
/// <value>The encoded name.</value>
|
|
internal string EncodedName {
|
|
get; set;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets an object that can be used to synchronize access to the IMAP server.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>Gets an object that can be used to synchronize access to the IMAP server.</para>
|
|
/// <para>When using the non-Async methods from multiple threads, it is important to lock the
|
|
/// <see cref="SyncRoot"/> object for thread safety when using the synchronous methods.</para>
|
|
/// </remarks>
|
|
/// <value>The lock object.</value>
|
|
public override object SyncRoot {
|
|
get { return Engine; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the threading algorithms supported by the folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Get the threading algorithms supported by the folder.
|
|
/// </remarks>
|
|
/// <value>The supported threading algorithms.</value>
|
|
public override HashSet<ThreadingAlgorithm> ThreadingAlgorithms {
|
|
get { return Engine.ThreadingAlgorithms; }
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determine whether or not an <see cref="ImapFolder"/> supports a feature.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Determines whether or not an <see cref="ImapFolder"/> supports a feature.
|
|
/// </remarks>
|
|
/// <param name="feature">The desired feature.</param>
|
|
/// <returns><c>true</c> if the feature is supported; otherwise, <c>false</c>.</returns>
|
|
public override bool Supports (FolderFeature feature)
|
|
{
|
|
switch (feature) {
|
|
case FolderFeature.AccessRights: return (Engine.Capabilities & ImapCapabilities.Acl) != 0;
|
|
case FolderFeature.Annotations: return AnnotationAccess != AnnotationAccess.None;
|
|
case FolderFeature.Metadata: return (Engine.Capabilities & ImapCapabilities.Metadata) != 0;
|
|
case FolderFeature.ModSequences: return supportsModSeq;
|
|
case FolderFeature.QuickResync: return Engine.QResyncEnabled;
|
|
case FolderFeature.Quotas: return (Engine.Capabilities & ImapCapabilities.Quota) != 0;
|
|
case FolderFeature.Sorting: return (Engine.Capabilities & ImapCapabilities.Sort) != 0;
|
|
case FolderFeature.Threading: return (Engine.Capabilities & ImapCapabilities.Thread) != 0;
|
|
case FolderFeature.UTF8: return Engine.UTF8Enabled;
|
|
default: return false;
|
|
}
|
|
}
|
|
|
|
void CheckState (bool open, bool rw)
|
|
{
|
|
if (Engine.IsDisposed)
|
|
throw new ObjectDisposedException (nameof (ImapClient));
|
|
|
|
if (!Engine.IsConnected)
|
|
throw new ServiceNotConnectedException ("The ImapClient is not connected.");
|
|
|
|
if (Engine.State < ImapEngineState.Authenticated)
|
|
throw new ServiceNotAuthenticatedException ("The ImapClient is not authenticated.");
|
|
|
|
if (open) {
|
|
var access = rw ? FolderAccess.ReadWrite : FolderAccess.ReadOnly;
|
|
|
|
if (!IsOpen || Access < access)
|
|
throw new FolderNotOpenException (FullName, access);
|
|
}
|
|
}
|
|
|
|
void CheckAllowIndexes ()
|
|
{
|
|
// Indexes ("Message Sequence Numbers" or MSNs in the RFCs) and * are not stable while MessageNew/MessageExpunge is registered for SELECTED and therefore should not be used
|
|
// https://tools.ietf.org/html/rfc5465#section-5.2
|
|
if (Engine.NotifySelectedNewExpunge)
|
|
throw new InvalidOperationException ("Indexes and '*' cannot be used while MessageNew/MessageExpunge is registered with NOTIFY for SELECTED.");
|
|
}
|
|
|
|
internal void Reset ()
|
|
{
|
|
// basic state
|
|
PermanentFlags = MessageFlags.None;
|
|
AcceptedFlags = MessageFlags.None;
|
|
Access = FolderAccess.None;
|
|
|
|
// annotate state
|
|
AnnotationAccess = AnnotationAccess.None;
|
|
AnnotationScopes = AnnotationScope.None;
|
|
MaxAnnotationSize = 0;
|
|
|
|
// condstore state
|
|
supportsModSeq = false;
|
|
HighestModSeq = 0;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Notifies the folder that a parent folder has been renamed.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Updates the <see cref="MailFolder.FullName"/> property.
|
|
/// </remarks>
|
|
protected override void OnParentFolderRenamed ()
|
|
{
|
|
var oldEncodedName = EncodedName;
|
|
|
|
FullName = ParentFolder.FullName + DirectorySeparator + Name;
|
|
EncodedName = Engine.EncodeMailboxName (FullName);
|
|
Engine.FolderCache.Remove (oldEncodedName);
|
|
Engine.FolderCache[EncodedName] = this;
|
|
Reset ();
|
|
|
|
if (Engine.Selected == this) {
|
|
Engine.State = ImapEngineState.Authenticated;
|
|
Engine.Selected = null;
|
|
OnClosed ();
|
|
}
|
|
}
|
|
|
|
void ProcessResponseCodes (ImapCommand ic, IMailFolder folder, bool throwNotFound = true)
|
|
{
|
|
bool tryCreate = false;
|
|
|
|
foreach (var code in ic.RespCodes) {
|
|
switch (code.Type) {
|
|
case ImapResponseCodeType.PermanentFlags:
|
|
PermanentFlags = ((PermanentFlagsResponseCode) code).Flags;
|
|
break;
|
|
case ImapResponseCodeType.ReadOnly:
|
|
if (code.IsTagged)
|
|
Access = FolderAccess.ReadOnly;
|
|
break;
|
|
case ImapResponseCodeType.ReadWrite:
|
|
if (code.IsTagged)
|
|
Access = FolderAccess.ReadWrite;
|
|
break;
|
|
case ImapResponseCodeType.TryCreate:
|
|
tryCreate = true;
|
|
break;
|
|
case ImapResponseCodeType.UidNext:
|
|
UidNext = ((UidNextResponseCode) code).Uid;
|
|
break;
|
|
case ImapResponseCodeType.UidValidity:
|
|
var uidValidity = ((UidValidityResponseCode) code).UidValidity;
|
|
if (IsOpen)
|
|
UpdateUidValidity (uidValidity);
|
|
else
|
|
UidValidity = uidValidity;
|
|
break;
|
|
case ImapResponseCodeType.Unseen:
|
|
FirstUnread = ((UnseenResponseCode) code).Index;
|
|
break;
|
|
case ImapResponseCodeType.HighestModSeq:
|
|
var highestModSeq = ((HighestModSeqResponseCode) code).HighestModSeq;
|
|
supportsModSeq = true;
|
|
if (IsOpen)
|
|
UpdateHighestModSeq (highestModSeq);
|
|
else
|
|
HighestModSeq = highestModSeq;
|
|
break;
|
|
case ImapResponseCodeType.NoModSeq:
|
|
supportsModSeq = false;
|
|
HighestModSeq = 0;
|
|
break;
|
|
case ImapResponseCodeType.MailboxId:
|
|
// Note: an untagged MAILBOX resp-code is returned on SELECT/EXAMINE while
|
|
// a *tagged* MAILBOXID resp-code is returned on CREATE.
|
|
if (!code.IsTagged)
|
|
Id = ((MailboxIdResponseCode) code).MailboxId;
|
|
break;
|
|
case ImapResponseCodeType.Annotations:
|
|
var annotations = (AnnotationsResponseCode) code;
|
|
AnnotationAccess = annotations.Access;
|
|
AnnotationScopes = annotations.Scopes;
|
|
MaxAnnotationSize = annotations.MaxSize;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (tryCreate && throwNotFound && folder != null)
|
|
throw new FolderNotFoundException (folder.FullName);
|
|
}
|
|
|
|
static ImapResponseCode GetResponseCode (ImapCommand ic, ImapResponseCodeType type)
|
|
{
|
|
for (int i = 0; i < ic.RespCodes.Count; i++) {
|
|
if (ic.RespCodes[i].Type == type)
|
|
return ic.RespCodes[i];
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
#region IMailFolder implementation
|
|
|
|
/// <summary>
|
|
/// Gets a value indicating whether the folder is currently open.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Gets a value indicating whether the folder is currently open.
|
|
/// </remarks>
|
|
/// <value><c>true</c> if the folder is currently open; otherwise, <c>false</c>.</value>
|
|
public override bool IsOpen {
|
|
get { return Engine.Selected == this; }
|
|
}
|
|
|
|
static string SelectOrExamine (FolderAccess access)
|
|
{
|
|
return access == FolderAccess.ReadOnly ? "EXAMINE" : "SELECT";
|
|
}
|
|
|
|
static Task QResyncFetchAsync (ImapEngine engine, ImapCommand ic, int index, bool doAsync)
|
|
{
|
|
return ic.Folder.OnFetchAsync (engine, index, doAsync, ic.CancellationToken);
|
|
}
|
|
|
|
async Task<FolderAccess> OpenAsync (ImapCommand ic, FolderAccess access, bool doAsync, CancellationToken cancellationToken)
|
|
{
|
|
Reset ();
|
|
|
|
if (access == FolderAccess.ReadWrite) {
|
|
// Note: if the server does not respond with a PERMANENTFLAGS response,
|
|
// then we need to assume all flags are permanent.
|
|
PermanentFlags = SettableFlags | MessageFlags.UserDefined;
|
|
} else {
|
|
PermanentFlags = MessageFlags.None;
|
|
}
|
|
|
|
try {
|
|
Engine.QueueCommand (ic);
|
|
|
|
await Engine.RunAsync (ic, doAsync).ConfigureAwait (false);
|
|
|
|
ProcessResponseCodes (ic, this);
|
|
|
|
if (ic.Response != ImapCommandResponse.Ok)
|
|
throw ImapCommandException.Create (access == FolderAccess.ReadOnly ? "EXAMINE" : "SELECT", ic);
|
|
} catch {
|
|
PermanentFlags = MessageFlags.None;
|
|
throw;
|
|
}
|
|
|
|
if (Engine.Selected != null && Engine.Selected != this) {
|
|
var folder = Engine.Selected;
|
|
|
|
folder.Reset ();
|
|
|
|
folder.OnClosed ();
|
|
}
|
|
|
|
Engine.State = ImapEngineState.Selected;
|
|
Engine.Selected = this;
|
|
|
|
OnOpened ();
|
|
|
|
return Access;
|
|
}
|
|
|
|
Task<FolderAccess> OpenAsync (FolderAccess access, uint uidValidity, ulong highestModSeq, IList<UniqueId> uids, bool doAsync, CancellationToken cancellationToken)
|
|
{
|
|
if (access != FolderAccess.ReadOnly && access != FolderAccess.ReadWrite)
|
|
throw new ArgumentOutOfRangeException (nameof (access));
|
|
|
|
if (uids == null)
|
|
throw new ArgumentNullException (nameof (uids));
|
|
|
|
CheckState (false, false);
|
|
|
|
if ((Engine.Capabilities & ImapCapabilities.QuickResync) == 0)
|
|
throw new NotSupportedException ("The IMAP server does not support the QRESYNC extension.");
|
|
|
|
if (!Supports (FolderFeature.QuickResync))
|
|
throw new InvalidOperationException ("The QRESYNC extension has not been enabled.");
|
|
|
|
string qresync;
|
|
|
|
if ((Engine.Capabilities & ImapCapabilities.Annotate) != 0 && Engine.QuirksMode != ImapQuirksMode.SunMicrosystems)
|
|
qresync = string.Format (CultureInfo.InvariantCulture, "(ANNOTATE QRESYNC ({0} {1}", uidValidity, highestModSeq);
|
|
else
|
|
qresync = string.Format (CultureInfo.InvariantCulture, "(QRESYNC ({0} {1}", uidValidity, highestModSeq);
|
|
|
|
if (uids.Count > 0) {
|
|
var set = UniqueIdSet.ToString (uids);
|
|
qresync += " " + set;
|
|
}
|
|
|
|
qresync += "))";
|
|
|
|
var command = string.Format ("{0} %F {1}\r\n", SelectOrExamine (access), qresync);
|
|
var ic = new ImapCommand (Engine, cancellationToken, this, command, this);
|
|
ic.RegisterUntaggedHandler ("FETCH", QResyncFetchAsync);
|
|
|
|
return OpenAsync (ic, access, doAsync, cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Open the folder using the requested folder access.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>This variant of the <see cref="Open(FolderAccess,System.Threading.CancellationToken)"/>
|
|
/// method is meant for quick resynchronization of the folder. Before calling this method,
|
|
/// the <see cref="ImapClient.EnableQuickResync(CancellationToken)"/> method MUST be called.</para>
|
|
/// <para>You should also make sure to add listeners to the <see cref="MailFolder.MessagesVanished"/> and
|
|
/// <see cref="MailFolder.MessageFlagsChanged"/> events to get notifications of changes since
|
|
/// the last time the folder was opened.</para>
|
|
/// </remarks>
|
|
/// <returns>The <see cref="FolderAccess"/> state of the folder.</returns>
|
|
/// <param name="access">The requested folder access.</param>
|
|
/// <param name="uidValidity">The last known <see cref="MailFolder.UidValidity"/> value.</param>
|
|
/// <param name="highestModSeq">The last known <see cref="MailFolder.HighestModSeq"/> value.</param>
|
|
/// <param name="uids">The last known list of unique message identifiers.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ArgumentOutOfRangeException">
|
|
/// <paramref name="access"/> is not a valid value.
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotFoundException">
|
|
/// The <see cref="ImapFolder"/> does not exist.
|
|
/// </exception>
|
|
/// <exception cref="System.InvalidOperationException">
|
|
/// The QRESYNC feature has not been enabled.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// The IMAP server does not support the QRESYNC extension.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override FolderAccess Open (FolderAccess access, uint uidValidity, ulong highestModSeq, IList<UniqueId> uids, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return OpenAsync (access, uidValidity, highestModSeq, uids, false, cancellationToken).GetAwaiter ().GetResult ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously open the folder using the requested folder access.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>This variant of the <see cref="Open(FolderAccess,System.Threading.CancellationToken)"/>
|
|
/// method is meant for quick resynchronization of the folder. Before calling this method,
|
|
/// the <see cref="ImapClient.EnableQuickResync(CancellationToken)"/> method MUST be called.</para>
|
|
/// <para>You should also make sure to add listeners to the <see cref="MailFolder.MessagesVanished"/> and
|
|
/// <see cref="MailFolder.MessageFlagsChanged"/> events to get notifications of changes since
|
|
/// the last time the folder was opened.</para>
|
|
/// </remarks>
|
|
/// <returns>The <see cref="FolderAccess"/> state of the folder.</returns>
|
|
/// <param name="access">The requested folder access.</param>
|
|
/// <param name="uidValidity">The last known <see cref="MailFolder.UidValidity"/> value.</param>
|
|
/// <param name="highestModSeq">The last known <see cref="MailFolder.HighestModSeq"/> value.</param>
|
|
/// <param name="uids">The last known list of unique message identifiers.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ArgumentOutOfRangeException">
|
|
/// <paramref name="access"/> is not a valid value.
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotFoundException">
|
|
/// The <see cref="ImapFolder"/> does not exist.
|
|
/// </exception>
|
|
/// <exception cref="System.InvalidOperationException">
|
|
/// The QRESYNC feature has not been enabled.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// The IMAP server does not support the QRESYNC extension.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override Task<FolderAccess> OpenAsync (FolderAccess access, uint uidValidity, ulong highestModSeq, IList<UniqueId> uids, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return OpenAsync (access, uidValidity, highestModSeq, uids, true, cancellationToken);
|
|
}
|
|
|
|
Task<FolderAccess> OpenAsync (FolderAccess access, bool doAsync, CancellationToken cancellationToken)
|
|
{
|
|
if (access != FolderAccess.ReadOnly && access != FolderAccess.ReadWrite)
|
|
throw new ArgumentOutOfRangeException (nameof (access));
|
|
|
|
CheckState (false, false);
|
|
|
|
var @params = string.Empty;
|
|
|
|
if ((Engine.Capabilities & ImapCapabilities.CondStore) != 0)
|
|
@params += "CONDSTORE";
|
|
if ((Engine.Capabilities & ImapCapabilities.Annotate) != 0 && Engine.QuirksMode != ImapQuirksMode.SunMicrosystems)
|
|
@params += " ANNOTATE";
|
|
|
|
if (@params.Length > 0)
|
|
@params = " (" + @params.TrimStart () + ")";
|
|
|
|
var command = string.Format ("{0} %F{1}\r\n", SelectOrExamine (access), @params);
|
|
var ic = new ImapCommand (Engine, cancellationToken, this, command, this);
|
|
|
|
return OpenAsync (ic, access, doAsync, cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Open the folder using the requested folder access.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Opens the folder using the requested folder access.
|
|
/// </remarks>
|
|
/// <returns>The <see cref="FolderAccess"/> state of the folder.</returns>
|
|
/// <param name="access">The requested folder access.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ArgumentOutOfRangeException">
|
|
/// <paramref name="access"/> is not a valid value.
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotFoundException">
|
|
/// The <see cref="ImapFolder"/> does not exist.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override FolderAccess Open (FolderAccess access, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return OpenAsync (access, false, cancellationToken).GetAwaiter ().GetResult ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously open the folder using the requested folder access.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Opens the folder using the requested folder access.
|
|
/// </remarks>
|
|
/// <returns>The <see cref="FolderAccess"/> state of the folder.</returns>
|
|
/// <param name="access">The requested folder access.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ArgumentOutOfRangeException">
|
|
/// <paramref name="access"/> is not a valid value.
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotFoundException">
|
|
/// The <see cref="ImapFolder"/> does not exist.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override Task<FolderAccess> OpenAsync (FolderAccess access, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return OpenAsync (access, true, cancellationToken);
|
|
}
|
|
|
|
async Task CloseAsync (bool expunge, bool doAsync, CancellationToken cancellationToken)
|
|
{
|
|
CheckState (true, expunge);
|
|
|
|
ImapCommand ic;
|
|
|
|
if (expunge) {
|
|
ic = Engine.QueueCommand (cancellationToken, this, "CLOSE\r\n");
|
|
} else if ((Engine.Capabilities & ImapCapabilities.Unselect) != 0) {
|
|
ic = Engine.QueueCommand (cancellationToken, this, "UNSELECT\r\n");
|
|
} else {
|
|
ic = null;
|
|
}
|
|
|
|
if (ic != null) {
|
|
await Engine.RunAsync (ic, doAsync).ConfigureAwait (false);
|
|
|
|
ProcessResponseCodes (ic, null);
|
|
|
|
if (ic.Response != ImapCommandResponse.Ok)
|
|
throw ImapCommandException.Create (expunge ? "CLOSE" : "UNSELECT", ic);
|
|
}
|
|
|
|
Reset ();
|
|
|
|
if (Engine.Selected == this) {
|
|
Engine.State = ImapEngineState.Authenticated;
|
|
Engine.Selected = null;
|
|
OnClosed ();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Close the folder, optionally expunging the messages marked for deletion.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Closes the folder, optionally expunging the messages marked for deletion.
|
|
/// </remarks>
|
|
/// <param name="expunge">If set to <c>true</c>, expunge.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotOpenException">
|
|
/// The <see cref="ImapFolder"/> is not currently open.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override void Close (bool expunge = false, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
CloseAsync (expunge, false, cancellationToken).GetAwaiter ().GetResult ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously close the folder, optionally expunging the messages marked for deletion.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Closes the folder, optionally expunging the messages marked for deletion.
|
|
/// </remarks>
|
|
/// <returns>An asynchronous task context.</returns>
|
|
/// <param name="expunge">If set to <c>true</c>, expunge.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotOpenException">
|
|
/// The <see cref="ImapFolder"/> is not currently open.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override Task CloseAsync (bool expunge = false, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return CloseAsync (expunge, true, cancellationToken);
|
|
}
|
|
|
|
async Task<IMailFolder> GetCreatedFolderAsync (string encodedName, string id, bool specialUse, bool doAsync, CancellationToken cancellationToken)
|
|
{
|
|
var ic = new ImapCommand (Engine, cancellationToken, null, "LIST \"\" %S\r\n", encodedName);
|
|
var list = new List<ImapFolder> ();
|
|
ImapFolder folder;
|
|
|
|
ic.RegisterUntaggedHandler ("LIST", ImapUtils.ParseFolderListAsync);
|
|
ic.UserData = list;
|
|
|
|
Engine.QueueCommand (ic);
|
|
|
|
await Engine.RunAsync (ic, doAsync).ConfigureAwait (false);
|
|
|
|
ProcessResponseCodes (ic, null);
|
|
|
|
if (ic.Response != ImapCommandResponse.Ok)
|
|
throw ImapCommandException.Create ("LIST", ic);
|
|
|
|
if ((folder = ImapEngine.GetFolder (list, encodedName)) != null) {
|
|
folder.ParentFolder = this;
|
|
folder.Id = id;
|
|
|
|
if (specialUse)
|
|
Engine.AssignSpecialFolder (folder);
|
|
}
|
|
|
|
return folder;
|
|
}
|
|
|
|
async Task<IMailFolder> CreateAsync (string name, bool isMessageFolder, bool doAsync, CancellationToken cancellationToken)
|
|
{
|
|
if (name == null)
|
|
throw new ArgumentNullException (nameof (name));
|
|
|
|
if (!Engine.IsValidMailboxName (name, DirectorySeparator))
|
|
throw new ArgumentException ("The name is not a legal folder name.", nameof (name));
|
|
|
|
CheckState (false, false);
|
|
|
|
if (!string.IsNullOrEmpty (FullName) && DirectorySeparator == '\0')
|
|
throw new InvalidOperationException ("Cannot create child folders.");
|
|
|
|
var fullName = !string.IsNullOrEmpty (FullName) ? FullName + DirectorySeparator + name : name;
|
|
var encodedName = Engine.EncodeMailboxName (fullName);
|
|
var createName = encodedName;
|
|
|
|
if (!isMessageFolder && Engine.QuirksMode != ImapQuirksMode.GMail)
|
|
createName += DirectorySeparator;
|
|
|
|
var ic = Engine.QueueCommand (cancellationToken, null, "CREATE %S\r\n", createName);
|
|
|
|
await Engine.RunAsync (ic, doAsync).ConfigureAwait (false);
|
|
|
|
ProcessResponseCodes (ic, null);
|
|
|
|
if (ic.Response != ImapCommandResponse.Ok && GetResponseCode (ic, ImapResponseCodeType.AlreadyExists) == null)
|
|
throw ImapCommandException.Create ("CREATE", ic);
|
|
|
|
var code = (MailboxIdResponseCode) GetResponseCode (ic, ImapResponseCodeType.MailboxId);
|
|
var id = code?.MailboxId;
|
|
|
|
var created = await GetCreatedFolderAsync (encodedName, id, false, doAsync, cancellationToken).ConfigureAwait (false);
|
|
|
|
Engine.OnFolderCreated (created);
|
|
|
|
return created;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a new subfolder with the given name.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Creates a new subfolder with the given name.
|
|
/// </remarks>
|
|
/// <returns>The created folder.</returns>
|
|
/// <param name="name">The name of the folder to create.</param>
|
|
/// <param name="isMessageFolder"><c>true</c> if the folder will be used to contain messages; otherwise <c>false</c>.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <paramref name="name"/> is <c>null</c>.
|
|
/// </exception>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// <paramref name="name"/> is empty.
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.InvalidOperationException">
|
|
/// The <see cref="MailFolder.DirectorySeparator"/> is nil, and thus child folders cannot be created.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override IMailFolder Create (string name, bool isMessageFolder, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return CreateAsync (name, isMessageFolder, false, cancellationToken).GetAwaiter ().GetResult ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously create a new subfolder with the given name.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Creates a new subfolder with the given name.
|
|
/// </remarks>
|
|
/// <returns>The created folder.</returns>
|
|
/// <param name="name">The name of the folder to create.</param>
|
|
/// <param name="isMessageFolder"><c>true</c> if the folder will be used to contain messages; otherwise <c>false</c>.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <paramref name="name"/> is <c>null</c>.
|
|
/// </exception>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// <paramref name="name"/> is empty.
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.InvalidOperationException">
|
|
/// The <see cref="MailFolder.DirectorySeparator"/> is nil, and thus child folders cannot be created.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override Task<IMailFolder> CreateAsync (string name, bool isMessageFolder, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return CreateAsync (name, isMessageFolder, true, cancellationToken);
|
|
}
|
|
|
|
async Task<IMailFolder> CreateAsync (string name, IEnumerable<SpecialFolder> specialUses, bool doAsync, CancellationToken cancellationToken)
|
|
{
|
|
if (name == null)
|
|
throw new ArgumentNullException (nameof (name));
|
|
|
|
if (!Engine.IsValidMailboxName (name, DirectorySeparator))
|
|
throw new ArgumentException ("The name is not a legal folder name.", nameof (name));
|
|
|
|
if (specialUses == null)
|
|
throw new ArgumentNullException (nameof (specialUses));
|
|
|
|
CheckState (false, false);
|
|
|
|
if (!string.IsNullOrEmpty (FullName) && DirectorySeparator == '\0')
|
|
throw new InvalidOperationException ("Cannot create child folders.");
|
|
|
|
if ((Engine.Capabilities & ImapCapabilities.CreateSpecialUse) == 0)
|
|
throw new NotSupportedException ("The IMAP server does not support the CREATE-SPECIAL-USE extension.");
|
|
|
|
var uses = new StringBuilder ();
|
|
uint used = 0;
|
|
|
|
foreach (var use in specialUses) {
|
|
var bit = (uint) (1 << ((int) use));
|
|
|
|
if ((used & bit) != 0)
|
|
continue;
|
|
|
|
used |= bit;
|
|
|
|
if (uses.Length > 0)
|
|
uses.Append (' ');
|
|
|
|
switch (use) {
|
|
case SpecialFolder.All: uses.Append ("\\All"); break;
|
|
case SpecialFolder.Archive: uses.Append ("\\Archive"); break;
|
|
case SpecialFolder.Drafts: uses.Append ("\\Drafts"); break;
|
|
case SpecialFolder.Flagged: uses.Append ("\\Flagged"); break;
|
|
case SpecialFolder.Important: uses.Append ("\\Important"); break;
|
|
case SpecialFolder.Junk: uses.Append ("\\Junk"); break;
|
|
case SpecialFolder.Sent: uses.Append ("\\Sent"); break;
|
|
case SpecialFolder.Trash: uses.Append ("\\Trash"); break;
|
|
default: if (uses.Length > 0) uses.Length--; break;
|
|
}
|
|
}
|
|
|
|
var fullName = !string.IsNullOrEmpty (FullName) ? FullName + DirectorySeparator + name : name;
|
|
var encodedName = Engine.EncodeMailboxName (fullName);
|
|
string command;
|
|
|
|
if (uses.Length > 0)
|
|
command = string.Format ("CREATE %S (USE ({0}))\r\n", uses);
|
|
else
|
|
command = "CREATE %S\r\n";
|
|
|
|
var ic = Engine.QueueCommand (cancellationToken, null, command, encodedName);
|
|
|
|
await Engine.RunAsync (ic, doAsync).ConfigureAwait (false);
|
|
|
|
ProcessResponseCodes (ic, null);
|
|
|
|
if (ic.Response != ImapCommandResponse.Ok)
|
|
throw ImapCommandException.Create ("CREATE", ic);
|
|
|
|
var code = (MailboxIdResponseCode) GetResponseCode (ic, ImapResponseCodeType.MailboxId);
|
|
var id = code?.MailboxId;
|
|
|
|
var created = await GetCreatedFolderAsync (encodedName, id, true, doAsync, cancellationToken).ConfigureAwait (false);
|
|
|
|
Engine.OnFolderCreated (created);
|
|
|
|
return created;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a new subfolder with the given name.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Creates a new subfolder with the given name.
|
|
/// </remarks>
|
|
/// <returns>The created folder.</returns>
|
|
/// <param name="name">The name of the folder to create.</param>
|
|
/// <param name="specialUses">A list of special uses for the folder being created.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <para><paramref name="name"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="specialUses"/> is <c>null</c>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// <paramref name="name"/> is empty.
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.InvalidOperationException">
|
|
/// The <see cref="MailFolder.DirectorySeparator"/> is nil, and thus child folders cannot be created.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// The IMAP server does not support the CREATE-SPECIAL-USE extension.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override IMailFolder Create (string name, IEnumerable<SpecialFolder> specialUses, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return CreateAsync (name, specialUses, false, cancellationToken).GetAwaiter ().GetResult ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously create a new subfolder with the given name.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Creates a new subfolder with the given name.
|
|
/// </remarks>
|
|
/// <returns>The created folder.</returns>
|
|
/// <param name="name">The name of the folder to create.</param>
|
|
/// <param name="specialUses">A list of special uses for the folder being created.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <para><paramref name="name"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="specialUses"/> is <c>null</c>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// <paramref name="name"/> is empty.
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.InvalidOperationException">
|
|
/// The <see cref="MailFolder.DirectorySeparator"/> is nil, and thus child folders cannot be created.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// The IMAP server does not support the CREATE-SPECIAL-USE extension.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override Task<IMailFolder> CreateAsync (string name, IEnumerable<SpecialFolder> specialUses, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return CreateAsync (name, specialUses, true, cancellationToken);
|
|
}
|
|
|
|
async Task RenameAsync (IMailFolder parent, string name, bool doAsync, CancellationToken cancellationToken)
|
|
{
|
|
if (parent == null)
|
|
throw new ArgumentNullException (nameof (parent));
|
|
|
|
if (!(parent is ImapFolder) || ((ImapFolder) parent).Engine != Engine)
|
|
throw new ArgumentException ("The parent folder does not belong to this ImapClient.", nameof (parent));
|
|
|
|
if (name == null)
|
|
throw new ArgumentNullException (nameof (name));
|
|
|
|
if (!Engine.IsValidMailboxName (name, DirectorySeparator))
|
|
throw new ArgumentException ("The name is not a legal folder name.", nameof (name));
|
|
|
|
if (IsNamespace || (Attributes & FolderAttributes.Inbox) != 0)
|
|
throw new InvalidOperationException ("Cannot rename this folder.");
|
|
|
|
CheckState (false, false);
|
|
|
|
string newFullName;
|
|
|
|
if (!string.IsNullOrEmpty (parent.FullName))
|
|
newFullName = parent.FullName + parent.DirectorySeparator + name;
|
|
else
|
|
newFullName = name;
|
|
|
|
var encodedName = Engine.EncodeMailboxName (newFullName);
|
|
var ic = Engine.QueueCommand (cancellationToken, null, "RENAME %F %S\r\n", this, encodedName);
|
|
var oldFullName = FullName;
|
|
|
|
await Engine.RunAsync (ic, doAsync).ConfigureAwait (false);
|
|
|
|
ProcessResponseCodes (ic, this);
|
|
|
|
if (ic.Response != ImapCommandResponse.Ok)
|
|
throw ImapCommandException.Create ("RENAME", ic);
|
|
|
|
Engine.FolderCache.Remove (EncodedName);
|
|
Engine.FolderCache[encodedName] = this;
|
|
|
|
ParentFolder = parent;
|
|
|
|
FullName = Engine.DecodeMailboxName (encodedName);
|
|
EncodedName = encodedName;
|
|
Name = name;
|
|
|
|
Reset ();
|
|
|
|
if (Engine.Selected == this) {
|
|
Engine.State = ImapEngineState.Authenticated;
|
|
Engine.Selected = null;
|
|
OnClosed ();
|
|
}
|
|
|
|
OnRenamed (oldFullName, FullName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Rename the folder to exist with a new name under a new parent folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Renames the folder to exist with a new name under a new parent folder.
|
|
/// </remarks>
|
|
/// <param name="parent">The new parent folder.</param>
|
|
/// <param name="name">The new name of the folder.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <para><paramref name="parent"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="name"/> is <c>null</c>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// <para><paramref name="parent"/> does not belong to the <see cref="ImapClient"/>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="name"/> is not a legal folder name.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotFoundException">
|
|
/// The <see cref="ImapFolder"/> does not exist.
|
|
/// </exception>
|
|
/// <exception cref="System.InvalidOperationException">
|
|
/// The folder cannot be renamed (it is either a namespace or the Inbox).
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override void Rename (IMailFolder parent, string name, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
RenameAsync (parent, name, false, cancellationToken).GetAwaiter ().GetResult ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously rename the folder to exist with a new name under a new parent folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Renames the folder to exist with a new name under a new parent folder.
|
|
/// </remarks>
|
|
/// <returns>An awaitable task.</returns>
|
|
/// <param name="parent">The new parent folder.</param>
|
|
/// <param name="name">The new name of the folder.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <para><paramref name="parent"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="name"/> is <c>null</c>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// <para><paramref name="parent"/> does not belong to the <see cref="ImapClient"/>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="name"/> is not a legal folder name.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotFoundException">
|
|
/// The <see cref="ImapFolder"/> does not exist.
|
|
/// </exception>
|
|
/// <exception cref="System.InvalidOperationException">
|
|
/// The folder cannot be renamed (it is either a namespace or the Inbox).
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override Task RenameAsync (IMailFolder parent, string name, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return RenameAsync (parent, name, true, cancellationToken);
|
|
}
|
|
|
|
async Task DeleteAsync (bool doAsync, CancellationToken cancellationToken)
|
|
{
|
|
if (IsNamespace || (Attributes & FolderAttributes.Inbox) != 0)
|
|
throw new InvalidOperationException ("Cannot delete this folder.");
|
|
|
|
CheckState (false, false);
|
|
|
|
var ic = Engine.QueueCommand (cancellationToken, null, "DELETE %F\r\n", this);
|
|
|
|
await Engine.RunAsync (ic, doAsync).ConfigureAwait (false);
|
|
|
|
ProcessResponseCodes (ic, this);
|
|
|
|
if (ic.Response != ImapCommandResponse.Ok)
|
|
throw ImapCommandException.Create ("DELETE", ic);
|
|
|
|
Reset ();
|
|
|
|
if (Engine.Selected == this) {
|
|
Engine.State = ImapEngineState.Authenticated;
|
|
Engine.Selected = null;
|
|
OnClosed ();
|
|
}
|
|
|
|
Attributes |= FolderAttributes.NonExistent;
|
|
OnDeleted ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Delete the folder on the IMAP server.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>Deletes the folder on the IMAP server.</para>
|
|
/// <note type="note">This method will not delete any child folders.</note>
|
|
/// </remarks>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.InvalidOperationException">
|
|
/// The folder cannot be deleted (it is either a namespace or the Inbox).
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override void Delete (CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
DeleteAsync (false, cancellationToken).GetAwaiter ().GetResult ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously delete the folder on the IMAP server.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>Deletes the folder on the IMAP server.</para>
|
|
/// <note type="note">This method will not delete any child folders.</note>
|
|
/// </remarks>
|
|
/// <returns>An awaitable task.</returns>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.InvalidOperationException">
|
|
/// The folder cannot be deleted (it is either a namespace or the Inbox).
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override Task DeleteAsync (CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return DeleteAsync (true, cancellationToken);
|
|
}
|
|
|
|
async Task SubscribeAsync (bool doAsync, CancellationToken cancellationToken)
|
|
{
|
|
CheckState (false, false);
|
|
|
|
var ic = Engine.QueueCommand (cancellationToken, null, "SUBSCRIBE %F\r\n", this);
|
|
|
|
await Engine.RunAsync (ic, doAsync).ConfigureAwait (false);
|
|
|
|
ProcessResponseCodes (ic, null);
|
|
|
|
if (ic.Response != ImapCommandResponse.Ok)
|
|
throw ImapCommandException.Create ("SUBSCRIBE", ic);
|
|
|
|
if ((Attributes & FolderAttributes.Subscribed) == 0) {
|
|
Attributes |= FolderAttributes.Subscribed;
|
|
|
|
OnSubscribed ();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Subscribe the folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Subscribes the folder.
|
|
/// </remarks>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override void Subscribe (CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
SubscribeAsync (false, cancellationToken).GetAwaiter ().GetResult ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously subscribe the folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Subscribes the folder.
|
|
/// </remarks>
|
|
/// <returns>An awaitable task.</returns>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override Task SubscribeAsync (CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return SubscribeAsync (true, cancellationToken);
|
|
}
|
|
|
|
async Task UnsubscribeAsync (bool doAsync, CancellationToken cancellationToken)
|
|
{
|
|
CheckState (false, false);
|
|
|
|
var ic = Engine.QueueCommand (cancellationToken, null, "UNSUBSCRIBE %F\r\n", this);
|
|
|
|
await Engine.RunAsync (ic, doAsync).ConfigureAwait (false);
|
|
|
|
ProcessResponseCodes (ic, null);
|
|
|
|
if (ic.Response != ImapCommandResponse.Ok)
|
|
throw ImapCommandException.Create ("UNSUBSCRIBE", ic);
|
|
|
|
if ((Attributes & FolderAttributes.Subscribed) != 0) {
|
|
Attributes &= ~FolderAttributes.Subscribed;
|
|
|
|
OnUnsubscribed ();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Unsubscribe the folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Unsubscribes the folder.
|
|
/// </remarks>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override void Unsubscribe (CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
UnsubscribeAsync (false, cancellationToken).GetAwaiter ().GetResult ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously unsubscribe the folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Unsubscribes the folder.
|
|
/// </remarks>
|
|
/// <returns>An awaitable task.</returns>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override Task UnsubscribeAsync (CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return UnsubscribeAsync (true, cancellationToken);
|
|
}
|
|
|
|
async Task<IList<IMailFolder>> GetSubfoldersAsync (StatusItems items, bool subscribedOnly, bool doAsync, CancellationToken cancellationToken)
|
|
{
|
|
CheckState (false, false);
|
|
|
|
// Note: folder names can contain wildcards (including '*' and '%'), so replace '*' with '%'
|
|
// in order to reduce the list of folders returned by our LIST command.
|
|
var pattern = new StringBuilder (EncodedName.Length + 2);
|
|
pattern.Append (EncodedName);
|
|
for (int i = 0; i < EncodedName.Length; i++) {
|
|
if (pattern[i] == '*')
|
|
pattern[i] = '%';
|
|
}
|
|
if (pattern.Length > 0)
|
|
pattern.Append (DirectorySeparator);
|
|
pattern.Append ('%');
|
|
|
|
var children = new List<IMailFolder> ();
|
|
var status = items != StatusItems.None;
|
|
var list = new List<ImapFolder> ();
|
|
var command = new StringBuilder ();
|
|
var returnsSubscribed = false;
|
|
var lsub = subscribedOnly;
|
|
|
|
if (subscribedOnly) {
|
|
if ((Engine.Capabilities & ImapCapabilities.ListExtended) != 0) {
|
|
command.Append ("LIST (SUBSCRIBED)");
|
|
returnsSubscribed = true;
|
|
lsub = false;
|
|
} else {
|
|
command.Append ("LSUB");
|
|
}
|
|
} else {
|
|
command.Append ("LIST");
|
|
}
|
|
|
|
command.Append (" \"\" %S");
|
|
|
|
if (!lsub) {
|
|
if (items != StatusItems.None && (Engine.Capabilities & ImapCapabilities.ListStatus) != 0) {
|
|
command.Append (" RETURN (");
|
|
|
|
if ((Engine.Capabilities & ImapCapabilities.ListExtended) != 0) {
|
|
if (!subscribedOnly) {
|
|
command.Append ("SUBSCRIBED ");
|
|
returnsSubscribed = true;
|
|
}
|
|
command.Append ("CHILDREN ");
|
|
}
|
|
|
|
command.AppendFormat ("STATUS ({0})", Engine.GetStatusQuery (items));
|
|
command.Append (')');
|
|
status = false;
|
|
} else if ((Engine.Capabilities & ImapCapabilities.ListExtended) != 0) {
|
|
command.Append (" RETURN (");
|
|
if (!subscribedOnly) {
|
|
command.Append ("SUBSCRIBED ");
|
|
returnsSubscribed = true;
|
|
}
|
|
command.Append ("CHILDREN");
|
|
command.Append (')');
|
|
}
|
|
}
|
|
|
|
command.Append ("\r\n");
|
|
|
|
var ic = new ImapCommand (Engine, cancellationToken, null, command.ToString (), pattern.ToString ());
|
|
ic.RegisterUntaggedHandler (lsub ? "LSUB" : "LIST", ImapUtils.ParseFolderListAsync);
|
|
ic.ListReturnsSubscribed = returnsSubscribed;
|
|
ic.UserData = list;
|
|
ic.Lsub = lsub;
|
|
|
|
Engine.QueueCommand (ic);
|
|
|
|
await Engine.RunAsync (ic, doAsync).ConfigureAwait (false);
|
|
|
|
// Note: Due to the fact that folders can contain wildcards in them, we'll need to
|
|
// filter out any folders that are not children of this folder.
|
|
var prefix = FullName.Length > 0 ? FullName + DirectorySeparator : string.Empty;
|
|
prefix = ImapUtils.CanonicalizeMailboxName (prefix, DirectorySeparator);
|
|
var unparented = false;
|
|
|
|
foreach (var folder in list) {
|
|
var canonicalFullName = ImapUtils.CanonicalizeMailboxName (folder.FullName, folder.DirectorySeparator);
|
|
var canonicalName = ImapUtils.IsInbox (folder.FullName) ? "INBOX" : folder.Name;
|
|
|
|
if (!canonicalFullName.StartsWith (prefix, StringComparison.Ordinal)) {
|
|
unparented |= folder.ParentFolder == null;
|
|
continue;
|
|
}
|
|
|
|
if (string.Compare (canonicalFullName, prefix.Length, canonicalName, 0, canonicalName.Length, StringComparison.Ordinal) != 0) {
|
|
unparented |= folder.ParentFolder == null;
|
|
continue;
|
|
}
|
|
|
|
folder.ParentFolder = this;
|
|
children.Add (folder);
|
|
}
|
|
|
|
ProcessResponseCodes (ic, null);
|
|
|
|
if (ic.Response != ImapCommandResponse.Ok)
|
|
throw ImapCommandException.Create (lsub ? "LSUB" : "LIST", ic);
|
|
|
|
// Note: if any folders returned in the LIST command are unparented, have the ImapEngine look up their
|
|
// parent folders now so that they are not left in an inconsistent state.
|
|
if (unparented)
|
|
await Engine.LookupParentFoldersAsync (list, doAsync, cancellationToken).ConfigureAwait (false);
|
|
|
|
if (status) {
|
|
for (int i = 0; i < children.Count; i++) {
|
|
if (children[i].Exists)
|
|
await ((ImapFolder) children[i]).StatusAsync (items, doAsync, false, cancellationToken).ConfigureAwait (false);
|
|
}
|
|
}
|
|
|
|
return children;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the subfolders.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Gets the subfolders.
|
|
/// </remarks>
|
|
/// <returns>The subfolders.</returns>
|
|
/// <param name="items">The status items to pre-populate.</param>
|
|
/// <param name="subscribedOnly">If set to <c>true</c>, only subscribed folders will be listed.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override IList<IMailFolder> GetSubfolders (StatusItems items, bool subscribedOnly = false, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return GetSubfoldersAsync (items, subscribedOnly, false, cancellationToken).GetAwaiter ().GetResult ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously get the subfolders.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Gets the subfolders.
|
|
/// </remarks>
|
|
/// <returns>The subfolders.</returns>
|
|
/// <param name="items">The status items to pre-populate.</param>
|
|
/// <param name="subscribedOnly">If set to <c>true</c>, only subscribed folders will be listed.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override Task<IList<IMailFolder>> GetSubfoldersAsync (StatusItems items, bool subscribedOnly = false, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return GetSubfoldersAsync (items, subscribedOnly, true, cancellationToken);
|
|
}
|
|
|
|
async Task<IMailFolder> GetSubfolderAsync (string name, bool doAsync, CancellationToken cancellationToken)
|
|
{
|
|
if (name == null)
|
|
throw new ArgumentNullException (nameof (name));
|
|
|
|
if (!Engine.IsValidMailboxName (name, DirectorySeparator))
|
|
throw new ArgumentException ("The name of the subfolder is invalid.", nameof (name));
|
|
|
|
CheckState (false, false);
|
|
|
|
var fullName = FullName.Length > 0 ? FullName + DirectorySeparator + name : name;
|
|
var encodedName = Engine.EncodeMailboxName (fullName);
|
|
List<ImapFolder> list;
|
|
ImapFolder folder;
|
|
|
|
if (Engine.GetCachedFolder (encodedName, out folder))
|
|
return folder;
|
|
|
|
// Note: folder names can contain wildcards (including '*' and '%'), so replace '*' with '%'
|
|
// in order to reduce the list of folders returned by our LIST command.
|
|
var pattern = encodedName.Replace ('*', '%');
|
|
|
|
var ic = new ImapCommand (Engine, cancellationToken, null, "LIST \"\" %S\r\n", pattern);
|
|
ic.RegisterUntaggedHandler ("LIST", ImapUtils.ParseFolderListAsync);
|
|
ic.UserData = list = new List<ImapFolder> ();
|
|
|
|
Engine.QueueCommand (ic);
|
|
|
|
await Engine.RunAsync (ic, doAsync).ConfigureAwait (false);
|
|
|
|
ProcessResponseCodes (ic, null);
|
|
|
|
if (ic.Response != ImapCommandResponse.Ok)
|
|
throw ImapCommandException.Create ("LIST", ic);
|
|
|
|
if ((folder = ImapEngine.GetFolder (list, encodedName)) != null)
|
|
folder.ParentFolder = this;
|
|
|
|
if (list.Count > 1 || folder == null) {
|
|
// Note: if any folders returned in the LIST command are unparented, have the ImapEngine look up their
|
|
// parent folders now so that they are not left in an inconsistent state.
|
|
await Engine.LookupParentFoldersAsync (list, doAsync, cancellationToken).ConfigureAwait (false);
|
|
}
|
|
|
|
if (folder == null)
|
|
throw new FolderNotFoundException (fullName);
|
|
|
|
return folder;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the specified subfolder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Gets the specified subfolder.
|
|
/// </remarks>
|
|
/// <returns>The subfolder.</returns>
|
|
/// <param name="name">The name of the subfolder.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <paramref name="name"/> is <c>null</c>.
|
|
/// </exception>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// <paramref name="name"/> is either an empty string or contains the <see cref="MailFolder.DirectorySeparator"/>.
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotFoundException">
|
|
/// The requested folder could not be found.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override IMailFolder GetSubfolder (string name, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return GetSubfolderAsync (name, false, cancellationToken).GetAwaiter ().GetResult ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously get the specified subfolder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Gets the specified subfolder.
|
|
/// </remarks>
|
|
/// <returns>The subfolder.</returns>
|
|
/// <param name="name">The name of the subfolder.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <paramref name="name"/> is <c>null</c>.
|
|
/// </exception>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// <paramref name="name"/> is either an empty string or contains the <see cref="MailFolder.DirectorySeparator"/>.
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotFoundException">
|
|
/// The requested folder could not be found.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override Task<IMailFolder> GetSubfolderAsync (string name, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return GetSubfolderAsync (name, true, cancellationToken);
|
|
}
|
|
|
|
async Task CheckAsync (bool doAsync, CancellationToken cancellationToken)
|
|
{
|
|
CheckState (true, false);
|
|
|
|
var ic = Engine.QueueCommand (cancellationToken, this, "CHECK\r\n");
|
|
|
|
await Engine.RunAsync (ic, doAsync).ConfigureAwait (false);
|
|
|
|
ProcessResponseCodes (ic, null);
|
|
|
|
if (ic.Response != ImapCommandResponse.Ok)
|
|
throw ImapCommandException.Create ("CHECK", ic);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Force the server to sync its in-memory state with its disk state.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>The <c>CHECK</c> command forces the IMAP server to sync its
|
|
/// in-memory state with its disk state.</para>
|
|
/// <para>For more information about the <c>CHECK</c> command, see
|
|
/// <a href="https://tools.ietf.org/html/rfc3501#section-6.4.1">rfc350101</a>.</para>
|
|
/// </remarks>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotOpenException">
|
|
/// The <see cref="ImapFolder"/> is not currently open.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override void Check (CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
CheckAsync (false, cancellationToken).GetAwaiter ().GetResult ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously force the server to sync its in-memory state with its disk state.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>The <c>CHECK</c> command forces the IMAP server to sync its
|
|
/// in-memory state with its disk state.</para>
|
|
/// <para>For more information about the <c>CHECK</c> command, see
|
|
/// <a href="https://tools.ietf.org/html/rfc3501#section-6.4.1">rfc350101</a>.</para>
|
|
/// </remarks>
|
|
/// <returns>An awaitable task.</returns>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotOpenException">
|
|
/// The <see cref="ImapFolder"/> is not currently open.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override Task CheckAsync (CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return CheckAsync (true, cancellationToken);
|
|
}
|
|
|
|
internal async Task StatusAsync (StatusItems items, bool doAsync, bool throwNotFound, CancellationToken cancellationToken)
|
|
{
|
|
if ((Engine.Capabilities & ImapCapabilities.Status) == 0)
|
|
throw new NotSupportedException ("The IMAP server does not support the STATUS command.");
|
|
|
|
CheckState (false, false);
|
|
|
|
if (items == StatusItems.None)
|
|
return;
|
|
|
|
var command = string.Format ("STATUS %F ({0})\r\n", Engine.GetStatusQuery (items));
|
|
var ic = Engine.QueueCommand (cancellationToken, null, command, this);
|
|
|
|
await Engine.RunAsync (ic, doAsync).ConfigureAwait (false);
|
|
|
|
ProcessResponseCodes (ic, this, throwNotFound);
|
|
|
|
if (ic.Response != ImapCommandResponse.Ok)
|
|
throw ImapCommandException.Create ("STATUS", ic);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Update the values of the specified items.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>Updates the values of the specified items.</para>
|
|
/// <para>The <see cref="Status(StatusItems, System.Threading.CancellationToken)"/> method
|
|
/// MUST NOT be used on a folder that is already in the opened state. Instead, other ways
|
|
/// of getting the desired information should be used.</para>
|
|
/// <para>For example, a common use for the <see cref="Status(StatusItems,System.Threading.CancellationToken)"/>
|
|
/// method is to get the number of unread messages in the folder. When the folder is open, however, it is
|
|
/// possible to use the <see cref="ImapFolder.Search(MailKit.Search.SearchQuery, System.Threading.CancellationToken)"/>
|
|
/// method to query for the list of unread messages.</para>
|
|
/// <para>For more information about the <c>STATUS</c> command, see
|
|
/// <a href="https://tools.ietf.org/html/rfc3501#section-6.3.10">rfc3501</a>.</para>
|
|
/// </remarks>
|
|
/// <param name="items">The items to update.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotFoundException">
|
|
/// The <see cref="ImapFolder"/> does not exist.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// The IMAP server does not support the STATUS command.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override void Status (StatusItems items, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
StatusAsync (items, false, true, cancellationToken).GetAwaiter ().GetResult ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously update the values of the specified items.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>Updates the values of the specified items.</para>
|
|
/// <para>The <see cref="Status(StatusItems, System.Threading.CancellationToken)"/> method
|
|
/// MUST NOT be used on a folder that is already in the opened state. Instead, other ways
|
|
/// of getting the desired information should be used.</para>
|
|
/// <para>For example, a common use for the <see cref="Status(StatusItems,System.Threading.CancellationToken)"/>
|
|
/// method is to get the number of unread messages in the folder. When the folder is open, however, it is
|
|
/// possible to use the <see cref="ImapFolder.Search(MailKit.Search.SearchQuery, System.Threading.CancellationToken)"/>
|
|
/// method to query for the list of unread messages.</para>
|
|
/// <para>For more information about the <c>STATUS</c> command, see
|
|
/// <a href="https://tools.ietf.org/html/rfc3501#section-6.3.10">rfc3501</a>.</para>
|
|
/// </remarks>
|
|
/// <returns>An awaitable task.</returns>
|
|
/// <param name="items">The items to update.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotFoundException">
|
|
/// The <see cref="ImapFolder"/> does not exist.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// The IMAP server does not support the STATUS command.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override Task StatusAsync (StatusItems items, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return StatusAsync (items, true, true, cancellationToken);
|
|
}
|
|
|
|
static async Task<string> ReadStringTokenAsync (ImapEngine engine, string format, bool doAsync, CancellationToken cancellationToken)
|
|
{
|
|
var token = await engine.ReadTokenAsync (ImapStream.AtomSpecials, doAsync, cancellationToken).ConfigureAwait (false);
|
|
|
|
switch (token.Type) {
|
|
case ImapTokenType.Literal: return await engine.ReadLiteralAsync (doAsync, cancellationToken).ConfigureAwait (false);
|
|
case ImapTokenType.QString: return (string) token.Value;
|
|
case ImapTokenType.Atom: return (string) token.Value;
|
|
default:
|
|
throw ImapEngine.UnexpectedToken (format, token);
|
|
}
|
|
}
|
|
|
|
static async Task UntaggedAclAsync (ImapEngine engine, ImapCommand ic, int index, bool doAsync)
|
|
{
|
|
string format = string.Format (ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "ACL", "{0}");
|
|
var acl = (AccessControlList) ic.UserData;
|
|
string name, rights;
|
|
ImapToken token;
|
|
|
|
// read the mailbox name
|
|
await ReadStringTokenAsync (engine, format, doAsync, ic.CancellationToken).ConfigureAwait (false);
|
|
|
|
do {
|
|
name = await ReadStringTokenAsync (engine, format, doAsync, ic.CancellationToken).ConfigureAwait (false);
|
|
rights = await ReadStringTokenAsync (engine, format, doAsync, ic.CancellationToken).ConfigureAwait (false);
|
|
|
|
acl.Add (new AccessControl (name, rights));
|
|
|
|
token = await engine.PeekTokenAsync (doAsync, ic.CancellationToken).ConfigureAwait (false);
|
|
} while (token.Type != ImapTokenType.Eoln);
|
|
}
|
|
|
|
async Task<AccessControlList> GetAccessControlListAsync (bool doAsync, CancellationToken cancellationToken)
|
|
{
|
|
if ((Engine.Capabilities & ImapCapabilities.Acl) == 0)
|
|
throw new NotSupportedException ("The IMAP server does not support the ACL extension.");
|
|
|
|
CheckState (false, false);
|
|
|
|
var ic = new ImapCommand (Engine, cancellationToken, null, "GETACL %F\r\n", this);
|
|
ic.RegisterUntaggedHandler ("ACL", UntaggedAclAsync);
|
|
ic.UserData = new AccessControlList ();
|
|
|
|
Engine.QueueCommand (ic);
|
|
|
|
await Engine.RunAsync (ic, doAsync).ConfigureAwait (false);
|
|
|
|
ProcessResponseCodes (ic, null);
|
|
|
|
if (ic.Response != ImapCommandResponse.Ok)
|
|
throw ImapCommandException.Create ("GETACL", ic);
|
|
|
|
return (AccessControlList) ic.UserData;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the complete access control list for the folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Gets the complete access control list for the folder.
|
|
/// </remarks>
|
|
/// <returns>The access control list.</returns>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// The IMAP server does not support the ACL extension.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The command failed.
|
|
/// </exception>
|
|
public override AccessControlList GetAccessControlList (CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return GetAccessControlListAsync (false, cancellationToken).GetAwaiter ().GetResult ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously get the complete access control list for the folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Gets the complete access control list for the folder.
|
|
/// </remarks>
|
|
/// <returns>The access control list.</returns>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// The IMAP server does not support the ACL extension.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The command failed.
|
|
/// </exception>
|
|
public override Task<AccessControlList> GetAccessControlListAsync (CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return GetAccessControlListAsync (true, cancellationToken);
|
|
}
|
|
|
|
static async Task UntaggedListRightsAsync (ImapEngine engine, ImapCommand ic, int index, bool doAsync)
|
|
{
|
|
string format = string.Format (ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "LISTRIGHTS", "{0}");
|
|
var access = (AccessRights) ic.UserData;
|
|
ImapToken token;
|
|
|
|
// read the mailbox name
|
|
await ReadStringTokenAsync (engine, format, doAsync, ic.CancellationToken).ConfigureAwait (false);
|
|
|
|
// read the identity name
|
|
await ReadStringTokenAsync (engine, format, doAsync, ic.CancellationToken).ConfigureAwait (false);
|
|
|
|
do {
|
|
var rights = await ReadStringTokenAsync (engine, format, doAsync, ic.CancellationToken).ConfigureAwait (false);
|
|
|
|
access.AddRange (rights);
|
|
|
|
token = await engine.PeekTokenAsync (doAsync, ic.CancellationToken).ConfigureAwait (false);
|
|
} while (token.Type != ImapTokenType.Eoln);
|
|
}
|
|
|
|
async Task<AccessRights> GetAccessRightsAsync (string name, bool doAsync, CancellationToken cancellationToken)
|
|
{
|
|
if (name == null)
|
|
throw new ArgumentNullException (nameof (name));
|
|
|
|
if ((Engine.Capabilities & ImapCapabilities.Acl) == 0)
|
|
throw new NotSupportedException ("The IMAP server does not support the ACL extension.");
|
|
|
|
CheckState (false, false);
|
|
|
|
var ic = new ImapCommand (Engine, cancellationToken, null, "LISTRIGHTS %F %S\r\n", this, name);
|
|
ic.RegisterUntaggedHandler ("LISTRIGHTS", UntaggedListRightsAsync);
|
|
ic.UserData = new AccessRights ();
|
|
|
|
Engine.QueueCommand (ic);
|
|
|
|
await Engine.RunAsync (ic, doAsync).ConfigureAwait (false);
|
|
|
|
ProcessResponseCodes (ic, null);
|
|
|
|
if (ic.Response != ImapCommandResponse.Ok)
|
|
throw ImapCommandException.Create ("LISTRIGHTS", ic);
|
|
|
|
return (AccessRights) ic.UserData;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the access rights for a particular identifier.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Gets the access rights for a particular identifier.
|
|
/// </remarks>
|
|
/// <returns>The access rights.</returns>
|
|
/// <param name="name">The identifier name.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <paramref name="name"/> is <c>null</c>.
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// The IMAP server does not support the ACL extension.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The command failed.
|
|
/// </exception>
|
|
public override AccessRights GetAccessRights (string name, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return GetAccessRightsAsync (name, false, cancellationToken).GetAwaiter ().GetResult ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously get the access rights for a particular identifier.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Gets the access rights for a particular identifier.
|
|
/// </remarks>
|
|
/// <returns>The access rights.</returns>
|
|
/// <param name="name">The identifier name.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <paramref name="name"/> is <c>null</c>.
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// The IMAP server does not support the ACL extension.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The command failed.
|
|
/// </exception>
|
|
public override Task<AccessRights> GetAccessRightsAsync (string name, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return GetAccessRightsAsync (name, true, cancellationToken);
|
|
}
|
|
|
|
static async Task UntaggedMyRightsAsync (ImapEngine engine, ImapCommand ic, int index, bool doAsync)
|
|
{
|
|
string format = string.Format (ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "MYRIGHTS", "{0}");
|
|
var access = (AccessRights) ic.UserData;
|
|
|
|
// read the mailbox name
|
|
await ReadStringTokenAsync (engine, format, doAsync, ic.CancellationToken).ConfigureAwait (false);
|
|
|
|
// read the access rights
|
|
access.AddRange (await ReadStringTokenAsync (engine, format, doAsync, ic.CancellationToken).ConfigureAwait (false));
|
|
}
|
|
|
|
async Task<AccessRights> GetMyAccessRightsAsync (bool doAsync, CancellationToken cancellationToken)
|
|
{
|
|
if ((Engine.Capabilities & ImapCapabilities.Acl) == 0)
|
|
throw new NotSupportedException ("The IMAP server does not support the ACL extension.");
|
|
|
|
CheckState (false, false);
|
|
|
|
var ic = new ImapCommand (Engine, cancellationToken, null, "MYRIGHTS %F\r\n", this);
|
|
ic.RegisterUntaggedHandler ("MYRIGHTS", UntaggedMyRightsAsync);
|
|
ic.UserData = new AccessRights ();
|
|
|
|
Engine.QueueCommand (ic);
|
|
|
|
await Engine.RunAsync (ic, doAsync).ConfigureAwait (false);
|
|
|
|
ProcessResponseCodes (ic, null);
|
|
|
|
if (ic.Response != ImapCommandResponse.Ok)
|
|
throw ImapCommandException.Create ("MYRIGHTS", ic);
|
|
|
|
return (AccessRights) ic.UserData;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the access rights for the current authenticated user.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Gets the access rights for the current authenticated user.
|
|
/// </remarks>
|
|
/// <returns>The access rights.</returns>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// The IMAP server does not support the ACL extension.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The command failed.
|
|
/// </exception>
|
|
public override AccessRights GetMyAccessRights (CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return GetMyAccessRightsAsync (false, cancellationToken).GetAwaiter ().GetResult ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously get the access rights for the current authenticated user.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Gets the access rights for the current authenticated user.
|
|
/// </remarks>
|
|
/// <returns>The access rights.</returns>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// The IMAP server does not support the ACL extension.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The command failed.
|
|
/// </exception>
|
|
public override Task<AccessRights> GetMyAccessRightsAsync (CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return GetMyAccessRightsAsync (true, cancellationToken);
|
|
}
|
|
|
|
async Task ModifyAccessRightsAsync (string name, AccessRights rights, string action, bool doAsync, CancellationToken cancellationToken)
|
|
{
|
|
if ((Engine.Capabilities & ImapCapabilities.Acl) == 0)
|
|
throw new NotSupportedException ("The IMAP server does not support the ACL extension.");
|
|
|
|
CheckState (false, false);
|
|
|
|
var ic = Engine.QueueCommand (cancellationToken, null, "SETACL %F %S %S\r\n", this, name, action + rights);
|
|
|
|
await Engine.RunAsync (ic, doAsync).ConfigureAwait (false);
|
|
|
|
ProcessResponseCodes (ic, null);
|
|
|
|
if (ic.Response != ImapCommandResponse.Ok)
|
|
throw ImapCommandException.Create ("SETACL", ic);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Add access rights for the specified identity.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Adds the given access rights for the specified identity.
|
|
/// </remarks>
|
|
/// <param name="name">The identity name.</param>
|
|
/// <param name="rights">The access rights.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <para><paramref name="name"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="rights"/> is <c>null</c>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// No rights were specified.
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// The IMAP server does not support the ACL extension.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The command failed.
|
|
/// </exception>
|
|
public override void AddAccessRights (string name, AccessRights rights, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
if (name == null)
|
|
throw new ArgumentNullException (nameof (name));
|
|
|
|
if (rights == null)
|
|
throw new ArgumentNullException (nameof (rights));
|
|
|
|
if (rights.Count == 0)
|
|
throw new ArgumentException ("No rights were specified.", nameof (rights));
|
|
|
|
ModifyAccessRightsAsync (name, rights, "+", false, cancellationToken).GetAwaiter ().GetResult ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously add access rights for the specified identity.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Adds the given access rights for the specified identity.
|
|
/// </remarks>
|
|
/// <returns>An asynchronous task context.</returns>
|
|
/// <param name="name">The identity name.</param>
|
|
/// <param name="rights">The access rights.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <para><paramref name="name"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="rights"/> is <c>null</c>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// No rights were specified.
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// The IMAP server does not support the ACL extension.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The command failed.
|
|
/// </exception>
|
|
public override Task AddAccessRightsAsync (string name, AccessRights rights, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
if (name == null)
|
|
throw new ArgumentNullException (nameof (name));
|
|
|
|
if (rights == null)
|
|
throw new ArgumentNullException (nameof (rights));
|
|
|
|
if (rights.Count == 0)
|
|
throw new ArgumentException ("No rights were specified.", nameof (rights));
|
|
|
|
return ModifyAccessRightsAsync (name, rights, "+", true, cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove access rights for the specified identity.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Removes the given access rights for the specified identity.
|
|
/// </remarks>
|
|
/// <param name="name">The identity name.</param>
|
|
/// <param name="rights">The access rights.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <para><paramref name="name"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="rights"/> is <c>null</c>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// No rights were specified.
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// The IMAP server does not support the ACL extension.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The command failed.
|
|
/// </exception>
|
|
public override void RemoveAccessRights (string name, AccessRights rights, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
if (name == null)
|
|
throw new ArgumentNullException (nameof (name));
|
|
|
|
if (rights == null)
|
|
throw new ArgumentNullException (nameof (rights));
|
|
|
|
if (rights.Count == 0)
|
|
throw new ArgumentException ("No rights were specified.", nameof (rights));
|
|
|
|
ModifyAccessRightsAsync (name, rights, "-", false, cancellationToken).GetAwaiter ().GetResult ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously remove access rights for the specified identity.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Removes the given access rights for the specified identity.
|
|
/// </remarks>
|
|
/// <returns>An asynchronous task context.</returns>
|
|
/// <param name="name">The identity name.</param>
|
|
/// <param name="rights">The access rights.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <para><paramref name="name"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="rights"/> is <c>null</c>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// No rights were specified.
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// The IMAP server does not support the ACL extension.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The command failed.
|
|
/// </exception>
|
|
public override Task RemoveAccessRightsAsync (string name, AccessRights rights, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
if (name == null)
|
|
throw new ArgumentNullException (nameof (name));
|
|
|
|
if (rights == null)
|
|
throw new ArgumentNullException (nameof (rights));
|
|
|
|
if (rights.Count == 0)
|
|
throw new ArgumentException ("No rights were specified.", nameof (rights));
|
|
|
|
return ModifyAccessRightsAsync (name, rights, "-", true, cancellationToken);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the access rights for the specified identity.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Sets the access rights for the specified identity.
|
|
/// </remarks>
|
|
/// <param name="name">The identity name.</param>
|
|
/// <param name="rights">The access rights.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <para><paramref name="name"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="rights"/> is <c>null</c>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// The IMAP server does not support the ACL extension.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The command failed.
|
|
/// </exception>
|
|
public override void SetAccessRights (string name, AccessRights rights, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
if (name == null)
|
|
throw new ArgumentNullException (nameof (name));
|
|
|
|
if (rights == null)
|
|
throw new ArgumentNullException (nameof (rights));
|
|
|
|
ModifyAccessRightsAsync (name, rights, string.Empty, false, cancellationToken).GetAwaiter ().GetResult ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously get the access rights for the specified identity.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Sets the access rights for the specified identity.
|
|
/// </remarks>
|
|
/// <returns>An awaitable task.</returns>
|
|
/// <param name="name">The identity name.</param>
|
|
/// <param name="rights">The access rights.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <para><paramref name="name"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="rights"/> is <c>null</c>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// The IMAP server does not support the ACL extension.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The command failed.
|
|
/// </exception>
|
|
public override Task SetAccessRightsAsync (string name, AccessRights rights, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
if (name == null)
|
|
throw new ArgumentNullException (nameof (name));
|
|
|
|
if (rights == null)
|
|
throw new ArgumentNullException (nameof (rights));
|
|
|
|
return ModifyAccessRightsAsync (name, rights, string.Empty, true, cancellationToken);
|
|
}
|
|
|
|
async Task RemoveAccessAsync (string name, bool doAsync, CancellationToken cancellationToken)
|
|
{
|
|
if (name == null)
|
|
throw new ArgumentNullException (nameof (name));
|
|
|
|
if ((Engine.Capabilities & ImapCapabilities.Acl) == 0)
|
|
throw new NotSupportedException ("The IMAP server does not support the ACL extension.");
|
|
|
|
CheckState (false, false);
|
|
|
|
var ic = Engine.QueueCommand (cancellationToken, null, "DELETEACL %F %S\r\n", this, name);
|
|
|
|
await Engine.RunAsync (ic, doAsync).ConfigureAwait (false);
|
|
|
|
ProcessResponseCodes (ic, null);
|
|
|
|
if (ic.Response != ImapCommandResponse.Ok)
|
|
throw ImapCommandException.Create ("DELETEACL", ic);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Remove all access rights for the given identity.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Removes all access rights for the given identity.
|
|
/// </remarks>
|
|
/// <param name="name">The identity name.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <paramref name="name"/> is <c>null</c>.
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// The IMAP server does not support the ACL extension.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The command failed.
|
|
/// </exception>
|
|
public override void RemoveAccess (string name, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
RemoveAccessAsync (name, false, cancellationToken).GetAwaiter ().GetResult ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously remove all access rights for the given identity.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Removes all access rights for the given identity.
|
|
/// </remarks>
|
|
/// <returns>An awaitable task.</returns>
|
|
/// <param name="name">The identity name.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <paramref name="name"/> is <c>null</c>.
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// The IMAP server does not support the ACL extension.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The command failed.
|
|
/// </exception>
|
|
public override Task RemoveAccessAsync (string name, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return RemoveAccessAsync (name, true, cancellationToken);
|
|
}
|
|
|
|
async Task<string> GetMetadataAsync (MetadataTag tag, bool doAsync, CancellationToken cancellationToken)
|
|
{
|
|
CheckState (false, false);
|
|
|
|
if ((Engine.Capabilities & ImapCapabilities.Metadata) == 0)
|
|
throw new NotSupportedException ("The IMAP server does not support the METADATA extension.");
|
|
|
|
var ic = new ImapCommand (Engine, cancellationToken, null, "GETMETADATA %F %S\r\n", this, tag.Id);
|
|
ic.RegisterUntaggedHandler ("METADATA", ImapUtils.ParseMetadataAsync);
|
|
var metadata = new MetadataCollection ();
|
|
ic.UserData = metadata;
|
|
|
|
Engine.QueueCommand (ic);
|
|
|
|
await Engine.RunAsync (ic, doAsync).ConfigureAwait (false);
|
|
|
|
ProcessResponseCodes (ic, null);
|
|
|
|
if (ic.Response != ImapCommandResponse.Ok)
|
|
throw ImapCommandException.Create ("GETMETADATA", ic);
|
|
|
|
string value = null;
|
|
|
|
for (int i = 0; i < metadata.Count; i++) {
|
|
if (metadata[i].EncodedName == EncodedName && metadata[i].Tag.Id == tag.Id) {
|
|
value = metadata[i].Value;
|
|
metadata.RemoveAt (i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
Engine.ProcessMetadataChanges (metadata);
|
|
|
|
return value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the specified metadata.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Gets the specified metadata.
|
|
/// </remarks>
|
|
/// <returns>The requested metadata value.</returns>
|
|
/// <param name="tag">The metadata tag.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// The IMAP server does not support the METADATA extension.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override string GetMetadata (MetadataTag tag, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return GetMetadataAsync (tag, false, cancellationToken).GetAwaiter ().GetResult ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously get the specified metadata.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Gets the specified metadata.
|
|
/// </remarks>
|
|
/// <returns>The requested metadata value.</returns>
|
|
/// <param name="tag">The metadata tag.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// The IMAP server does not support the METADATA extension.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override Task<string> GetMetadataAsync (MetadataTag tag, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return GetMetadataAsync (tag, true, cancellationToken);
|
|
}
|
|
|
|
async Task<MetadataCollection> GetMetadataAsync (MetadataOptions options, IEnumerable<MetadataTag> tags, bool doAsync, CancellationToken cancellationToken)
|
|
{
|
|
if (options == null)
|
|
throw new ArgumentNullException (nameof (options));
|
|
|
|
if (tags == null)
|
|
throw new ArgumentNullException (nameof (tags));
|
|
|
|
CheckState (false, false);
|
|
|
|
if ((Engine.Capabilities & ImapCapabilities.Metadata) == 0)
|
|
throw new NotSupportedException ("The IMAP server does not support the METADATA extension.");
|
|
|
|
var command = new StringBuilder ("GETMETADATA %F");
|
|
var args = new List<object> ();
|
|
bool hasOptions = false;
|
|
|
|
if (options.MaxSize.HasValue || options.Depth != 0) {
|
|
command.Append (" (");
|
|
if (options.MaxSize.HasValue)
|
|
command.AppendFormat ("MAXSIZE {0} ", options.MaxSize.Value);
|
|
if (options.Depth > 0)
|
|
command.AppendFormat ("DEPTH {0} ", options.Depth == int.MaxValue ? "infinity" : "1");
|
|
command[command.Length - 1] = ')';
|
|
command.Append (' ');
|
|
hasOptions = true;
|
|
}
|
|
|
|
args.Add (this);
|
|
|
|
int startIndex = command.Length;
|
|
foreach (var tag in tags) {
|
|
command.Append (" %S");
|
|
args.Add (tag.Id);
|
|
}
|
|
|
|
if (hasOptions) {
|
|
command[startIndex] = '(';
|
|
command.Append (')');
|
|
}
|
|
|
|
command.Append ("\r\n");
|
|
|
|
if (args.Count == 1)
|
|
return new MetadataCollection ();
|
|
|
|
var ic = new ImapCommand (Engine, cancellationToken, null, command.ToString (), args.ToArray ());
|
|
ic.RegisterUntaggedHandler ("METADATA", ImapUtils.ParseMetadataAsync);
|
|
ic.UserData = new MetadataCollection ();
|
|
options.LongEntries = 0;
|
|
|
|
Engine.QueueCommand (ic);
|
|
|
|
await Engine.RunAsync (ic, doAsync).ConfigureAwait (false);
|
|
|
|
ProcessResponseCodes (ic, null);
|
|
|
|
if (ic.Response != ImapCommandResponse.Ok)
|
|
throw ImapCommandException.Create ("GETMETADATA", ic);
|
|
|
|
var metadata = (MetadataResponseCode) GetResponseCode (ic, ImapResponseCodeType.Metadata);
|
|
if (metadata != null && metadata.SubType == MetadataResponseCodeSubType.LongEntries)
|
|
options.LongEntries = metadata.Value;
|
|
|
|
return Engine.FilterMetadata ((MetadataCollection) ic.UserData, EncodedName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the specified metadata.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Gets the specified metadata.
|
|
/// </remarks>
|
|
/// <returns>The requested metadata.</returns>
|
|
/// <param name="options">The metadata options.</param>
|
|
/// <param name="tags">The metadata tags.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <para><paramref name="options"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="tags"/> is <c>null</c>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// The IMAP server does not support the METADATA extension.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override MetadataCollection GetMetadata (MetadataOptions options, IEnumerable<MetadataTag> tags, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return GetMetadataAsync (options, tags, false, cancellationToken).GetAwaiter ().GetResult ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously get the specified metadata.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Gets the specified metadata.
|
|
/// </remarks>
|
|
/// <returns>The requested metadata.</returns>
|
|
/// <param name="options">The metadata options.</param>
|
|
/// <param name="tags">The metadata tags.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <para><paramref name="options"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="tags"/> is <c>null</c>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// The IMAP server does not support the METADATA extension.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override Task<MetadataCollection> GetMetadataAsync (MetadataOptions options, IEnumerable<MetadataTag> tags, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return GetMetadataAsync (options, tags, true, cancellationToken);
|
|
}
|
|
|
|
async Task SetMetadataAsync (MetadataCollection metadata, bool doAsync, CancellationToken cancellationToken)
|
|
{
|
|
if (metadata == null)
|
|
throw new ArgumentNullException (nameof (metadata));
|
|
|
|
CheckState (false, false);
|
|
|
|
if ((Engine.Capabilities & ImapCapabilities.Metadata) == 0)
|
|
throw new NotSupportedException ("The IMAP server does not support the METADATA extension.");
|
|
|
|
if (metadata.Count == 0)
|
|
return;
|
|
|
|
var command = new StringBuilder ("SETMETADATA %F (");
|
|
var args = new List<object> ();
|
|
|
|
args.Add (this);
|
|
|
|
for (int i = 0; i < metadata.Count; i++) {
|
|
if (i > 0)
|
|
command.Append (' ');
|
|
|
|
if (metadata[i].Value != null) {
|
|
command.Append ("%S %S");
|
|
args.Add (metadata[i].Tag.Id);
|
|
args.Add (metadata[i].Value);
|
|
} else {
|
|
command.Append ("%S NIL");
|
|
args.Add (metadata[i].Tag.Id);
|
|
}
|
|
}
|
|
command.Append (")\r\n");
|
|
|
|
var ic = new ImapCommand (Engine, cancellationToken, null, command.ToString (), args.ToArray ());
|
|
|
|
Engine.QueueCommand (ic);
|
|
|
|
await Engine.RunAsync (ic, doAsync).ConfigureAwait (false);
|
|
|
|
ProcessResponseCodes (ic, null);
|
|
|
|
if (ic.Response != ImapCommandResponse.Ok)
|
|
throw ImapCommandException.Create ("SETMETADATA", ic);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the specified metadata.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Sets the specified metadata.
|
|
/// </remarks>
|
|
/// <param name="metadata">The metadata.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <paramref name="metadata"/> is <c>null</c>.
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// The IMAP server does not support the METADATA extension.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override void SetMetadata (MetadataCollection metadata, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
SetMetadataAsync (metadata, false, cancellationToken).GetAwaiter ().GetResult ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously set the specified metadata.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Sets the specified metadata.
|
|
/// </remarks>
|
|
/// <returns>An asynchronous task context.</returns>
|
|
/// <param name="metadata">The metadata.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <paramref name="metadata"/> is <c>null</c>.
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// The IMAP server does not support the METADATA extension.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override Task SetMetadataAsync (MetadataCollection metadata, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return SetMetadataAsync (metadata, true, cancellationToken);
|
|
}
|
|
|
|
class Quota
|
|
{
|
|
public uint? MessageLimit;
|
|
public uint? StorageLimit;
|
|
public uint? CurrentMessageCount;
|
|
public uint? CurrentStorageSize;
|
|
}
|
|
|
|
class QuotaContext
|
|
{
|
|
public QuotaContext ()
|
|
{
|
|
Quotas = new Dictionary<string, Quota> ();
|
|
QuotaRoots = new List<string> ();
|
|
}
|
|
|
|
public IList<string> QuotaRoots {
|
|
get; private set;
|
|
}
|
|
|
|
public IDictionary<string, Quota> Quotas {
|
|
get; private set;
|
|
}
|
|
}
|
|
|
|
static async Task UntaggedQuotaRootAsync (ImapEngine engine, ImapCommand ic, int index, bool doAsync)
|
|
{
|
|
var format = string.Format (ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "QUOTAROOT", "{0}");
|
|
var ctx = (QuotaContext) ic.UserData;
|
|
|
|
// The first token should be the mailbox name
|
|
await ReadStringTokenAsync (engine, format, doAsync, ic.CancellationToken).ConfigureAwait (false);
|
|
|
|
// ...followed by 0 or more quota roots
|
|
var token = await engine.PeekTokenAsync (doAsync, ic.CancellationToken).ConfigureAwait (false);
|
|
|
|
while (token.Type != ImapTokenType.Eoln) {
|
|
var root = await ReadStringTokenAsync (engine, format, doAsync, ic.CancellationToken).ConfigureAwait (false);
|
|
ctx.QuotaRoots.Add (root);
|
|
|
|
token = await engine.PeekTokenAsync (doAsync, ic.CancellationToken).ConfigureAwait (false);
|
|
}
|
|
}
|
|
|
|
static async Task UntaggedQuotaAsync (ImapEngine engine, ImapCommand ic, int index, bool doAsync)
|
|
{
|
|
var format = string.Format (ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "QUOTA", "{0}");
|
|
var quotaRoot = await ReadStringTokenAsync (engine, format, doAsync, ic.CancellationToken).ConfigureAwait (false);
|
|
var ctx = (QuotaContext) ic.UserData;
|
|
var quota = new Quota ();
|
|
|
|
var token = await engine.ReadTokenAsync (doAsync, ic.CancellationToken).ConfigureAwait (false);
|
|
|
|
ImapEngine.AssertToken (token, ImapTokenType.OpenParen, format, token);
|
|
|
|
while (token.Type != ImapTokenType.CloseParen) {
|
|
uint used, limit;
|
|
string resource;
|
|
|
|
token = await engine.ReadTokenAsync (doAsync, ic.CancellationToken).ConfigureAwait (false);
|
|
|
|
ImapEngine.AssertToken (token, ImapTokenType.Atom, format, token);
|
|
|
|
resource = (string) token.Value;
|
|
|
|
token = await engine.ReadTokenAsync (doAsync, ic.CancellationToken).ConfigureAwait (false);
|
|
|
|
used = ImapEngine.ParseNumber (token, false, format, token);
|
|
|
|
token = await engine.ReadTokenAsync (doAsync, ic.CancellationToken).ConfigureAwait (false);
|
|
|
|
limit = ImapEngine.ParseNumber (token, false, format, token);
|
|
|
|
switch (resource.ToUpperInvariant ()) {
|
|
case "MESSAGE":
|
|
quota.CurrentMessageCount = used;
|
|
quota.MessageLimit = limit;
|
|
break;
|
|
case "STORAGE":
|
|
quota.CurrentStorageSize = used;
|
|
quota.StorageLimit = limit;
|
|
break;
|
|
}
|
|
|
|
token = await engine.PeekTokenAsync (doAsync, ic.CancellationToken).ConfigureAwait (false);
|
|
}
|
|
|
|
// read the closing paren
|
|
await engine.ReadTokenAsync (doAsync, ic.CancellationToken).ConfigureAwait (false);
|
|
|
|
ctx.Quotas[quotaRoot] = quota;
|
|
}
|
|
|
|
async Task<FolderQuota> GetQuotaAsync (bool doAsync, CancellationToken cancellationToken)
|
|
{
|
|
CheckState (false, false);
|
|
|
|
if ((Engine.Capabilities & ImapCapabilities.Quota) == 0)
|
|
throw new NotSupportedException ("The IMAP server does not support the QUOTA extension.");
|
|
|
|
var ic = new ImapCommand (Engine, cancellationToken, null, "GETQUOTAROOT %F\r\n", this);
|
|
var ctx = new QuotaContext ();
|
|
|
|
ic.RegisterUntaggedHandler ("QUOTAROOT", UntaggedQuotaRootAsync);
|
|
ic.RegisterUntaggedHandler ("QUOTA", UntaggedQuotaAsync);
|
|
ic.UserData = ctx;
|
|
|
|
Engine.QueueCommand (ic);
|
|
|
|
await Engine.RunAsync (ic, doAsync).ConfigureAwait (false);
|
|
|
|
ProcessResponseCodes (ic, null);
|
|
|
|
if (ic.Response != ImapCommandResponse.Ok)
|
|
throw ImapCommandException.Create ("GETQUOTAROOT", ic);
|
|
|
|
for (int i = 0; i < ctx.QuotaRoots.Count; i++) {
|
|
var encodedName = ctx.QuotaRoots[i];
|
|
ImapFolder quotaRoot;
|
|
Quota quota;
|
|
|
|
if (!ctx.Quotas.TryGetValue (encodedName, out quota))
|
|
continue;
|
|
|
|
quotaRoot = await Engine.GetQuotaRootFolderAsync (encodedName, doAsync, cancellationToken).ConfigureAwait (false);
|
|
|
|
return new FolderQuota (quotaRoot) {
|
|
CurrentMessageCount = quota.CurrentMessageCount,
|
|
CurrentStorageSize = quota.CurrentStorageSize,
|
|
MessageLimit = quota.MessageLimit,
|
|
StorageLimit = quota.StorageLimit
|
|
};
|
|
}
|
|
|
|
return new FolderQuota (null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get the quota information for the folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>Gets the quota information for the folder.</para>
|
|
/// <para>To determine if a quotas are supported, check the
|
|
/// <see cref="ImapClient.SupportsQuotas"/> property.</para>
|
|
/// </remarks>
|
|
/// <returns>The folder quota.</returns>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// The IMAP server does not support the QUOTA extension.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override FolderQuota GetQuota (CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return GetQuotaAsync (false, cancellationToken).GetAwaiter ().GetResult ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously get the quota information for the folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>Gets the quota information for the folder.</para>
|
|
/// <para>To determine if a quotas are supported, check the
|
|
/// <see cref="ImapClient.SupportsQuotas"/> property.</para>
|
|
/// </remarks>
|
|
/// <returns>The folder quota.</returns>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// The IMAP server does not support the QUOTA extension.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override Task<FolderQuota> GetQuotaAsync (CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return GetQuotaAsync (true, cancellationToken);
|
|
}
|
|
|
|
async Task<FolderQuota> SetQuotaAsync (uint? messageLimit, uint? storageLimit, bool doAsync, CancellationToken cancellationToken)
|
|
{
|
|
CheckState (false, false);
|
|
|
|
if ((Engine.Capabilities & ImapCapabilities.Quota) == 0)
|
|
throw new NotSupportedException ("The IMAP server does not support the QUOTA extension.");
|
|
|
|
var command = new StringBuilder ("SETQUOTA %F (");
|
|
if (messageLimit.HasValue)
|
|
command.AppendFormat ("MESSAGE {0} ", messageLimit.Value);
|
|
if (storageLimit.HasValue)
|
|
command.AppendFormat ("STORAGE {0} ", storageLimit.Value);
|
|
command[command.Length - 1] = ')';
|
|
command.Append ("\r\n");
|
|
|
|
var ic = new ImapCommand (Engine, cancellationToken, null, command.ToString (), this);
|
|
var ctx = new QuotaContext ();
|
|
Quota quota;
|
|
|
|
ic.RegisterUntaggedHandler ("QUOTA", UntaggedQuotaAsync);
|
|
ic.UserData = ctx;
|
|
|
|
Engine.QueueCommand (ic);
|
|
|
|
await Engine.RunAsync (ic, doAsync).ConfigureAwait (false);
|
|
|
|
ProcessResponseCodes (ic, null);
|
|
|
|
if (ic.Response != ImapCommandResponse.Ok)
|
|
throw ImapCommandException.Create ("SETQUOTA", ic);
|
|
|
|
if (ctx.Quotas.TryGetValue (EncodedName, out quota)) {
|
|
return new FolderQuota (this) {
|
|
CurrentMessageCount = quota.CurrentMessageCount,
|
|
CurrentStorageSize = quota.CurrentStorageSize,
|
|
MessageLimit = quota.MessageLimit,
|
|
StorageLimit = quota.StorageLimit
|
|
};
|
|
}
|
|
|
|
return new FolderQuota (null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Set the quota limits for the folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>Sets the quota limits for the folder.</para>
|
|
/// <para>To determine if a quotas are supported, check the
|
|
/// <see cref="ImapClient.SupportsQuotas"/> property.</para>
|
|
/// </remarks>
|
|
/// <returns>The folder quota.</returns>
|
|
/// <param name="messageLimit">If not <c>null</c>, sets the maximum number of messages to allow.</param>
|
|
/// <param name="storageLimit">If not <c>null</c>, sets the maximum storage size (in kilobytes).</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// The IMAP server does not support the QUOTA extension.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override FolderQuota SetQuota (uint? messageLimit, uint? storageLimit, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return SetQuotaAsync (messageLimit, storageLimit, false, cancellationToken).GetAwaiter ().GetResult ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously set the quota limits for the folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>Sets the quota limits for the folder.</para>
|
|
/// <para>To determine if a quotas are supported, check the
|
|
/// <see cref="ImapClient.SupportsQuotas"/> property.</para>
|
|
/// </remarks>
|
|
/// <returns>The folder quota.</returns>
|
|
/// <param name="messageLimit">If not <c>null</c>, sets the maximum number of messages to allow.</param>
|
|
/// <param name="storageLimit">If not <c>null</c>, sets the maximum storage size (in kilobytes).</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// The IMAP server does not support the QUOTA extension.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override Task<FolderQuota> SetQuotaAsync (uint? messageLimit, uint? storageLimit, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return SetQuotaAsync (messageLimit, storageLimit, true, cancellationToken);
|
|
}
|
|
|
|
async Task ExpungeAsync (bool doAsync, CancellationToken cancellationToken)
|
|
{
|
|
CheckState (true, true);
|
|
|
|
var ic = Engine.QueueCommand (cancellationToken, this, "EXPUNGE\r\n");
|
|
|
|
await Engine.RunAsync (ic, doAsync).ConfigureAwait (false);
|
|
|
|
ProcessResponseCodes (ic, null);
|
|
|
|
if (ic.Response != ImapCommandResponse.Ok)
|
|
throw ImapCommandException.Create ("EXPUNGE", ic);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Expunge the folder, permanently removing all messages marked for deletion.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>The <c>EXPUNGE</c> command permanently removes all messages in the folder
|
|
/// that have the <see cref="MessageFlags.Deleted"/> flag set.</para>
|
|
/// <para>For more information about the <c>EXPUNGE</c> command, see
|
|
/// <a href="https://tools.ietf.org/html/rfc3501#section-6.4.3">rfc3501</a>.</para>
|
|
/// <note type="note">Normally, a <see cref="MailFolder.MessageExpunged"/> event will be emitted
|
|
/// for each message that is expunged. However, if the IMAP server supports the QRESYNC extension
|
|
/// and it has been enabled via the <see cref="ImapClient.EnableQuickResync(CancellationToken)"/>
|
|
/// method, then the <see cref="MailFolder.MessagesVanished"/> event will be emitted rather than
|
|
/// the <see cref="MailFolder.MessageExpunged"/> event.</note>
|
|
/// </remarks>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotOpenException">
|
|
/// The <see cref="ImapFolder"/> is not currently open in read-write mode.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override void Expunge (CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
ExpungeAsync (false, cancellationToken).GetAwaiter ().GetResult ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously expunge the folder, permanently removing all messages marked for deletion.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>The <c>EXPUNGE</c> command permanently removes all messages in the folder
|
|
/// that have the <see cref="MessageFlags.Deleted"/> flag set.</para>
|
|
/// <para>For more information about the <c>EXPUNGE</c> command, see
|
|
/// <a href="https://tools.ietf.org/html/rfc3501#section-6.4.3">rfc3501</a>.</para>
|
|
/// <note type="note">Normally, a <see cref="MailFolder.MessageExpunged"/> event will be emitted
|
|
/// for each message that is expunged. However, if the IMAP server supports the QRESYNC extension
|
|
/// and it has been enabled via the <see cref="ImapClient.EnableQuickResync(CancellationToken)"/>
|
|
/// method, then the <see cref="MailFolder.MessagesVanished"/> event will be emitted rather than
|
|
/// the <see cref="MailFolder.MessageExpunged"/> event.</note>
|
|
/// </remarks>
|
|
/// <returns>An asynchronous task context.</returns>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotOpenException">
|
|
/// The <see cref="ImapFolder"/> is not currently open in read-write mode.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override Task ExpungeAsync (CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return ExpungeAsync (true, cancellationToken);
|
|
}
|
|
|
|
async Task ExpungeAsync (IList<UniqueId> uids, bool doAsync, CancellationToken cancellationToken)
|
|
{
|
|
if (uids == null)
|
|
throw new ArgumentNullException (nameof (uids));
|
|
|
|
CheckState (true, true);
|
|
|
|
if (uids.Count == 0)
|
|
return;
|
|
|
|
if ((Engine.Capabilities & ImapCapabilities.UidPlus) == 0) {
|
|
// get the list of messages marked for deletion that should not be expunged
|
|
var query = SearchQuery.Deleted.And (SearchQuery.Not (SearchQuery.Uids (uids)));
|
|
var unmark = await SearchAsync (query, doAsync, false, cancellationToken).ConfigureAwait (false);
|
|
|
|
if (unmark.Count > 0) {
|
|
// clear the \Deleted flag on all messages except the ones that are to be expunged
|
|
await ModifyFlagsAsync (unmark, null, MessageFlags.Deleted, null, "-FLAGS.SILENT", doAsync, cancellationToken).ConfigureAwait (false);
|
|
}
|
|
|
|
// expunge the folder
|
|
await ExpungeAsync (doAsync, cancellationToken).ConfigureAwait (false);
|
|
|
|
if (unmark.Count > 0) {
|
|
// restore the \Deleted flags
|
|
await ModifyFlagsAsync (unmark, null, MessageFlags.Deleted, null, "+FLAGS.SILENT", doAsync, cancellationToken).ConfigureAwait (false);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
foreach (var ic in Engine.QueueCommands (cancellationToken, this, "UID EXPUNGE %s\r\n", uids)) {
|
|
await Engine.RunAsync (ic, doAsync).ConfigureAwait (false);
|
|
|
|
ProcessResponseCodes (ic, null);
|
|
|
|
if (ic.Response != ImapCommandResponse.Ok)
|
|
throw ImapCommandException.Create ("EXPUNGE", ic);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Expunge the specified uids, permanently removing them from the folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>Expunges the specified uids, permanently removing them from the folder.</para>
|
|
/// <para>If the IMAP server supports the UIDPLUS extension (check the
|
|
/// <see cref="ImapClient.Capabilities"/> for the <see cref="ImapCapabilities.UidPlus"/>
|
|
/// flag), then this operation is atomic. Otherwise, MailKit implements this operation
|
|
/// by first searching for the full list of message uids in the folder that are marked for
|
|
/// deletion, unmarking the set of message uids that are not within the specified list of
|
|
/// uids to be be expunged, expunging the folder (thus expunging the requested uids), and
|
|
/// finally restoring the deleted flag on the collection of message uids that were originally
|
|
/// marked for deletion that were not included in the list of uids provided. For this reason,
|
|
/// it is advisable for clients that wish to maintain state to implement this themselves when
|
|
/// the IMAP server does not support the UIDPLUS extension.</para>
|
|
/// <para>For more information about the <c>UID EXPUNGE</c> command, see
|
|
/// <a href="https://tools.ietf.org/html/rfc4315#section-2.1">rfc4315</a>.</para>
|
|
/// <note type="note">Normally, a <see cref="MailFolder.MessageExpunged"/> event will be emitted
|
|
/// for each message that is expunged. However, if the IMAP server supports the QRESYNC extension
|
|
/// and it has been enabled via the <see cref="ImapClient.EnableQuickResync(CancellationToken)"/>
|
|
/// method, then the <see cref="MailFolder.MessagesVanished"/> event will be emitted rather than
|
|
/// the <see cref="MailFolder.MessageExpunged"/> event.</note>
|
|
/// </remarks>
|
|
/// <param name="uids">The message uids.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <paramref name="uids"/> is <c>null</c>.
|
|
/// </exception>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// One or more of the <paramref name="uids"/> is invalid.
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotOpenException">
|
|
/// The <see cref="ImapFolder"/> is not currently open in read-write mode.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override void Expunge (IList<UniqueId> uids, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
ExpungeAsync (uids, false, cancellationToken).GetAwaiter ().GetResult ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously expunge the specified uids, permanently removing them from the folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>Expunges the specified uids, permanently removing them from the folder.</para>
|
|
/// <para>If the IMAP server supports the UIDPLUS extension (check the
|
|
/// <see cref="ImapClient.Capabilities"/> for the <see cref="ImapCapabilities.UidPlus"/>
|
|
/// flag), then this operation is atomic. Otherwise, MailKit implements this operation
|
|
/// by first searching for the full list of message uids in the folder that are marked for
|
|
/// deletion, unmarking the set of message uids that are not within the specified list of
|
|
/// uids to be be expunged, expunging the folder (thus expunging the requested uids), and
|
|
/// finally restoring the deleted flag on the collection of message uids that were originally
|
|
/// marked for deletion that were not included in the list of uids provided. For this reason,
|
|
/// it is advisable for clients that wish to maintain state to implement this themselves when
|
|
/// the IMAP server does not support the UIDPLUS extension.</para>
|
|
/// <para>For more information about the <c>UID EXPUNGE</c> command, see
|
|
/// <a href="https://tools.ietf.org/html/rfc4315#section-2.1">rfc4315</a>.</para>
|
|
/// <note type="note">Normally, a <see cref="MailFolder.MessageExpunged"/> event will be emitted
|
|
/// for each message that is expunged. However, if the IMAP server supports the QRESYNC extension
|
|
/// and it has been enabled via the <see cref="ImapClient.EnableQuickResync(CancellationToken)"/>
|
|
/// method, then the <see cref="MailFolder.MessagesVanished"/> event will be emitted rather than
|
|
/// the <see cref="MailFolder.MessageExpunged"/> event.</note>
|
|
/// </remarks>
|
|
/// <returns>An asynchronous task context.</returns>
|
|
/// <param name="uids">The message uids.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <paramref name="uids"/> is <c>null</c>.
|
|
/// </exception>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// One or more of the <paramref name="uids"/> is invalid.
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotOpenException">
|
|
/// The <see cref="ImapFolder"/> is not currently open in read-write mode.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override Task ExpungeAsync (IList<UniqueId> uids, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return ExpungeAsync (uids, true, cancellationToken);
|
|
}
|
|
|
|
ImapCommand QueueAppend (FormatOptions options, MimeMessage message, MessageFlags flags, DateTimeOffset? date, IList<Annotation> annotations, CancellationToken cancellationToken, ITransferProgress progress)
|
|
{
|
|
var builder = new StringBuilder ("APPEND %F ");
|
|
var list = new List<object> ();
|
|
|
|
list.Add (this);
|
|
|
|
if ((flags & SettableFlags) != 0)
|
|
builder.AppendFormat ("{0} ", ImapUtils.FormatFlagsList (flags, 0));
|
|
|
|
if (date.HasValue)
|
|
builder.AppendFormat ("\"{0}\" ", ImapUtils.FormatInternalDate (date.Value));
|
|
|
|
if (annotations != null && annotations.Count > 0) {
|
|
ImapUtils.FormatAnnotations (builder, annotations, list, false);
|
|
|
|
if (builder[builder.Length - 1] != ' ')
|
|
builder.Append (' ');
|
|
}
|
|
|
|
builder.Append ("%L\r\n");
|
|
list.Add (message);
|
|
|
|
var command = builder.ToString ();
|
|
var args = list.ToArray ();
|
|
|
|
var ic = new ImapCommand (Engine, cancellationToken, null, options, command, args);
|
|
ic.Progress = progress;
|
|
|
|
Engine.QueueCommand (ic);
|
|
|
|
return ic;
|
|
}
|
|
|
|
async Task<UniqueId?> AppendAsync (FormatOptions options, MimeMessage message, MessageFlags flags, DateTimeOffset? date, IList<Annotation> annotations, bool doAsync, CancellationToken cancellationToken, ITransferProgress progress)
|
|
{
|
|
if (options == null)
|
|
throw new ArgumentNullException (nameof (options));
|
|
|
|
if (message == null)
|
|
throw new ArgumentNullException (nameof (message));
|
|
|
|
CheckState (false, false);
|
|
|
|
if (options.International && (Engine.Capabilities & ImapCapabilities.UTF8Accept) == 0)
|
|
throw new NotSupportedException ("The IMAP server does not support the UTF8 extension.");
|
|
|
|
var format = options.Clone ();
|
|
format.NewLineFormat = NewLineFormat.Dos;
|
|
format.EnsureNewLine = true;
|
|
|
|
if ((Engine.Capabilities & ImapCapabilities.UTF8Only) == ImapCapabilities.UTF8Only)
|
|
format.International = true;
|
|
|
|
if (format.International && !Engine.UTF8Enabled)
|
|
throw new InvalidOperationException ("The UTF8 extension has not been enabled.");
|
|
|
|
var ic = QueueAppend (format, message, flags, date, annotations, cancellationToken, progress);
|
|
|
|
await Engine.RunAsync (ic, doAsync).ConfigureAwait (false);
|
|
|
|
ProcessResponseCodes (ic, this);
|
|
|
|
if (ic.Response != ImapCommandResponse.Ok)
|
|
throw ImapCommandException.Create ("APPEND", ic);
|
|
|
|
var append = (AppendUidResponseCode) GetResponseCode (ic, ImapResponseCodeType.AppendUid);
|
|
|
|
if (append != null)
|
|
return append.UidSet[0];
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Append the specified message to the folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Appends the specified message to the folder and returns the UniqueId assigned to the message.
|
|
/// </remarks>
|
|
/// <returns>The UID of the appended message, if available; otherwise, <c>null</c>.</returns>
|
|
/// <param name="options">The formatting options.</param>
|
|
/// <param name="message">The message.</param>
|
|
/// <param name="flags">The message flags.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <param name="progress">The progress reporting mechanism.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <para><paramref name="options"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="message"/> is <c>null</c>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.InvalidOperationException">
|
|
/// Internationalized formatting was requested but has not been enabled.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotFoundException">
|
|
/// The <see cref="ImapFolder"/> does not exist.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// Internationalized formatting was requested but is not supported by the server.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override UniqueId? Append (FormatOptions options, MimeMessage message, MessageFlags flags = MessageFlags.None, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null)
|
|
{
|
|
return AppendAsync (options, message, flags, null, null, false, cancellationToken, progress).GetAwaiter ().GetResult ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously append the specified message to the folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Appends the specified message to the folder and returns the UniqueId assigned to the message.
|
|
/// </remarks>
|
|
/// <returns>The UID of the appended message, if available; otherwise, <c>null</c>.</returns>
|
|
/// <param name="options">The formatting options.</param>
|
|
/// <param name="message">The message.</param>
|
|
/// <param name="flags">The message flags.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <param name="progress">The progress reporting mechanism.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <para><paramref name="options"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="message"/> is <c>null</c>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.InvalidOperationException">
|
|
/// Internationalized formatting was requested but has not been enabled.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotFoundException">
|
|
/// The <see cref="ImapFolder"/> does not exist.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// Internationalized formatting was requested but is not supported by the server.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override Task<UniqueId?> AppendAsync (FormatOptions options, MimeMessage message, MessageFlags flags = MessageFlags.None, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null)
|
|
{
|
|
return AppendAsync (options, message, flags, null, null, true, cancellationToken, progress);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Append the specified message to the folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Appends the specified message to the folder and returns the UniqueId assigned to the message.
|
|
/// </remarks>
|
|
/// <returns>The UID of the appended message, if available; otherwise, <c>null</c>.</returns>
|
|
/// <param name="options">The formatting options.</param>
|
|
/// <param name="message">The message.</param>
|
|
/// <param name="flags">The message flags.</param>
|
|
/// <param name="date">The received date of the message.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <param name="progress">The progress reporting mechanism.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <para><paramref name="options"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="message"/> is <c>null</c>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.InvalidOperationException">
|
|
/// Internationalized formatting was requested but has not been enabled.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotFoundException">
|
|
/// The <see cref="ImapFolder"/> does not exist.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// Internationalized formatting was requested but is not supported by the server.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override UniqueId? Append (FormatOptions options, MimeMessage message, MessageFlags flags, DateTimeOffset date, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null)
|
|
{
|
|
return AppendAsync (options, message, flags, date, null, false, cancellationToken, progress).GetAwaiter ().GetResult ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously append the specified message to the folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Appends the specified message to the folder and returns the UniqueId assigned to the message.
|
|
/// </remarks>
|
|
/// <returns>The UID of the appended message, if available; otherwise, <c>null</c>.</returns>
|
|
/// <param name="options">The formatting options.</param>
|
|
/// <param name="message">The message.</param>
|
|
/// <param name="flags">The message flags.</param>
|
|
/// <param name="date">The received date of the message.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <param name="progress">The progress reporting mechanism.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <para><paramref name="options"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="message"/> is <c>null</c>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.InvalidOperationException">
|
|
/// Internationalized formatting was requested but has not been enabled.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotFoundException">
|
|
/// The <see cref="ImapFolder"/> does not exist.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// Internationalized formatting was requested but is not supported by the server.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override Task<UniqueId?> AppendAsync (FormatOptions options, MimeMessage message, MessageFlags flags, DateTimeOffset date, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null)
|
|
{
|
|
return AppendAsync (options, message, flags, date, null, true, cancellationToken, progress);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Append the specified message to the folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Appends the specified message to the folder and returns the UniqueId assigned to the message.
|
|
/// </remarks>
|
|
/// <returns>The UID of the appended message, if available; otherwise, <c>null</c>.</returns>
|
|
/// <param name="options">The formatting options.</param>
|
|
/// <param name="message">The message.</param>
|
|
/// <param name="flags">The message flags.</param>
|
|
/// <param name="date">The received date of the message.</param>
|
|
/// <param name="annotations">The message annotations.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <param name="progress">The progress reporting mechanism.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <para><paramref name="options"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="message"/> is <c>null</c>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.InvalidOperationException">
|
|
/// Internationalized formatting was requested but has not been enabled.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotFoundException">
|
|
/// The <see cref="ImapFolder"/> does not exist.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// Internationalized formatting was requested but is not supported by the server.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override UniqueId? Append (FormatOptions options, MimeMessage message, MessageFlags flags, DateTimeOffset? date, IList<Annotation> annotations, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null)
|
|
{
|
|
return AppendAsync (options, message, flags, date, annotations, false, cancellationToken, progress).GetAwaiter ().GetResult ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously append the specified message to the folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Appends the specified message to the folder and returns the UniqueId assigned to the message.
|
|
/// </remarks>
|
|
/// <returns>The UID of the appended message, if available; otherwise, <c>null</c>.</returns>
|
|
/// <param name="options">The formatting options.</param>
|
|
/// <param name="message">The message.</param>
|
|
/// <param name="flags">The message flags.</param>
|
|
/// <param name="date">The received date of the message.</param>
|
|
/// <param name="annotations">The message annotations.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <param name="progress">The progress reporting mechanism.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <para><paramref name="options"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="message"/> is <c>null</c>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.InvalidOperationException">
|
|
/// Internationalized formatting was requested but has not been enabled.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotFoundException">
|
|
/// The <see cref="ImapFolder"/> does not exist.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// Internationalized formatting was requested but is not supported by the server.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override Task<UniqueId?> AppendAsync (FormatOptions options, MimeMessage message, MessageFlags flags, DateTimeOffset? date, IList<Annotation> annotations, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null)
|
|
{
|
|
return AppendAsync (options, message, flags, date, annotations, true, cancellationToken, progress);
|
|
}
|
|
|
|
ImapCommand QueueMultiAppend (FormatOptions options, IList<MimeMessage> messages, IList<MessageFlags> flags, IList<DateTimeOffset> dates, IList<IList<Annotation>> annotations, CancellationToken cancellationToken, ITransferProgress progress)
|
|
{
|
|
var builder = new StringBuilder ("APPEND %F");
|
|
var list = new List<object> ();
|
|
|
|
list.Add (this);
|
|
|
|
for (int i = 0; i < messages.Count; i++) {
|
|
builder.Append (' ');
|
|
|
|
if ((flags[i] & SettableFlags) != 0)
|
|
builder.AppendFormat ("{0} ", ImapUtils.FormatFlagsList (flags[i], 0));
|
|
|
|
if (dates != null)
|
|
builder.AppendFormat ("\"{0}\" ", ImapUtils.FormatInternalDate (dates[i]));
|
|
|
|
//if (annotations != null && annotations[i] != null && annotations[i].Count > 0) {
|
|
// ImapUtils.FormatAnnotations (builder, annotations[i], list, false);
|
|
|
|
// if (builder[builder.Length - 1] != ' ')
|
|
// builder.Append (' ');
|
|
//}
|
|
|
|
builder.Append ("%L");
|
|
list.Add (messages[i]);
|
|
}
|
|
|
|
builder.Append ("\r\n");
|
|
|
|
var command = builder.ToString ();
|
|
var args = list.ToArray ();
|
|
|
|
var ic = new ImapCommand (Engine, cancellationToken, null, options, command, args);
|
|
ic.Progress = progress;
|
|
|
|
Engine.QueueCommand (ic);
|
|
|
|
return ic;
|
|
}
|
|
|
|
async Task<IList<UniqueId>> AppendAsync (FormatOptions options, IList<MimeMessage> messages, IList<MessageFlags> flags, bool doAsync, CancellationToken cancellationToken, ITransferProgress progress)
|
|
{
|
|
if (options == null)
|
|
throw new ArgumentNullException (nameof (options));
|
|
|
|
if (messages == null)
|
|
throw new ArgumentNullException (nameof (messages));
|
|
|
|
for (int i = 0; i < messages.Count; i++) {
|
|
if (messages[i] == null)
|
|
throw new ArgumentException ("One or more of the messages is null.");
|
|
}
|
|
|
|
if (flags == null)
|
|
throw new ArgumentNullException (nameof (flags));
|
|
|
|
if (messages.Count != flags.Count)
|
|
throw new ArgumentException ("The number of messages and the number of flags must be equal.");
|
|
|
|
CheckState (false, false);
|
|
|
|
if (options.International && (Engine.Capabilities & ImapCapabilities.UTF8Accept) == 0)
|
|
throw new NotSupportedException ("The IMAP server does not support the UTF8 extension.");
|
|
|
|
var format = options.Clone ();
|
|
format.NewLineFormat = NewLineFormat.Dos;
|
|
format.EnsureNewLine = true;
|
|
|
|
if ((Engine.Capabilities & ImapCapabilities.UTF8Only) == ImapCapabilities.UTF8Only)
|
|
format.International = true;
|
|
|
|
if (format.International && !Engine.UTF8Enabled)
|
|
throw new InvalidOperationException ("The UTF8 extension has not been enabled.");
|
|
|
|
if (messages.Count == 0)
|
|
return new UniqueId[0];
|
|
|
|
if ((Engine.Capabilities & ImapCapabilities.MultiAppend) != 0) {
|
|
var ic = QueueMultiAppend (format, messages, flags, null, null, cancellationToken, progress);
|
|
|
|
await Engine.RunAsync (ic, doAsync).ConfigureAwait (false);
|
|
|
|
ProcessResponseCodes (ic, this);
|
|
|
|
if (ic.Response != ImapCommandResponse.Ok)
|
|
throw ImapCommandException.Create ("APPEND", ic);
|
|
|
|
var append = (AppendUidResponseCode) GetResponseCode (ic, ImapResponseCodeType.AppendUid);
|
|
|
|
if (append != null)
|
|
return append.UidSet;
|
|
|
|
return new UniqueId[0];
|
|
}
|
|
|
|
// FIXME: use an aggregate progress reporter
|
|
var uids = new List<UniqueId> ();
|
|
|
|
for (int i = 0; i < messages.Count; i++) {
|
|
var uid = await AppendAsync (format, messages[i], flags[i], null, null, doAsync, cancellationToken, progress).ConfigureAwait (false);
|
|
if (uids != null && uid.HasValue)
|
|
uids.Add (uid.Value);
|
|
else
|
|
uids = null;
|
|
}
|
|
|
|
if (uids == null)
|
|
return new UniqueId[0];
|
|
|
|
return uids;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Append the specified messages to the folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Appends the specified messages to the folder and returns the UniqueIds assigned to the messages.
|
|
/// </remarks>
|
|
/// <returns>The UIDs of the appended messages, if available; otherwise an empty array.</returns>
|
|
/// <param name="options">The formatting options.</param>
|
|
/// <param name="messages">The list of messages to append to the folder.</param>
|
|
/// <param name="flags">The message flags to use for each message.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <param name="progress">The progress reporting mechanism.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <para><paramref name="options"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="messages"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="flags"/> is <c>null</c>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// <para>One or more of the <paramref name="messages"/> is null.</para>
|
|
/// <para>-or-</para>
|
|
/// <para>The number of messages does not match the number of flags.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.InvalidOperationException">
|
|
/// Internationalized formatting was requested but has not been enabled.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotFoundException">
|
|
/// The <see cref="ImapFolder"/> does not exist.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// Internationalized formatting was requested but is not supported by the server.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override IList<UniqueId> Append (FormatOptions options, IList<MimeMessage> messages, IList<MessageFlags> flags, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null)
|
|
{
|
|
return AppendAsync (options, messages, flags, false, cancellationToken, progress).GetAwaiter ().GetResult ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously append the specified messages to the folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Appends the specified messages to the folder and returns the UniqueIds assigned to the messages.
|
|
/// </remarks>
|
|
/// <returns>The UIDs of the appended messages, if available; otherwise an empty array.</returns>
|
|
/// <param name="options">The formatting options.</param>
|
|
/// <param name="messages">The list of messages to append to the folder.</param>
|
|
/// <param name="flags">The message flags to use for each message.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <param name="progress">The progress reporting mechanism.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <para><paramref name="options"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="messages"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="flags"/> is <c>null</c>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// <para>One or more of the <paramref name="messages"/> is null.</para>
|
|
/// <para>-or-</para>
|
|
/// <para>The number of messages does not match the number of flags.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.InvalidOperationException">
|
|
/// Internationalized formatting was requested but has not been enabled.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotFoundException">
|
|
/// The <see cref="ImapFolder"/> does not exist.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// Internationalized formatting was requested but is not supported by the server.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override Task<IList<UniqueId>> AppendAsync (FormatOptions options, IList<MimeMessage> messages, IList<MessageFlags> flags, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null)
|
|
{
|
|
return AppendAsync (options, messages, flags, true, cancellationToken, progress);
|
|
}
|
|
|
|
async Task<IList<UniqueId>> AppendAsync (FormatOptions options, IList<MimeMessage> messages, IList<MessageFlags> flags, IList<DateTimeOffset> dates, bool doAsync, CancellationToken cancellationToken, ITransferProgress progress)
|
|
{
|
|
if (options == null)
|
|
throw new ArgumentNullException (nameof (options));
|
|
|
|
if (messages == null)
|
|
throw new ArgumentNullException (nameof (messages));
|
|
|
|
for (int i = 0; i < messages.Count; i++) {
|
|
if (messages[i] == null)
|
|
throw new ArgumentException ("One or more of the messages is null.");
|
|
}
|
|
|
|
if (flags == null)
|
|
throw new ArgumentNullException (nameof (flags));
|
|
|
|
if (dates == null)
|
|
throw new ArgumentNullException (nameof (dates));
|
|
|
|
if (messages.Count != flags.Count || messages.Count != dates.Count)
|
|
throw new ArgumentException ("The number of messages, the number of flags, and the number of dates must be equal.");
|
|
|
|
CheckState (false, false);
|
|
|
|
if (options.International && (Engine.Capabilities & ImapCapabilities.UTF8Accept) == 0)
|
|
throw new NotSupportedException ("The IMAP server does not support the UTF8 extension.");
|
|
|
|
var format = options.Clone ();
|
|
format.NewLineFormat = NewLineFormat.Dos;
|
|
format.EnsureNewLine = true;
|
|
|
|
if ((Engine.Capabilities & ImapCapabilities.UTF8Only) == ImapCapabilities.UTF8Only)
|
|
format.International = true;
|
|
|
|
if (format.International && !Engine.UTF8Enabled)
|
|
throw new InvalidOperationException ("The UTF8 extension has not been enabled.");
|
|
|
|
if (messages.Count == 0)
|
|
return new UniqueId[0];
|
|
|
|
if ((Engine.Capabilities & ImapCapabilities.MultiAppend) != 0) {
|
|
var ic = QueueMultiAppend (format, messages, flags, dates, null, cancellationToken, progress);
|
|
|
|
await Engine.RunAsync (ic, doAsync).ConfigureAwait (false);
|
|
|
|
ProcessResponseCodes (ic, null);
|
|
|
|
if (ic.Response != ImapCommandResponse.Ok)
|
|
throw ImapCommandException.Create ("APPEND", ic);
|
|
|
|
var append = (AppendUidResponseCode) GetResponseCode (ic, ImapResponseCodeType.AppendUid);
|
|
|
|
if (append != null)
|
|
return append.UidSet;
|
|
|
|
return new UniqueId[0];
|
|
}
|
|
|
|
// FIXME: use an aggregate progress reporter
|
|
var uids = new List<UniqueId> ();
|
|
|
|
for (int i = 0; i < messages.Count; i++) {
|
|
var uid = await AppendAsync (format, messages[i], flags[i], dates[i], null, doAsync, cancellationToken, progress).ConfigureAwait (false);
|
|
if (uids != null && uid.HasValue)
|
|
uids.Add (uid.Value);
|
|
else
|
|
uids = null;
|
|
}
|
|
|
|
if (uids == null)
|
|
return new UniqueId[0];
|
|
|
|
return uids;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Append the specified messages to the folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Appends the specified messages to the folder and returns the UniqueIds assigned to the messages.
|
|
/// </remarks>
|
|
/// <returns>The UIDs of the appended messages, if available; otherwise an empty array.</returns>
|
|
/// <param name="options">The formatting options.</param>
|
|
/// <param name="messages">The list of messages to append to the folder.</param>
|
|
/// <param name="flags">The message flags to use for each of the messages.</param>
|
|
/// <param name="dates">The received dates to use for each of the messages.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <param name="progress">The progress reporting mechanism.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <para><paramref name="options"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="messages"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="flags"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="dates"/> is <c>null</c>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// <para>One or more of the <paramref name="messages"/> is null.</para>
|
|
/// <para>-or-</para>
|
|
/// <para>The number of messages, flags, and dates do not match.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.InvalidOperationException">
|
|
/// Internationalized formatting was requested but has not been enabled.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotFoundException">
|
|
/// The <see cref="ImapFolder"/> does not exist.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// Internationalized formatting was requested but is not supported by the server.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override IList<UniqueId> Append (FormatOptions options, IList<MimeMessage> messages, IList<MessageFlags> flags, IList<DateTimeOffset> dates, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null)
|
|
{
|
|
return AppendAsync (options, messages, flags, dates, false, cancellationToken, progress).GetAwaiter ().GetResult ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously append the specified messages to the folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Appends the specified messages to the folder and returns the UniqueIds assigned to the messages.
|
|
/// </remarks>
|
|
/// <returns>The UIDs of the appended messages, if available; otherwise an empty array.</returns>
|
|
/// <param name="options">The formatting options.</param>
|
|
/// <param name="messages">The list of messages to append to the folder.</param>
|
|
/// <param name="flags">The message flags to use for each of the messages.</param>
|
|
/// <param name="dates">The received dates to use for each of the messages.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <param name="progress">The progress reporting mechanism.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <para><paramref name="options"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="messages"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="flags"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="dates"/> is <c>null</c>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// <para>One or more of the <paramref name="messages"/> is null.</para>
|
|
/// <para>-or-</para>
|
|
/// <para>The number of messages, flags, and dates do not match.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.InvalidOperationException">
|
|
/// Internationalized formatting was requested but has not been enabled.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotFoundException">
|
|
/// The <see cref="ImapFolder"/> does not exist.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// Internationalized formatting was requested but is not supported by the server.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override Task<IList<UniqueId>> AppendAsync (FormatOptions options, IList<MimeMessage> messages, IList<MessageFlags> flags, IList<DateTimeOffset> dates, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null)
|
|
{
|
|
return AppendAsync (options, messages, flags, dates, true, cancellationToken, progress);
|
|
}
|
|
|
|
ImapCommand QueueReplace (FormatOptions options, UniqueId uid, MimeMessage message, MessageFlags flags, DateTimeOffset? date, IList<Annotation> annotations, CancellationToken cancellationToken, ITransferProgress progress)
|
|
{
|
|
var builder = new StringBuilder ($"UID REPLACE {uid} %F ");
|
|
var list = new List<object> ();
|
|
|
|
list.Add (this);
|
|
|
|
if ((flags & SettableFlags) != 0)
|
|
builder.AppendFormat ("{0} ", ImapUtils.FormatFlagsList (flags, 0));
|
|
|
|
if (date.HasValue)
|
|
builder.AppendFormat ("\"{0}\" ", ImapUtils.FormatInternalDate (date.Value));
|
|
|
|
//if (annotations != null && annotations.Count > 0) {
|
|
// ImapUtils.FormatAnnotations (builder, annotations, list, false);
|
|
//
|
|
// if (builder[builder.Length - 1] != ' ')
|
|
// builder.Append (' ');
|
|
//}
|
|
|
|
builder.Append ("%L\r\n");
|
|
list.Add (message);
|
|
|
|
var command = builder.ToString ();
|
|
var args = list.ToArray ();
|
|
|
|
var ic = new ImapCommand (Engine, cancellationToken, null, options, command, args);
|
|
ic.Progress = progress;
|
|
|
|
Engine.QueueCommand (ic);
|
|
|
|
return ic;
|
|
}
|
|
|
|
async Task<UniqueId?> ReplaceAsync (FormatOptions options, UniqueId uid, MimeMessage message, MessageFlags flags, DateTimeOffset? date, IList<Annotation> annotations, bool doAsync, CancellationToken cancellationToken, ITransferProgress progress)
|
|
{
|
|
if (options == null)
|
|
throw new ArgumentNullException (nameof (options));
|
|
|
|
if (!uid.IsValid)
|
|
throw new ArgumentException ("The uid is invalid.", nameof (uid));
|
|
|
|
if (message == null)
|
|
throw new ArgumentNullException (nameof (message));
|
|
|
|
CheckState (true, true);
|
|
|
|
if ((Engine.Capabilities & ImapCapabilities.Replace) == 0) {
|
|
var appended = await AppendAsync (options, message, flags, date, annotations, doAsync, cancellationToken, progress).ConfigureAwait (false);
|
|
await ModifyFlagsAsync (new[] { uid }, null, MessageFlags.Deleted, null, "+FLAGS.SILENT", doAsync, cancellationToken).ConfigureAwait (false);
|
|
if ((Engine.Capabilities & ImapCapabilities.UidPlus) != 0)
|
|
await ExpungeAsync (new[] { uid }, doAsync, cancellationToken).ConfigureAwait (false);
|
|
return appended;
|
|
}
|
|
|
|
if (options.International && (Engine.Capabilities & ImapCapabilities.UTF8Accept) == 0)
|
|
throw new NotSupportedException ("The IMAP server does not support the UTF8 extension.");
|
|
|
|
var format = options.Clone ();
|
|
format.NewLineFormat = NewLineFormat.Dos;
|
|
format.EnsureNewLine = true;
|
|
|
|
if ((Engine.Capabilities & ImapCapabilities.UTF8Only) == ImapCapabilities.UTF8Only)
|
|
format.International = true;
|
|
|
|
if (format.International && !Engine.UTF8Enabled)
|
|
throw new InvalidOperationException ("The UTF8 extension has not been enabled.");
|
|
|
|
var ic = QueueReplace (format, uid, message, flags, date, annotations, cancellationToken, progress);
|
|
|
|
await Engine.RunAsync (ic, doAsync).ConfigureAwait (false);
|
|
|
|
ProcessResponseCodes (ic, this);
|
|
|
|
if (ic.Response != ImapCommandResponse.Ok)
|
|
throw ImapCommandException.Create ("REPLACE", ic);
|
|
|
|
var append = (AppendUidResponseCode) GetResponseCode (ic, ImapResponseCodeType.AppendUid);
|
|
|
|
if (append != null)
|
|
return append.UidSet[0];
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Replace a message in the folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Replaces the specified message in the folder and returns the UniqueId assigned to the new message.
|
|
/// </remarks>
|
|
/// <returns>The UID of the new message, if available; otherwise, <c>null</c>.</returns>
|
|
/// <param name="options">The formatting options.</param>
|
|
/// <param name="uid">The UID of the message to be replaced.</param>
|
|
/// <param name="message">The message.</param>
|
|
/// <param name="flags">The message flags.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <param name="progress">The progress reporting mechanism.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <para><paramref name="options"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="message"/> is <c>null</c>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// <paramref name="uid"/> is invalid.
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.InvalidOperationException">
|
|
/// Internationalized formatting was requested but has not been enabled.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotFoundException">
|
|
/// The <see cref="ImapFolder"/> does not exist.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotOpenException">
|
|
/// The <see cref="ImapFolder"/> is not currently open in read-write mode.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// <para>Internationalized formatting was requested but is not supported by the server.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override UniqueId? Replace (FormatOptions options, UniqueId uid, MimeMessage message, MessageFlags flags = MessageFlags.None, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null)
|
|
{
|
|
return ReplaceAsync (options, uid, message, flags, null, null, false, cancellationToken, progress).GetAwaiter ().GetResult ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously replace a message in the folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Replaces the specified message in the folder and returns the UniqueId assigned to the new message.
|
|
/// </remarks>
|
|
/// <returns>The UID of the new message, if available; otherwise, <c>null</c>.</returns>
|
|
/// <param name="options">The formatting options.</param>
|
|
/// <param name="uid">The UID of the message to be replaced.</param>
|
|
/// <param name="message">The message.</param>
|
|
/// <param name="flags">The message flags.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <param name="progress">The progress reporting mechanism.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <para><paramref name="options"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="message"/> is <c>null</c>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// <paramref name="uid"/> is invalid.
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.InvalidOperationException">
|
|
/// Internationalized formatting was requested but has not been enabled.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotFoundException">
|
|
/// The <see cref="ImapFolder"/> does not exist.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotOpenException">
|
|
/// The <see cref="ImapFolder"/> is not currently open in read-write mode.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// <para>Internationalized formatting was requested but is not supported by the server.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override Task<UniqueId?> ReplaceAsync (FormatOptions options, UniqueId uid, MimeMessage message, MessageFlags flags = MessageFlags.None, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null)
|
|
{
|
|
return ReplaceAsync (options, uid, message, flags, null, null, true, cancellationToken, progress);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Replace a message in the folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Replaces the specified message in the folder and returns the UniqueId assigned to the new message.
|
|
/// </remarks>
|
|
/// <returns>The UID of the new message, if available; otherwise, <c>null</c>.</returns>
|
|
/// <param name="options">The formatting options.</param>
|
|
/// <param name="uid">The UID of the message to be replaced.</param>
|
|
/// <param name="message">The message.</param>
|
|
/// <param name="flags">The message flags.</param>
|
|
/// <param name="date">The received date of the message.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <param name="progress">The progress reporting mechanism.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <para><paramref name="options"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="message"/> is <c>null</c>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// <paramref name="uid"/> is invalid.
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.InvalidOperationException">
|
|
/// Internationalized formatting was requested but has not been enabled.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotFoundException">
|
|
/// The <see cref="ImapFolder"/> does not exist.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotOpenException">
|
|
/// The <see cref="ImapFolder"/> is not currently open in read-write mode.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// <para>Internationalized formatting was requested but is not supported by the server.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override UniqueId? Replace (FormatOptions options, UniqueId uid, MimeMessage message, MessageFlags flags, DateTimeOffset date, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null)
|
|
{
|
|
return ReplaceAsync (options, uid, message, flags, date, null, false, cancellationToken, progress).GetAwaiter ().GetResult ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously replace a message in the folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Replaces the specified message in the folder and returns the UniqueId assigned to the new message.
|
|
/// </remarks>
|
|
/// <returns>The UID of the new message, if available; otherwise, <c>null</c>.</returns>
|
|
/// <param name="options">The formatting options.</param>
|
|
/// <param name="uid">The UID of the message to be replaced.</param>
|
|
/// <param name="message">The message.</param>
|
|
/// <param name="flags">The message flags.</param>
|
|
/// <param name="date">The received date of the message.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <param name="progress">The progress reporting mechanism.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <para><paramref name="options"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="message"/> is <c>null</c>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// <paramref name="uid"/> is invalid.
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.InvalidOperationException">
|
|
/// Internationalized formatting was requested but has not been enabled.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotFoundException">
|
|
/// The <see cref="ImapFolder"/> does not exist.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotOpenException">
|
|
/// The <see cref="ImapFolder"/> is not currently open in read-write mode.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// <para>Internationalized formatting was requested but is not supported by the server.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override Task<UniqueId?> ReplaceAsync (FormatOptions options, UniqueId uid, MimeMessage message, MessageFlags flags, DateTimeOffset date, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null)
|
|
{
|
|
return ReplaceAsync (options, uid, message, flags, date, null, true, cancellationToken, progress);
|
|
}
|
|
|
|
ImapCommand QueueReplace (FormatOptions options, int index, MimeMessage message, MessageFlags flags, DateTimeOffset? date, IList<Annotation> annotations, CancellationToken cancellationToken, ITransferProgress progress)
|
|
{
|
|
var builder = new StringBuilder ($"REPLACE %d %F ");
|
|
var list = new List<object> ();
|
|
|
|
list.Add (index + 1);
|
|
list.Add (this);
|
|
|
|
if ((flags & SettableFlags) != 0)
|
|
builder.AppendFormat ("{0} ", ImapUtils.FormatFlagsList (flags, 0));
|
|
|
|
if (date.HasValue)
|
|
builder.AppendFormat ("\"{0}\" ", ImapUtils.FormatInternalDate (date.Value));
|
|
|
|
//if (annotations != null && annotations.Count > 0) {
|
|
// ImapUtils.FormatAnnotations (builder, annotations, list, false);
|
|
//
|
|
// if (builder[builder.Length - 1] != ' ')
|
|
// builder.Append (' ');
|
|
//}
|
|
|
|
builder.Append ("%L\r\n");
|
|
list.Add (message);
|
|
|
|
var command = builder.ToString ();
|
|
var args = list.ToArray ();
|
|
|
|
var ic = new ImapCommand (Engine, cancellationToken, null, options, command, args);
|
|
ic.Progress = progress;
|
|
|
|
Engine.QueueCommand (ic);
|
|
|
|
return ic;
|
|
}
|
|
|
|
async Task<UniqueId?> ReplaceAsync (FormatOptions options, int index, MimeMessage message, MessageFlags flags, DateTimeOffset? date, IList<Annotation> annotations, bool doAsync, CancellationToken cancellationToken, ITransferProgress progress)
|
|
{
|
|
if (options == null)
|
|
throw new ArgumentNullException (nameof (options));
|
|
|
|
if (index < 0 || index >= Count)
|
|
throw new ArgumentOutOfRangeException (nameof (index));
|
|
|
|
if (message == null)
|
|
throw new ArgumentNullException (nameof (message));
|
|
|
|
CheckState (true, true);
|
|
|
|
if ((Engine.Capabilities & ImapCapabilities.Replace) == 0) {
|
|
var uid = await AppendAsync (options, message, flags, date, annotations, doAsync, cancellationToken, progress).ConfigureAwait (false);
|
|
await ModifyFlagsAsync (new[] { index }, null, MessageFlags.Deleted, null, "+FLAGS.SILENT", doAsync, cancellationToken).ConfigureAwait (false);
|
|
return uid;
|
|
}
|
|
|
|
if (options.International && (Engine.Capabilities & ImapCapabilities.UTF8Accept) == 0)
|
|
throw new NotSupportedException ("The IMAP server does not support the UTF8 extension.");
|
|
|
|
var format = options.Clone ();
|
|
format.NewLineFormat = NewLineFormat.Dos;
|
|
format.EnsureNewLine = true;
|
|
|
|
if ((Engine.Capabilities & ImapCapabilities.UTF8Only) == ImapCapabilities.UTF8Only)
|
|
format.International = true;
|
|
|
|
if (format.International && !Engine.UTF8Enabled)
|
|
throw new InvalidOperationException ("The UTF8 extension has not been enabled.");
|
|
|
|
var ic = QueueReplace (format, index, message, flags, date, annotations, cancellationToken, progress);
|
|
|
|
await Engine.RunAsync (ic, doAsync).ConfigureAwait (false);
|
|
|
|
ProcessResponseCodes (ic, this);
|
|
|
|
if (ic.Response != ImapCommandResponse.Ok)
|
|
throw ImapCommandException.Create ("REPLACE", ic);
|
|
|
|
var append = (AppendUidResponseCode) GetResponseCode (ic, ImapResponseCodeType.AppendUid);
|
|
|
|
if (append != null)
|
|
return append.UidSet[0];
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Replace a message in the folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Replaces the specified message in the folder and returns the UniqueId assigned to the new message.
|
|
/// </remarks>
|
|
/// <returns>The UID of the new message, if available; otherwise, <c>null</c>.</returns>
|
|
/// <param name="options">The formatting options.</param>
|
|
/// <param name="index">The index of the message to be replaced.</param>
|
|
/// <param name="message">The message.</param>
|
|
/// <param name="flags">The message flags.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <param name="progress">The progress reporting mechanism.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <para><paramref name="options"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="message"/> is <c>null</c>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ArgumentOutOfRangeException">
|
|
/// <paramref name="index"/> is out of range.
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.InvalidOperationException">
|
|
/// Internationalized formatting was requested but has not been enabled.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotFoundException">
|
|
/// The <see cref="ImapFolder"/> does not exist.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotOpenException">
|
|
/// The <see cref="ImapFolder"/> is not currently open in read-write mode.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// <para>Internationalized formatting was requested but is not supported by the server.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override UniqueId? Replace (FormatOptions options, int index, MimeMessage message, MessageFlags flags = MessageFlags.None, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null)
|
|
{
|
|
return ReplaceAsync (options, index, message, flags, null, null, false, cancellationToken, progress).GetAwaiter ().GetResult ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously replace a message in the folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Replaces the specified message in the folder and returns the UniqueId assigned to the new message.
|
|
/// </remarks>
|
|
/// <returns>The UID of the new message, if available; otherwise, <c>null</c>.</returns>
|
|
/// <param name="options">The formatting options.</param>
|
|
/// <param name="index">The index of the message to be replaced.</param>
|
|
/// <param name="message">The message.</param>
|
|
/// <param name="flags">The message flags.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <param name="progress">The progress reporting mechanism.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <para><paramref name="options"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="message"/> is <c>null</c>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ArgumentOutOfRangeException">
|
|
/// <paramref name="index"/> is out of range.
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.InvalidOperationException">
|
|
/// Internationalized formatting was requested but has not been enabled.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotFoundException">
|
|
/// The <see cref="ImapFolder"/> does not exist.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotOpenException">
|
|
/// The <see cref="ImapFolder"/> is not currently open in read-write mode.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// <para>Internationalized formatting was requested but is not supported by the server.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override Task<UniqueId?> ReplaceAsync (FormatOptions options, int index, MimeMessage message, MessageFlags flags = MessageFlags.None, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null)
|
|
{
|
|
return ReplaceAsync (options, index, message, flags, null, null, true, cancellationToken, progress);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Replace a message in the folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Replaces the specified message in the folder and returns the UniqueId assigned to the new message.
|
|
/// </remarks>
|
|
/// <returns>The UID of the new message, if available; otherwise, <c>null</c>.</returns>
|
|
/// <param name="options">The formatting options.</param>
|
|
/// <param name="index">The index of the message to be replaced.</param>
|
|
/// <param name="message">The message.</param>
|
|
/// <param name="flags">The message flags.</param>
|
|
/// <param name="date">The received date of the message.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <param name="progress">The progress reporting mechanism.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <para><paramref name="options"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="message"/> is <c>null</c>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ArgumentOutOfRangeException">
|
|
/// <paramref name="index"/> is out of range.
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.InvalidOperationException">
|
|
/// Internationalized formatting was requested but has not been enabled.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotFoundException">
|
|
/// The <see cref="ImapFolder"/> does not exist.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotOpenException">
|
|
/// The <see cref="ImapFolder"/> is not currently open in read-write mode.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// <para>Internationalized formatting was requested but is not supported by the server.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override UniqueId? Replace (FormatOptions options, int index, MimeMessage message, MessageFlags flags, DateTimeOffset date, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null)
|
|
{
|
|
return ReplaceAsync (options, index, message, flags, date, null, false, cancellationToken, progress).GetAwaiter ().GetResult ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously replace a message in the folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Replaces the specified message in the folder and returns the UniqueId assigned to the new message.
|
|
/// </remarks>
|
|
/// <returns>The UID of the new message, if available; otherwise, <c>null</c>.</returns>
|
|
/// <param name="options">The formatting options.</param>
|
|
/// <param name="index">The index of the message to be replaced.</param>
|
|
/// <param name="message">The message.</param>
|
|
/// <param name="flags">The message flags.</param>
|
|
/// <param name="date">The received date of the message.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <param name="progress">The progress reporting mechanism.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <para><paramref name="options"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="message"/> is <c>null</c>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ArgumentOutOfRangeException">
|
|
/// <paramref name="index"/> is out of range.
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="System.InvalidOperationException">
|
|
/// Internationalized formatting was requested but has not been enabled.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotFoundException">
|
|
/// The <see cref="ImapFolder"/> does not exist.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotOpenException">
|
|
/// The <see cref="ImapFolder"/> is not currently open in read-write mode.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// <para>Internationalized formatting was requested but is not supported by the server.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override Task<UniqueId?> ReplaceAsync (FormatOptions options, int index, MimeMessage message, MessageFlags flags, DateTimeOffset date, CancellationToken cancellationToken = default (CancellationToken), ITransferProgress progress = null)
|
|
{
|
|
return ReplaceAsync (options, index, message, flags, date, null, true, cancellationToken, progress);
|
|
}
|
|
|
|
async Task<IList<int>> GetIndexesAsync (IList<UniqueId> uids, bool doAsync, CancellationToken cancellationToken)
|
|
{
|
|
var command = string.Format ("SEARCH UID {0}\r\n", UniqueIdSet.ToString (uids));
|
|
var ic = new ImapCommand (Engine, cancellationToken, this, command);
|
|
var results = new SearchResults (SortOrder.Ascending);
|
|
|
|
if ((Engine.Capabilities & ImapCapabilities.ESearch) != 0)
|
|
ic.RegisterUntaggedHandler ("ESEARCH", ESearchMatchesAsync);
|
|
|
|
ic.RegisterUntaggedHandler ("SEARCH", SearchMatchesAsync);
|
|
ic.UserData = results;
|
|
|
|
Engine.QueueCommand (ic);
|
|
|
|
await Engine.RunAsync (ic, doAsync).ConfigureAwait (false);
|
|
|
|
ProcessResponseCodes (ic, null);
|
|
|
|
if (ic.Response != ImapCommandResponse.Ok)
|
|
throw ImapCommandException.Create ("SEARCH", ic);
|
|
|
|
var indexes = new int[results.UniqueIds.Count];
|
|
for (int i = 0; i < indexes.Length; i++)
|
|
indexes[i] = (int) results.UniqueIds[i].Id - 1;
|
|
|
|
return indexes;
|
|
}
|
|
|
|
async Task<UniqueIdMap> CopyToAsync (IList<UniqueId> uids, IMailFolder destination, bool doAsync, CancellationToken cancellationToken)
|
|
{
|
|
if (uids == null)
|
|
throw new ArgumentNullException (nameof (uids));
|
|
|
|
if (destination == null)
|
|
throw new ArgumentNullException (nameof (destination));
|
|
|
|
if (!(destination is ImapFolder) || ((ImapFolder) destination).Engine != Engine)
|
|
throw new ArgumentException ("The destination folder does not belong to this ImapClient.", nameof (destination));
|
|
|
|
CheckState (true, false);
|
|
|
|
if (uids.Count == 0)
|
|
return UniqueIdMap.Empty;
|
|
|
|
if ((Engine.Capabilities & ImapCapabilities.UidPlus) == 0) {
|
|
var indexes = await GetIndexesAsync (uids, doAsync, cancellationToken).ConfigureAwait (false);
|
|
await CopyToAsync (indexes, destination, doAsync, cancellationToken).ConfigureAwait (false);
|
|
return UniqueIdMap.Empty;
|
|
}
|
|
|
|
UniqueIdSet dest = null;
|
|
UniqueIdSet src = null;
|
|
|
|
foreach (var ic in Engine.QueueCommands (cancellationToken, this, "UID COPY %s %F\r\n", uids, destination)) {
|
|
await Engine.RunAsync (ic, doAsync).ConfigureAwait (false);
|
|
|
|
ProcessResponseCodes (ic, destination);
|
|
|
|
if (ic.Response != ImapCommandResponse.Ok)
|
|
throw ImapCommandException.Create ("COPY", ic);
|
|
|
|
var copy = (CopyUidResponseCode) GetResponseCode (ic, ImapResponseCodeType.CopyUid);
|
|
|
|
if (copy != null) {
|
|
if (dest == null) {
|
|
dest = copy.DestUidSet;
|
|
src = copy.SrcUidSet;
|
|
} else {
|
|
dest.AddRange (copy.DestUidSet);
|
|
src.AddRange (copy.SrcUidSet);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (dest == null)
|
|
return UniqueIdMap.Empty;
|
|
|
|
return new UniqueIdMap (src, dest);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copy the specified messages to the destination folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Copies the specified messages to the destination folder.
|
|
/// </remarks>
|
|
/// <returns>The UID mapping of the messages in the destination folder, if available; otherwise an empty mapping.</returns>
|
|
/// <param name="uids">The UIDs of the messages to copy.</param>
|
|
/// <param name="destination">The destination folder.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <para><paramref name="uids"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="destination"/> is <c>null</c>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// <para>One or more of the <paramref name="uids"/> is invalid.</para>
|
|
/// <para>-or-</para>
|
|
/// <para>The destination folder does not belong to the <see cref="ImapClient"/>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotFoundException">
|
|
/// <paramref name="destination"/> does not exist.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotOpenException">
|
|
/// The <see cref="ImapFolder"/> is not currently open.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// The IMAP server does not support the UIDPLUS extension.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override UniqueIdMap CopyTo (IList<UniqueId> uids, IMailFolder destination, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return CopyToAsync (uids, destination, false, cancellationToken).GetAwaiter ().GetResult ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously copy the specified messages to the destination folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Copies the specified messages to the destination folder.
|
|
/// </remarks>
|
|
/// <returns>The UID mapping of the messages in the destination folder, if available; otherwise an empty mapping.</returns>
|
|
/// <param name="uids">The UIDs of the messages to copy.</param>
|
|
/// <param name="destination">The destination folder.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <para><paramref name="uids"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="destination"/> is <c>null</c>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// <para>One or more of the <paramref name="uids"/> is invalid.</para>
|
|
/// <para>-or-</para>
|
|
/// <para>The destination folder does not belong to the <see cref="ImapClient"/>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotFoundException">
|
|
/// <paramref name="destination"/> does not exist.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotOpenException">
|
|
/// The <see cref="ImapFolder"/> is not currently open.
|
|
/// </exception>
|
|
/// <exception cref="System.NotSupportedException">
|
|
/// The IMAP server does not support the UIDPLUS extension.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override Task<UniqueIdMap> CopyToAsync (IList<UniqueId> uids, IMailFolder destination, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return CopyToAsync (uids, destination, true, cancellationToken);
|
|
}
|
|
|
|
async Task<UniqueIdMap> MoveToAsync (IList<UniqueId> uids, IMailFolder destination, bool doAsync, CancellationToken cancellationToken)
|
|
{
|
|
if ((Engine.Capabilities & ImapCapabilities.Move) == 0) {
|
|
var copied = await CopyToAsync (uids, destination, doAsync, cancellationToken).ConfigureAwait (false);
|
|
await ModifyFlagsAsync (uids, null, MessageFlags.Deleted, null, "+FLAGS.SILENT", doAsync, cancellationToken).ConfigureAwait (false);
|
|
await ExpungeAsync (uids, doAsync, cancellationToken).ConfigureAwait (false);
|
|
return copied;
|
|
}
|
|
|
|
if ((Engine.Capabilities & ImapCapabilities.UidPlus) == 0) {
|
|
var indexes = await GetIndexesAsync (uids, doAsync, cancellationToken).ConfigureAwait (false);
|
|
await MoveToAsync (indexes, destination, doAsync, cancellationToken).ConfigureAwait (false);
|
|
return UniqueIdMap.Empty;
|
|
}
|
|
|
|
if (uids == null)
|
|
throw new ArgumentNullException (nameof (uids));
|
|
|
|
if (destination == null)
|
|
throw new ArgumentNullException (nameof (destination));
|
|
|
|
if (!(destination is ImapFolder) || ((ImapFolder) destination).Engine != Engine)
|
|
throw new ArgumentException ("The destination folder does not belong to this ImapClient.", nameof (destination));
|
|
|
|
CheckState (true, true);
|
|
|
|
if (uids.Count == 0)
|
|
return UniqueIdMap.Empty;
|
|
|
|
UniqueIdSet dest = null;
|
|
UniqueIdSet src = null;
|
|
|
|
foreach (var ic in Engine.QueueCommands (cancellationToken, this, "UID MOVE %s %F\r\n", uids, destination)) {
|
|
await Engine.RunAsync (ic, doAsync).ConfigureAwait (false);
|
|
|
|
ProcessResponseCodes (ic, destination);
|
|
|
|
if (ic.Response != ImapCommandResponse.Ok)
|
|
throw ImapCommandException.Create ("MOVE", ic);
|
|
|
|
var copy = (CopyUidResponseCode) GetResponseCode (ic, ImapResponseCodeType.CopyUid);
|
|
|
|
if (copy != null) {
|
|
if (dest == null) {
|
|
dest = copy.DestUidSet;
|
|
src = copy.SrcUidSet;
|
|
} else {
|
|
dest.AddRange (copy.DestUidSet);
|
|
src.AddRange (copy.SrcUidSet);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (dest == null)
|
|
return UniqueIdMap.Empty;
|
|
|
|
return new UniqueIdMap (src, dest);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Move the specified messages to the destination folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>Moves the specified messages to the destination folder.</para>
|
|
/// <para>If the IMAP server supports the MOVE extension (check the <see cref="ImapClient.Capabilities"/>
|
|
/// property for the <see cref="ImapCapabilities.Move"/> flag), then this operation will be atomic.
|
|
/// Otherwise, MailKit implements this by first copying the messages to the destination folder, then
|
|
/// marking them for deletion in the originating folder, and finally expunging them (see
|
|
/// <see cref="Expunge(IList<UniqueId>,CancellationToken)"/> for more information about how a
|
|
/// subset of messages are expunged). Since the server could disconnect at any point between those 3
|
|
/// (or more) commands, it is advisable for clients to implement their own logic for moving messages when
|
|
/// the IMAP server does not support the MOVE command in order to better handle spontanious server
|
|
/// disconnects and other error conditions.</para>
|
|
/// </remarks>
|
|
/// <returns>The UID mapping of the messages in the destination folder, if available; otherwise an empty mapping.</returns>
|
|
/// <param name="uids">The UIDs of the messages to move.</param>
|
|
/// <param name="destination">The destination folder.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <para><paramref name="uids"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="destination"/> is <c>null</c>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// <para><paramref name="uids"/> is empty.</para>
|
|
/// <para>-or-</para>
|
|
/// <para>One or more of the <paramref name="uids"/> is invalid.</para>
|
|
/// <para>-or-</para>
|
|
/// <para>The destination folder does not belong to the <see cref="ImapClient"/>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotFoundException">
|
|
/// <paramref name="destination"/> does not exist.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotOpenException">
|
|
/// The <see cref="ImapFolder"/> is not currently open in read-write mode.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override UniqueIdMap MoveTo (IList<UniqueId> uids, IMailFolder destination, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return MoveToAsync (uids, destination, false, cancellationToken).GetAwaiter ().GetResult ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously move the specified messages to the destination folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>Moves the specified messages to the destination folder.</para>
|
|
/// <para>If the IMAP server supports the MOVE extension (check the <see cref="ImapClient.Capabilities"/>
|
|
/// property for the <see cref="ImapCapabilities.Move"/> flag), then this operation will be atomic.
|
|
/// Otherwise, MailKit implements this by first copying the messages to the destination folder, then
|
|
/// marking them for deletion in the originating folder, and finally expunging them (see
|
|
/// <see cref="Expunge(IList<UniqueId>,CancellationToken)"/> for more information about how a
|
|
/// subset of messages are expunged). Since the server could disconnect at any point between those 3
|
|
/// (or more) commands, it is advisable for clients to implement their own logic for moving messages when
|
|
/// the IMAP server does not support the MOVE command in order to better handle spontanious server
|
|
/// disconnects and other error conditions.</para>
|
|
/// </remarks>
|
|
/// <returns>The UID mapping of the messages in the destination folder, if available; otherwise an empty mapping.</returns>
|
|
/// <param name="uids">The UIDs of the messages to move.</param>
|
|
/// <param name="destination">The destination folder.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <para><paramref name="uids"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="destination"/> is <c>null</c>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// <para><paramref name="uids"/> is empty.</para>
|
|
/// <para>-or-</para>
|
|
/// <para>One or more of the <paramref name="uids"/> is invalid.</para>
|
|
/// <para>-or-</para>
|
|
/// <para>The destination folder does not belong to the <see cref="ImapClient"/>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotFoundException">
|
|
/// <paramref name="destination"/> does not exist.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotOpenException">
|
|
/// The <see cref="ImapFolder"/> is not currently open in read-write mode.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override Task<UniqueIdMap> MoveToAsync (IList<UniqueId> uids, IMailFolder destination, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return MoveToAsync (uids, destination, true, cancellationToken);
|
|
}
|
|
|
|
async Task CopyToAsync (IList<int> indexes, IMailFolder destination, bool doAsync, CancellationToken cancellationToken)
|
|
{
|
|
if (indexes == null)
|
|
throw new ArgumentNullException (nameof (indexes));
|
|
|
|
if (destination == null)
|
|
throw new ArgumentNullException (nameof (destination));
|
|
|
|
if (!(destination is ImapFolder) || ((ImapFolder) destination).Engine != Engine)
|
|
throw new ArgumentException ("The destination folder does not belong to this ImapClient.", nameof (destination));
|
|
|
|
CheckState (true, false);
|
|
CheckAllowIndexes ();
|
|
|
|
if (indexes.Count == 0)
|
|
return;
|
|
|
|
var set = ImapUtils.FormatIndexSet (indexes);
|
|
var command = string.Format ("COPY {0} %F\r\n", set);
|
|
var ic = Engine.QueueCommand (cancellationToken, this, command, destination);
|
|
|
|
await Engine.RunAsync (ic, doAsync).ConfigureAwait (false);
|
|
|
|
ProcessResponseCodes (ic, destination);
|
|
|
|
if (ic.Response != ImapCommandResponse.Ok)
|
|
throw ImapCommandException.Create ("COPY", ic);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Copy the specified messages to the destination folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Copies the specified messages to the destination folder.
|
|
/// </remarks>
|
|
/// <param name="indexes">The indexes of the messages to copy.</param>
|
|
/// <param name="destination">The destination folder.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <para><paramref name="indexes"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="destination"/> is <c>null</c>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// <para>One or more of the <paramref name="indexes"/> is invalid.</para>
|
|
/// <para>-or-</para>
|
|
/// <para>The destination folder does not belong to the <see cref="ImapClient"/>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotOpenException">
|
|
/// The <see cref="ImapFolder"/> is not currently open.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotFoundException">
|
|
/// <paramref name="destination"/> does not exist.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotOpenException">
|
|
/// The <see cref="ImapFolder"/> is not currently open.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override void CopyTo (IList<int> indexes, IMailFolder destination, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
CopyToAsync (indexes, destination, false, cancellationToken).GetAwaiter ().GetResult ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously copy the specified messages to the destination folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Copies the specified messages to the destination folder.
|
|
/// </remarks>
|
|
/// <returns>An awaitable task.</returns>
|
|
/// <param name="indexes">The indexes of the messages to copy.</param>
|
|
/// <param name="destination">The destination folder.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <para><paramref name="indexes"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="destination"/> is <c>null</c>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// <para>One or more of the <paramref name="indexes"/> is invalid.</para>
|
|
/// <para>-or-</para>
|
|
/// <para>The destination folder does not belong to the <see cref="ImapClient"/>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotOpenException">
|
|
/// The <see cref="ImapFolder"/> is not currently open.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotFoundException">
|
|
/// <paramref name="destination"/> does not exist.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotOpenException">
|
|
/// The <see cref="ImapFolder"/> is not currently open.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override Task CopyToAsync (IList<int> indexes, IMailFolder destination, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return CopyToAsync (indexes, destination, true, cancellationToken);
|
|
}
|
|
|
|
async Task MoveToAsync (IList<int> indexes, IMailFolder destination, bool doAsync, CancellationToken cancellationToken)
|
|
{
|
|
if ((Engine.Capabilities & ImapCapabilities.Move) == 0) {
|
|
await CopyToAsync (indexes, destination, doAsync, cancellationToken).ConfigureAwait (false);
|
|
await ModifyFlagsAsync (indexes, null, MessageFlags.Deleted, null, "+FLAGS.SILENT", doAsync, cancellationToken).ConfigureAwait (false);
|
|
return;
|
|
}
|
|
|
|
if (indexes == null)
|
|
throw new ArgumentNullException (nameof (indexes));
|
|
|
|
if (destination == null)
|
|
throw new ArgumentNullException (nameof (destination));
|
|
|
|
if (!(destination is ImapFolder) || ((ImapFolder) destination).Engine != Engine)
|
|
throw new ArgumentException ("The destination folder does not belong to this ImapClient.", nameof (destination));
|
|
|
|
CheckState (true, true);
|
|
CheckAllowIndexes ();
|
|
|
|
if (indexes.Count == 0)
|
|
return;
|
|
|
|
var set = ImapUtils.FormatIndexSet (indexes);
|
|
var command = string.Format ("MOVE {0} %F\r\n", set);
|
|
var ic = Engine.QueueCommand (cancellationToken, this, command, destination);
|
|
|
|
await Engine.RunAsync (ic, doAsync).ConfigureAwait (false);
|
|
|
|
ProcessResponseCodes (ic, destination);
|
|
|
|
if (ic.Response != ImapCommandResponse.Ok)
|
|
throw ImapCommandException.Create ("MOVE", ic);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Move the specified messages to the destination folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>If the IMAP server supports the MOVE command, then the MOVE command will be used. Otherwise,
|
|
/// the messages will first be copied to the destination folder and then marked as \Deleted in the
|
|
/// originating folder. Since the server could disconnect at any point between those 2 operations, it
|
|
/// may be advisable to implement your own logic for moving messages in this case in order to better
|
|
/// handle spontanious server disconnects and other error conditions.</para>
|
|
/// </remarks>
|
|
/// <param name="indexes">The indexes of the messages to move.</param>
|
|
/// <param name="destination">The destination folder.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <para><paramref name="indexes"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="destination"/> is <c>null</c>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// <para>One or more of the <paramref name="indexes"/> is invalid.</para>
|
|
/// <para>-or-</para>
|
|
/// <para>The destination folder does not belong to the <see cref="ImapClient"/>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotFoundException">
|
|
/// <paramref name="destination"/> does not exist.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotOpenException">
|
|
/// The <see cref="ImapFolder"/> is not currently open in read-write mode.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override void MoveTo (IList<int> indexes, IMailFolder destination, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
MoveToAsync (indexes, destination, false, cancellationToken).GetAwaiter ().GetResult ();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Asynchronously move the specified messages to the destination folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// <para>If the IMAP server supports the MOVE command, then the MOVE command will be used. Otherwise,
|
|
/// the messages will first be copied to the destination folder and then marked as \Deleted in the
|
|
/// originating folder. Since the server could disconnect at any point between those 2 operations, it
|
|
/// may be advisable to implement your own logic for moving messages in this case in order to better
|
|
/// handle spontanious server disconnects and other error conditions.</para>
|
|
/// </remarks>
|
|
/// <returns>An awaitable task.</returns>
|
|
/// <param name="indexes">The indexes of the messages to move.</param>
|
|
/// <param name="destination">The destination folder.</param>
|
|
/// <param name="cancellationToken">The cancellation token.</param>
|
|
/// <exception cref="System.ArgumentNullException">
|
|
/// <para><paramref name="indexes"/> is <c>null</c>.</para>
|
|
/// <para>-or-</para>
|
|
/// <para><paramref name="destination"/> is <c>null</c>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ArgumentException">
|
|
/// <para>One or more of the <paramref name="indexes"/> is invalid.</para>
|
|
/// <para>-or-</para>
|
|
/// <para>The destination folder does not belong to the <see cref="ImapClient"/>.</para>
|
|
/// </exception>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotFoundException">
|
|
/// <paramref name="destination"/> does not exist.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotOpenException">
|
|
/// The <see cref="ImapFolder"/> is not currently open in read-write mode.
|
|
/// </exception>
|
|
/// <exception cref="System.OperationCanceledException">
|
|
/// The operation was canceled via the cancellation token.
|
|
/// </exception>
|
|
/// <exception cref="System.IO.IOException">
|
|
/// An I/O error occurred.
|
|
/// </exception>
|
|
/// <exception cref="ImapProtocolException">
|
|
/// The server's response contained unexpected tokens.
|
|
/// </exception>
|
|
/// <exception cref="ImapCommandException">
|
|
/// The server replied with a NO or BAD response.
|
|
/// </exception>
|
|
public override Task MoveToAsync (IList<int> indexes, IMailFolder destination, CancellationToken cancellationToken = default (CancellationToken))
|
|
{
|
|
return MoveToAsync (indexes, destination, true, cancellationToken);
|
|
}
|
|
|
|
#region IEnumerable<MimeMessage> implementation
|
|
|
|
/// <summary>
|
|
/// Get an enumerator for the messages in the folder.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Gets an enumerator for the messages in the folder.
|
|
/// </remarks>
|
|
/// <returns>The enumerator.</returns>
|
|
/// <exception cref="System.ObjectDisposedException">
|
|
/// The <see cref="ImapClient"/> has been disposed.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotConnectedException">
|
|
/// The <see cref="ImapClient"/> is not connected.
|
|
/// </exception>
|
|
/// <exception cref="ServiceNotAuthenticatedException">
|
|
/// The <see cref="ImapClient"/> is not authenticated.
|
|
/// </exception>
|
|
/// <exception cref="FolderNotOpenException">
|
|
/// The <see cref="ImapFolder"/> is not currently open.
|
|
/// </exception>
|
|
public override IEnumerator<MimeMessage> GetEnumerator ()
|
|
{
|
|
CheckState (true, false);
|
|
|
|
for (int i = 0; i < Count; i++)
|
|
yield return GetMessage (i, CancellationToken.None);
|
|
|
|
yield break;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Untagged response handlers called by ImapEngine
|
|
|
|
internal void OnExists (int count)
|
|
{
|
|
if (Count == count)
|
|
return;
|
|
|
|
Count = count;
|
|
|
|
OnCountChanged ();
|
|
}
|
|
|
|
internal void OnExpunge (int index)
|
|
{
|
|
Count--;
|
|
|
|
OnMessageExpunged (new MessageEventArgs (index));
|
|
OnCountChanged ();
|
|
}
|
|
|
|
internal async Task OnFetchAsync (ImapEngine engine, int index, bool doAsync, CancellationToken cancellationToken)
|
|
{
|
|
var message = new MessageSummary (this, index);
|
|
UniqueId? uid = null;
|
|
|
|
await FetchSummaryItemsAsync (engine, message, doAsync, cancellationToken).ConfigureAwait (false);
|
|
|
|
if ((message.Fields & MessageSummaryItems.UniqueId) != 0)
|
|
uid = message.UniqueId;
|
|
|
|
if ((message.Fields & MessageSummaryItems.Flags) != 0) {
|
|
var args = new MessageFlagsChangedEventArgs (index, message.Flags.Value, message.Keywords);
|
|
args.ModSeq = message.ModSeq;
|
|
args.UniqueId = uid;
|
|
|
|
OnMessageFlagsChanged (args);
|
|
}
|
|
|
|
if ((message.Fields & MessageSummaryItems.GMailLabels) != 0) {
|
|
var args = new MessageLabelsChangedEventArgs (index, message.GMailLabels);
|
|
args.ModSeq = message.ModSeq;
|
|
args.UniqueId = uid;
|
|
|
|
OnMessageLabelsChanged (args);
|
|
}
|
|
|
|
if ((message.Fields & MessageSummaryItems.Annotations) != 0) {
|
|
var args = new AnnotationsChangedEventArgs (index, message.Annotations);
|
|
args.ModSeq = message.ModSeq;
|
|
args.UniqueId = uid;
|
|
|
|
OnAnnotationsChanged (args);
|
|
}
|
|
|
|
if ((message.Fields & MessageSummaryItems.ModSeq) != 0) {
|
|
var args = new ModSeqChangedEventArgs (index, message.ModSeq.Value);
|
|
args.UniqueId = uid;
|
|
|
|
OnModSeqChanged (args);
|
|
}
|
|
|
|
if (message.Fields != MessageSummaryItems.None)
|
|
OnMessageSummaryFetched (message);
|
|
}
|
|
|
|
internal void OnRecent (int count)
|
|
{
|
|
if (Recent == count)
|
|
return;
|
|
|
|
Recent = count;
|
|
|
|
OnRecentChanged ();
|
|
}
|
|
|
|
internal async Task OnVanishedAsync (ImapEngine engine, bool doAsync, CancellationToken cancellationToken)
|
|
{
|
|
var token = await engine.ReadTokenAsync (doAsync, cancellationToken).ConfigureAwait (false);
|
|
UniqueIdSet vanished;
|
|
bool earlier = false;
|
|
|
|
if (token.Type == ImapTokenType.OpenParen) {
|
|
do {
|
|
token = await engine.ReadTokenAsync (doAsync, cancellationToken).ConfigureAwait (false);
|
|
|
|
if (token.Type == ImapTokenType.CloseParen)
|
|
break;
|
|
|
|
ImapEngine.AssertToken (token, ImapTokenType.Atom, ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "VANISHED", token);
|
|
|
|
var atom = (string) token.Value;
|
|
|
|
if (atom == "EARLIER")
|
|
earlier = true;
|
|
} while (true);
|
|
|
|
token = await engine.ReadTokenAsync (doAsync, cancellationToken).ConfigureAwait (false);
|
|
}
|
|
|
|
vanished = ImapEngine.ParseUidSet (token, UidValidity, ImapEngine.GenericUntaggedResponseSyntaxErrorFormat, "VANISHED", token);
|
|
|
|
OnMessagesVanished (new MessagesVanishedEventArgs (vanished, earlier));
|
|
|
|
if (!earlier) {
|
|
Count -= vanished.Count;
|
|
|
|
OnCountChanged ();
|
|
}
|
|
}
|
|
|
|
internal void UpdateAttributes (FolderAttributes attrs)
|
|
{
|
|
var unsubscribed = false;
|
|
var subscribed = false;
|
|
|
|
if ((attrs & FolderAttributes.Subscribed) == 0)
|
|
unsubscribed = (Attributes & FolderAttributes.Subscribed) != 0;
|
|
else
|
|
subscribed = (Attributes & FolderAttributes.Subscribed) == 0;
|
|
|
|
var deleted = ((attrs & FolderAttributes.NonExistent) != 0) &&
|
|
(Attributes & FolderAttributes.NonExistent) == 0;
|
|
|
|
Attributes = attrs;
|
|
|
|
if (unsubscribed)
|
|
OnUnsubscribed ();
|
|
|
|
if (subscribed)
|
|
OnSubscribed ();
|
|
|
|
if (deleted)
|
|
OnDeleted ();
|
|
}
|
|
|
|
internal void UpdateAcceptedFlags (MessageFlags flags)
|
|
{
|
|
AcceptedFlags = flags;
|
|
}
|
|
|
|
internal void UpdatePermanentFlags (MessageFlags flags)
|
|
{
|
|
PermanentFlags = flags;
|
|
}
|
|
|
|
internal void UpdateIsNamespace (bool value)
|
|
{
|
|
IsNamespace = value;
|
|
}
|
|
|
|
internal void UpdateUnread (int count)
|
|
{
|
|
if (Unread == count)
|
|
return;
|
|
|
|
Unread = count;
|
|
|
|
OnUnreadChanged ();
|
|
}
|
|
|
|
internal void UpdateUidNext (UniqueId uid)
|
|
{
|
|
if (UidNext.HasValue && UidNext.Value == uid)
|
|
return;
|
|
|
|
UidNext = uid;
|
|
|
|
OnUidNextChanged ();
|
|
}
|
|
|
|
internal void UpdateAppendLimit (uint? limit)
|
|
{
|
|
AppendLimit = limit;
|
|
}
|
|
|
|
internal void UpdateSize (ulong? size)
|
|
{
|
|
if (Size == size)
|
|
return;
|
|
|
|
Size = size;
|
|
|
|
OnSizeChanged ();
|
|
}
|
|
|
|
internal void UpdateId (string id)
|
|
{
|
|
if (Id == id)
|
|
return;
|
|
|
|
Id = id;
|
|
|
|
OnIdChanged ();
|
|
}
|
|
|
|
internal void UpdateHighestModSeq (ulong modseq)
|
|
{
|
|
if (HighestModSeq == modseq)
|
|
return;
|
|
|
|
HighestModSeq = modseq;
|
|
|
|
OnHighestModSeqChanged ();
|
|
}
|
|
|
|
internal void UpdateUidValidity (uint validity)
|
|
{
|
|
if (UidValidity == validity)
|
|
return;
|
|
|
|
UidValidity = validity;
|
|
|
|
OnUidValidityChanged ();
|
|
}
|
|
|
|
internal void OnRenamed (ImapFolderConstructorArgs args)
|
|
{
|
|
var oldFullName = FullName;
|
|
|
|
InitializeProperties (args);
|
|
|
|
OnRenamed (oldFullName, FullName);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#endregion
|
|
}
|
|
}
|