This commit is contained in:
Jim Nelson 2013-05-29 22:24:47 -07:00
parent 1dfcf240b4
commit f2552d101f
21 changed files with 473 additions and 290 deletions

View file

@ -86,9 +86,12 @@ engine/imap/message/imap-mailbox-parameter.vala
engine/imap/message/imap-message-data.vala
engine/imap/message/imap-message-set.vala
engine/imap/message/imap-parameter.vala
engine/imap/message/imap-sequence-number.vala
engine/imap/message/imap-status-data.vala
engine/imap/message/imap-status-data-type.vala
engine/imap/message/imap-tag.vala
engine/imap/message/imap-uid.vala
engine/imap/message/imap-uid-validity.vala
engine/imap/response/imap-coded-status-response.vala
engine/imap/response/imap-completion-status-response.vala
engine/imap/response/imap-continuation-response.vala

View file

@ -689,8 +689,8 @@ private class Geary.ImapEngine.GenericFolder : Geary.AbstractFolder, Geary.Folde
// normalize starting at the message *after* the highest position of the local store,
// which has now changed
Imap.MessageSet msg_set = new Imap.MessageSet.range_by_first_last(remote_count + 1,
new_remote_count);
Imap.MessageSet msg_set = new Imap.MessageSet.range_by_first_last(
new Imap.SequenceNumber(remote_count + 1), new Imap.SequenceNumber(new_remote_count));
Gee.List<Geary.Email>? list = yield remote_folder.list_email_async(
msg_set, ImapDB.Folder.REQUIRED_FOR_DUPLICATE_DETECTION, null);
if (list != null && list.size > 0) {
@ -1146,7 +1146,7 @@ private class Geary.ImapEngine.GenericFolder : Geary.AbstractFolder, Geary.Folde
// Normalize the local folder by fetching EmailIdentifiers for all missing email as well
// as fields for duplicate detection
Gee.List<Geary.Email>? list = yield remote_folder.list_email_async(
new Imap.MessageSet.range_by_count(high, prefetch_count),
new Imap.MessageSet.range_by_count(new Imap.SequenceNumber(high), prefetch_count),
ImapDB.Folder.REQUIRED_FOR_DUPLICATE_DETECTION, cancellable);
if (list == null || list.size != prefetch_count) {
throw new EngineError.BAD_PARAMETERS("Unable to prefetch %d email starting at %d in %s",

View file

@ -318,9 +318,11 @@ private class Geary.ImapEngine.ListEmail : Geary.ImapEngine.SendReplayOperation
list = needed_by_position;
}
Imap.SequenceNumber[] seq_list = Imap.SequenceNumber.to_list(list);
// pull from server
Gee.List<Geary.Email>? remote_list = yield engine.remote_folder.list_email_async(
new Imap.MessageSet.sparse(list), required_fields, cancellable);
new Imap.MessageSet.sparse(seq_list), required_fields, cancellable);
if (remote_list == null || remote_list.size == 0)
break;

View file

@ -19,8 +19,8 @@ private class Geary.Imap.Folder : BaseObject {
private ClientSessionManager session_mgr;
private ClientSession? session = null;
private Nonblocking.Mutex fetch_mutex = new Nonblocking.Mutex();
private Gee.HashMap<MessageNumber, FetchedData> fetch_accumulator = new Gee.HashMap<
MessageNumber, FetchedData>();
private Gee.HashMap<SequenceNumber, FetchedData> fetch_accumulator = new Gee.HashMap<
SequenceNumber, FetchedData>();
public signal void exists(int total);
@ -132,7 +132,7 @@ private class Geary.Imap.Folder : BaseObject {
appended(total);
}
private void on_expunge(MessageNumber pos) {
private void on_expunge(SequenceNumber pos) {
debug("%s EXPUNGE %s", to_string(), pos.to_string());
properties.set_select_examine_message_count(properties.select_examine_messages - 1);
@ -143,8 +143,8 @@ private class Geary.Imap.Folder : BaseObject {
private void on_fetch(FetchedData fetched_data) {
// add if not found, merge if already received data for this email
FetchedData? already_present = fetch_accumulator.get(fetched_data.msg_num);
fetch_accumulator.set(fetched_data.msg_num,
FetchedData? already_present = fetch_accumulator.get(fetched_data.seq_num);
fetch_accumulator.set(fetched_data.seq_num,
(already_present != null) ? fetched_data.combine(already_present) : fetched_data);
fetched(fetched_data);
@ -196,7 +196,7 @@ private class Geary.Imap.Folder : BaseObject {
}
// For FETCH or STORE commands, both of which return FETCH results.
private async Gee.HashMap<MessageNumber, FetchedData> store_fetch_commands_async(MessageSet msg_set,
private async Gee.HashMap<SequenceNumber, FetchedData> store_fetch_commands_async(MessageSet msg_set,
Gee.Collection<Command> store_fetch_cmds, bool lock_mutex, Cancellable? cancellable)
throws Error {
// watch for deadlock
@ -216,8 +216,8 @@ private class Geary.Imap.Folder : BaseObject {
}
// swap out results and clear accumulator
Gee.HashMap<MessageNumber, FetchedData> results = fetch_accumulator;
fetch_accumulator = new Gee.HashMap<MessageNumber, FetchedData>();
Gee.HashMap<SequenceNumber, FetchedData> results = fetch_accumulator;
fetch_accumulator = new Gee.HashMap<SequenceNumber, FetchedData>();
// unlock after clearing accumulator
if (token != Nonblocking.Mutex.INVALID_TOKEN)
@ -326,8 +326,8 @@ private class Geary.Imap.Folder : BaseObject {
cmds.add(new FetchCommand(msg_set, data_types, null));
}
Gee.HashMap<MessageNumber, UID>? pos_uid_map = null;
Gee.HashMap<MessageNumber, FetchedData>? fetched = null;
Gee.HashMap<SequenceNumber, UID>? pos_uid_map = null;
Gee.HashMap<SequenceNumber, FetchedData>? fetched = null;
Error? fetch_err = null;
// Commands prepped, do the actual fetching with the mutex in place
@ -338,7 +338,7 @@ private class Geary.Imap.Folder : BaseObject {
// for responses to come back in any order, broken up in any way, with only positional
// addressing, and there's no way to build Email's without the UID.
if (!msg_set.is_uid) {
pos_uid_map = new Gee.HashMap<MessageNumber, UID>();
pos_uid_map = new Gee.HashMap<SequenceNumber, UID>();
FetchCommand cmd = new FetchCommand.data_type(msg_set, FetchDataType.UID);
Gee.HashMap<MessageData, FetchedData> uids = yield store_fetch_commands_async(
@ -346,10 +346,13 @@ private class Geary.Imap.Folder : BaseObject {
// convert fetched UIDs into easy-lookup map
foreach (FetchedData fetched_data in uids.values) {
if (fetched_data.data_map.has_key(FetchDataType.UID))
pos_uid_map.set(fetched_data.msg_num, (UID) fetched_data.data_map.get(FetchDataType.UID));
else
debug("No UID in FetchedData for %s on %s", fetched_data.msg_num.to_string(), to_string());
if (fetched_data.data_map.has_key(FetchDataType.UID)) {
pos_uid_map.set(fetched_data.seq_num,
(UID) fetched_data.data_map.get(FetchDataType.UID));
} else {
debug("No UID in FetchedData for %s on %s", fetched_data.seq_num.to_string(),
to_string());
}
}
}
@ -368,14 +371,14 @@ private class Geary.Imap.Folder : BaseObject {
// Convert fetched data into Geary.Email objects
Gee.List<Geary.Email> email_list = new Gee.ArrayList<Geary.Email>();
foreach (MessageNumber msg_num in fetched.keys) {
FetchedData fetched_data = fetched.get(msg_num);
foreach (SequenceNumber seq_num in fetched.keys) {
FetchedData fetched_data = fetched.get(seq_num);
// the UID should either have been looked up (if using positional addressing) or should
// have come back with the response (if using UID addressing)
UID? uid = null;
if (pos_uid_map != null)
uid = pos_uid_map.get(msg_num);
uid = pos_uid_map.get(seq_num);
else
uid = (UID) fetched_data.data_map.get(FetchDataType.UID);
assert(uid != null);
@ -534,7 +537,7 @@ private class Geary.Imap.Folder : BaseObject {
FetchBodyDataIdentifier? partial_header_identifier, FetchBodyDataIdentifier? body_identifier,
FetchBodyDataIdentifier? preview_identifier, FetchBodyDataIdentifier? preview_charset_identifier)
throws Error {
Geary.Email email = new Geary.Email(fetched_data.msg_num.value,
Geary.Email email = new Geary.Email(fetched_data.seq_num.value,
new Imap.EmailIdentifier(uid, path));
// accumulate these to submit Imap.EmailProperties all at once

View file

@ -1,83 +0,0 @@
/* Copyright 2013 Yorba Foundation
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
public class Geary.Imap.ServerDataNotifier : Object {
//
// ServerData (always untagged)
//
public virtual signal void capability(Capabilities capabilities) {
}
public virtual signal void exists(int count) {
}
public virtual signal void expunge(MessageNumber msg_num) {
}
public virtual signal void fetch(FetchedData fetched_data) {
}
public virtual signal void flags(MailboxAttributes mailbox_attrs) {
}
public virtual signal void list(MailboxInformation mailbox_info) {
}
// TODO: LSUB results
public virtual signal void recent(int count) {
}
// TODO: SEARCH results
// TODO: STATUS results
public ServerDataNotifier() {
}
public bool notify(ServerData server_data) throws ImapError {
switch (server_data.server_data_type) {
case ServerDataType.CAPABILITY:
capability(CapabilityDecoder.decode(server_data));
break;
case ServerDataType.EXISTS:
exists(ExistsDecoder.decode(server_data));
break;
case ServerDataType.EXPUNGE:
expunge(ExpungedDecoder.decode(server_data));
break;
case ServerDataType.FETCH:
fetch(FetchDecoder.decode(server_data));
break;
case ServerDataType.FLAGS:
flags(FlagsDecoder.decode(server_data));
break;
case ServerDataType.LIST:
list(ListDecoder.decode(server_data));
break;
case ServerDataType.RECENT:
recent(RecentDecoder.decode(server_data));
break;
// TODO: LSUB, SEARCH, and STATUS
case ServerDataType.STATUS:
case ServerDataType.LSUB:
case ServerDataType.SEARCH:
default:
return false;
}
return true;
}
}

View file

@ -4,11 +4,50 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
/**
* A representation of an IMAP command (request).
*
* A Command is created by the caller and then submitted to a {@link ClientSession} or
* {@link ClientConnection} for transmission to the server. In response, one or more
* {@link ServerResponse}s are returned, generally zero or more {@link ServerData}s followed by
* a {@link CompletionStatusResponse}. {@link ResponseCode}s, {@link StatusResponse}s, and/or
* {@link CodedStatusResponse}s may also be returned, depending on the Command.
*
* See [[http://tools.ietf.org/html/rfc3501#section-6]]
*/
public class Geary.Imap.Command : RootParameters {
/**
* All IMAP commands are tagged with an identifier assigned by the client.
*
* Note that this is not immutable. The general practice is to use an unassigned Tag
* up until the {@link Command} is about to be transmitted, at which point a Tag is
* assigned. This allows for all commands to be issued in Tag "order". This generally makes
* tracing network traffic easier.
*
* @see Tag.get_unassigned
* @see assign_tag
*/
public Tag tag { get; private set; }
/**
* The name (or "verb") of the {@link Command}.
*/
public string name { get; private set; }
/**
* Zero or more arguments for the {@link Command}.
*
* Note that some Commands have require args and others are optional. The format of the
* arguments ({@link StringParameter}, {@link ListParameter}, etc.) is sometimes crucial.
*/
public string[]? args { get; private set; }
/**
* Create a Command with an unassigned Tag.
*
* @see tag
*/
public Command(string name, string[]? args = null) {
tag = Tag.get_unassigned();
this.name = name;
@ -17,6 +56,11 @@ public class Geary.Imap.Command : RootParameters {
stock_params();
}
/**
* Create a Command with an assigned Tag.
*
* @see tag
*/
public Command.assigned(Tag tag, string name, string[]? args = null)
requires (tag.is_tagged() && tag.is_assigned()) {
this.tag = tag;
@ -36,6 +80,8 @@ public class Geary.Imap.Command : RootParameters {
}
/**
* Assign a {@link Tag} to a {@link Command} with an unassigned placeholder Tag.
*
* Can only be called on a Command that holds an unassigned Tag. Thus, this can only be called
* once at most, and zero times if Command.assigned() was used to generate the Command.
* Fires an assertion if either of these cases is true, or if the supplied Tag is unassigned.

View file

@ -4,6 +4,18 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
/**
* A representation of the IMAP FETCH command.
*
* FETCH is easily the most complicated IMAP command. It has a number of parameters, some of which
* have a number of variants, and the data that is returned requires involved decoding patterns.
*
* See [[http://tools.ietf.org/html/rfc3501#section-6.4.5]]
*
* @see FetchedData
* @see StoreCommand
*/
public class Geary.Imap.FetchCommand : Command {
public const string NAME = "fetch";
public const string UID_NAME = "uid fetch";

View file

@ -10,13 +10,14 @@
* See [[http://tools.ietf.org/html/rfc3501#section-7.4.2]]
*
* @see FetchCommand
* @see StoreCommand
*/
public class Geary.Imap.FetchedData : Object {
/**
* The positional address of the email in the mailbox.
*/
public SequenceNumber seq_num { get; private set; }
public MessageNumber msg_num { get; private set; }
/**
* A Map of {@link FetchDataType}s to their {@link Imap.MessageData} for this email.
*
@ -36,15 +37,24 @@ public class Geary.Imap.FetchedData : Object {
public Gee.Map<FetchBodyDataIdentifier, Memory.AbstractBuffer> body_data_map { get; private set;
default = new Gee.HashMap<FetchBodyDataIdentifier, Memory.AbstractBuffer>(); }
public FetchedData(MessageNumber msg_num) {
this.msg_num = msg_num;
public FetchedData(SequenceNumber seq_num) {
this.seq_num = seq_num;
}
/**
* Decodes {@link ServerData} into a FetchedData representation.
*
* The ServerData must be the response to a FETCH or STORE command.
*
* @see FetchCommand
* @see StoreCommand
* @see ServerData.get_fetch
*/
public static FetchedData decode(ServerData server_data) throws ImapError {
if (!server_data.get_as_string(2).equals_ci(FetchCommand.NAME))
throw new ImapError.PARSE_ERROR("Not FETCH data: %s", server_data.to_string());
FetchedData fetched_data = new FetchedData(new MessageNumber(server_data.get_as_string(1).as_int()));
FetchedData fetched_data = new FetchedData(new SequenceNumber(server_data.get_as_string(1).as_int()));
// walk the list for each returned fetch data item, which is paired by its data item name
// and the structured data itself
@ -94,13 +104,13 @@ public class Geary.Imap.FetchedData : Object {
*
* See warnings at {@link body_data_map} for dealing with multiple FetchBodyDataTypes.
*
* @return null if the FetchedData do not have the same {@link msg_num}.
* @return null if the FetchedData do not have the same {@link seq_num}.
*/
public FetchedData? combine(FetchedData other) {
if (!msg_num.equal_to(other.msg_num))
if (!seq_num.equal_to(other.seq_num))
return null;
FetchedData combined = new FetchedData(msg_num);
FetchedData combined = new FetchedData(seq_num);
Collection.map_set_all<FetchDataType, MessageData>(combined.data_map, data_map);
Collection.map_set_all<FetchDataType, MessageData>(combined.data_map, other.data_map);
Collection.map_set_all<FetchBodyDataIdentifier, Memory.AbstractBuffer>(combined.body_data_map,
@ -114,7 +124,7 @@ public class Geary.Imap.FetchedData : Object {
public string to_string() {
StringBuilder builder = new StringBuilder();
builder.append_printf("[%s] ", msg_num.to_string());
builder.append_printf("[%s] ", seq_num.to_string());
foreach (FetchDataType data_type in data_map.keys)
builder.append_printf("%s=%s ", data_type.to_string(), data_map.get(data_type).to_string());

View file

@ -4,9 +4,32 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
/**
* The decoded response to a LIST command.
*
* This is also the response to an XLIST command.
*
* See [[http://tools.ietf.org/html/rfc3501#section-7.2.2]]
*
* @see ListCommand
*/
public class Geary.Imap.MailboxInformation : Object {
/**
* The decoded mailbox name.
*
* See {@link MailboxParameter} for the encoded version of this string.
*/
public string name { get; private set; }
/**
* The (optional) delimiter specified by the server.
*/
public string? delim { get; private set; }
/**
* Folder attributes returned by the server.
*/
public MailboxAttributes attrs { get; private set; }
public MailboxInformation(string name, string? delim, MailboxAttributes attrs) {
@ -37,6 +60,8 @@ public class Geary.Imap.MailboxInformation : Object {
}
/**
* The mailbox's name without parent folders.
*
* If name is non-empty, will return a non-empty value which is the final folder name (i.e.
* the parent components are stripped). If no delimiter is specified, the name is returned.
*/
@ -53,11 +78,20 @@ public class Geary.Imap.MailboxInformation : Object {
return !String.is_empty(basename) ? basename : name;
}
/**
* Decodes {@link ServerData} into a MailboxInformation representation.
*
* The ServerData must be the response to a LIST or XLIST command.
*
* @see ListCommand
* @see ServerData.get_list
*/
public static MailboxInformation decode(ServerData server_data) throws ImapError {
StringParameter cmd = server_data.get_as_string(1);
if (!cmd.equals_ci(ListCommand.NAME) && !cmd.equals_ci(ListCommand.XLIST_NAME))
throw new ImapError.PARSE_ERROR("Not LIST or XLIST data: %s", server_data.to_string());
// Build list of attributes
ListParameter attrs = server_data.get_as_list(2);
Gee.Collection<MailboxAttribute> attrlist = new Gee.ArrayList<MailboxAttribute>();
foreach (Parameter attr in attrs.get_all()) {
@ -72,16 +106,18 @@ public class Geary.Imap.MailboxInformation : Object {
attrlist.add(new MailboxAttribute(stringp.value));
}
// decode everything
MailboxAttributes attributes = new MailboxAttributes(attrlist);
StringParameter? delim = server_data.get_as_nullable_string(3);
StringParameter mailbox = server_data.get_as_string(4);
MailboxParameter mailbox = new MailboxParameter.from_string_parameter(
server_data.get_as_string(4));
// Set \Inbox to standard path
MailboxAttributes attributes = new MailboxAttributes(attrlist);
if (Geary.Imap.MailboxAttribute.SPECIAL_FOLDER_INBOX in attributes) {
return new MailboxInformation(Geary.Imap.Account.INBOX_NAME,
(delim != null) ? delim.nullable_value : null, attributes);
} else {
return new MailboxInformation(mailbox.value,
return new MailboxInformation(mailbox.decode(),
(delim != null) ? delim.nullable_value : null, attributes);
}
}

View file

@ -5,12 +5,22 @@
*/
/**
* A StringParameter that holds a mailbox reference (can be wildcarded). Used
* to juggle between our internal UTF-8 representation of mailboxes and IMAP's
* A {@link StringParameter} that holds a mailbox reference (can be wildcarded).
*
* Used to juggle between our internal UTF-8 representation of mailboxes and IMAP's
* odd "modified UTF-7" representation. The value is stored in IMAP's encoded
* format since that's how it comes across the wire.
*/
public class Geary.Imap.MailboxParameter : StringParameter {
public MailboxParameter(string mailbox) {
base (utf8_to_imap_utf7(mailbox));
}
public MailboxParameter.from_string_parameter(StringParameter string_parameter) {
base (string_parameter.value);
}
private static string utf8_to_imap_utf7(string utf8) {
try {
return Geary.ImapUtf7.utf8_to_imap_utf7(utf8);
@ -29,15 +39,8 @@ public class Geary.Imap.MailboxParameter : StringParameter {
}
}
public MailboxParameter(string mailbox) {
base (utf8_to_imap_utf7(mailbox));
}
public MailboxParameter.from_string_parameter(StringParameter string_parameter) {
base (string_parameter.value);
}
public string decode() {
return imap_utf7_to_utf8(value);
}
}

View file

@ -6,9 +6,7 @@
/**
* MessageData is an IMAP data structure delivered in some form by the server to the client.
* Although the primary use of this object is for FETCH results, other commands return
* similarly-structured data (in particulars Flags and Attributes).
*
*
* Note that IMAP specifies that Flags and Attributes are *always* returned as a list, even if only
* one is present, which is why these elements are MessageData but not the elements within the
* lists (Flag, Attribute).
@ -19,91 +17,6 @@
public interface Geary.Imap.MessageData : Geary.MessageData.AbstractMessageData {
}
public class Geary.Imap.UID : Geary.MessageData.Int64MessageData, Geary.Imap.MessageData,
Gee.Comparable<Geary.Imap.UID> {
// Using statics because int32.MAX is static, not const (??)
public static int64 MIN = 1;
public static int64 MAX = int32.MAX;
public static int64 INVALID = -1;
public UID(int64 value) {
base (value);
}
public bool is_valid() {
return is_value_valid(value);
}
public static bool is_value_valid(int64 val) {
return Numeric.int64_in_range_inclusive(val, MIN, MAX);
}
/**
* Returns a valid UID, which means returning MIN or MAX if the value is out of range (either
* direction) or MAX if this value is already MAX.
*/
public UID next() {
if (value < MIN)
return new UID(MIN);
else if (value > MAX)
return new UID(MAX);
else
return new UID(Numeric.int64_ceiling(value + 1, MAX));
}
/**
* Returns a valid UID, which means returning MIN or MAX if the value is out of range (either
* direction) or MIN if this value is already MIN.
*/
public UID previous() {
if (value < MIN)
return new UID(MIN);
else if (value > MAX)
return new UID(MAX);
else
return new UID(Numeric.int64_floor(value - 1, MIN));
}
public virtual int compare_to(Geary.Imap.UID other) {
if (value < other.value)
return -1;
else if (value > other.value)
return 1;
else
return 0;
}
public string serialize() {
return value.to_string();
}
}
public class Geary.Imap.UIDValidity : Geary.MessageData.Int64MessageData, Geary.Imap.MessageData {
// Using statics because int32.MAX is static, not const (??)
public static int64 MIN = 1;
public static int64 MAX = int32.MAX;
public static int64 INVALID = -1;
public UIDValidity(int64 value) {
base (value);
}
}
public class Geary.Imap.MessageNumber : Geary.MessageData.IntMessageData, Geary.Imap.MessageData,
Gee.Comparable<MessageNumber> {
public MessageNumber(int value) {
base (value);
}
public virtual int compare_to(MessageNumber other) {
return value - other.value;
}
public string serialize() {
return value.to_string();
}
}
public abstract class Geary.Imap.Flags : Geary.MessageData.AbstractMessageData, Geary.Imap.MessageData,
Gee.Hashable<Geary.Imap.Flags> {
public int size { get { return list.size; } }

View file

@ -6,15 +6,29 @@
extern void qsort(void *base, size_t num, size_t size, CompareFunc compare_func);
/**
* A represenation of an IMAP message range specifier.
*
* A MessageSet can be for {@link SequenceNumber}s (which use positional addressing) or
* {@link UID}s.
*
* See [[http://tools.ietf.org/html/rfc3501#section-9]], "sequence-set" and "seq-range".
*/
public class Geary.Imap.MessageSet : BaseObject {
/**
* True if the {@link MessageSet} was created with a UID or a UID range.
*
* For {@link Command}s that accept MessageSets, they will use a UID variant
*/
public bool is_uid { get; private set; default = false; }
private string value { get; private set; }
public MessageSet(int msg_num) {
assert(msg_num > 0);
public MessageSet(SequenceNumber seq_num) {
assert(seq_num.value > 0);
value = "%d".printf(msg_num);
value = seq_num.serialize();
}
public MessageSet.uid(UID uid) {
@ -28,29 +42,29 @@ public class Geary.Imap.MessageSet : BaseObject {
MessageSet.uid(((Geary.Imap.EmailIdentifier) email_id).uid);
}
public MessageSet.range_by_count(int low_msg_num, int count) {
assert(low_msg_num > 0);
public MessageSet.range_by_count(SequenceNumber low_seq_num, int count) {
assert(low_seq_num.value > 0);
assert(count > 0);
value = (count > 1)
? "%d:%d".printf(low_msg_num, low_msg_num + count - 1)
: "%d".printf(low_msg_num);
? "%d:%d".printf(low_seq_num.value, low_seq_num.value + count - 1)
: low_seq_num.serialize();
}
public MessageSet.range_by_first_last(int low_msg_num, int high_msg_num) {
assert(low_msg_num > 0);
assert(high_msg_num > 0);
public MessageSet.range_by_first_last(SequenceNumber low_seq_num, SequenceNumber high_seq_num) {
assert(low_seq_num.value > 0);
assert(high_seq_num.value > 0);
// correct range problems (i.e. last before first)
if (low_msg_num > high_msg_num) {
int swap = low_msg_num;
low_msg_num = high_msg_num;
high_msg_num = swap;
if (low_seq_num.value > high_seq_num.value) {
SequenceNumber swap = low_seq_num;
low_seq_num = high_seq_num;
high_seq_num = swap;
}
value = (low_msg_num != high_msg_num)
? "%d:%d".printf(low_msg_num, high_msg_num)
: "%d".printf(low_msg_num);
value = (!low_seq_num.equal_to(high_seq_num))
? "%s:%s".printf(low_seq_num.serialize(), high_seq_num.serialize())
: low_seq_num.serialize();
}
public MessageSet.uid_range(UID low, UID high) {
@ -65,10 +79,10 @@ public class Geary.Imap.MessageSet : BaseObject {
is_uid = true;
}
public MessageSet.range_to_highest(int low_msg_num) {
assert(low_msg_num > 0);
public MessageSet.range_to_highest(SequenceNumber low_seq_num) {
assert(low_seq_num.value > 0);
value = "%d:*".printf(low_msg_num);
value = "%s:*".printf(low_seq_num.serialize());
}
/**
@ -108,8 +122,8 @@ public class Geary.Imap.MessageSet : BaseObject {
is_uid = true;
}
public MessageSet.sparse(int[] msg_nums) {
value = build_sparse_range(msg_array_to_int64(msg_nums));
public MessageSet.sparse(SequenceNumber[] seq_nums) {
value = build_sparse_range(seq_array_to_int64(seq_nums));
}
public MessageSet.uid_sparse(UID[] msg_uids) {
@ -124,8 +138,8 @@ public class Geary.Imap.MessageSet : BaseObject {
is_uid = true;
}
public MessageSet.sparse_to_highest(int[] msg_nums) {
value = "%s:*".printf(build_sparse_range(msg_array_to_int64(msg_nums)));
public MessageSet.sparse_to_highest(SequenceNumber[] seq_nums) {
value = "%s:*".printf(build_sparse_range(seq_array_to_int64(seq_nums)));
}
public MessageSet.multirange(MessageSet[] msg_sets) {
@ -168,29 +182,29 @@ public class Geary.Imap.MessageSet : BaseObject {
// Builds sparse range of either UID values or message numbers.
// NOTE: This method assumes the supplied array is internally allocated, and so an in-place sort
// is allowable
private static string build_sparse_range(int64[] msg_nums) {
assert(msg_nums.length > 0);
private static string build_sparse_range(int64[] seq_nums) {
assert(seq_nums.length > 0);
// sort array to search for spans
qsort(msg_nums, msg_nums.length, sizeof(int64), Numeric.int64_compare);
qsort(seq_nums, seq_nums.length, sizeof(int64), Numeric.int64_compare);
int64 start_of_span = -1;
int64 last_msg_num = -1;
int64 last_seq_num = -1;
int span_count = 0;
StringBuilder builder = new StringBuilder();
foreach (int64 msg_num in msg_nums) {
assert(msg_num >= 0);
foreach (int64 seq_num in seq_nums) {
assert(seq_num >= 0);
// the first number is automatically the start of a span, although it may be a span of one
// (start_of_span < 0 should only happen on first iteration; can't easily break out of
// loop because foreach/Iterator would still require a special case to skip it)
if (start_of_span < 0) {
// start of first span
builder.append(msg_num.to_string());
builder.append(seq_num.to_string());
start_of_span = msg_num;
start_of_span = seq_num;
span_count = 1;
} else if ((start_of_span + span_count) == msg_num) {
} else if ((start_of_span + span_count) == seq_num) {
// span continues
span_count++;
} else {
@ -198,40 +212,40 @@ public class Geary.Imap.MessageSet : BaseObject {
// span ends, another begins
if (span_count == 1) {
builder.append_printf(",%s", msg_num.to_string());
builder.append_printf(",%s", seq_num.to_string());
} else if (span_count == 2) {
builder.append_printf(",%s,%s", (start_of_span + 1).to_string(),
msg_num.to_string());
seq_num.to_string());
} else {
builder.append_printf(":%s,%s", (start_of_span + span_count - 1).to_string(),
msg_num.to_string());
seq_num.to_string());
}
start_of_span = msg_num;
start_of_span = seq_num;
span_count = 1;
}
last_msg_num = msg_num;
last_seq_num = seq_num;
}
// there should always be one msg_num in sorted, so the loop should exit with some state
// there should always be one seq_num in sorted, so the loop should exit with some state
assert(start_of_span >= 0);
assert(span_count > 0);
assert(last_msg_num >= 0);
assert(last_seq_num >= 0);
// look for open-ended span
if (span_count == 2)
builder.append_printf(",%s", last_msg_num.to_string());
builder.append_printf(",%s", last_seq_num.to_string());
else
builder.append_printf(":%s", last_msg_num.to_string());
builder.append_printf(":%s", last_seq_num.to_string());
return builder.str;
}
private static int64[] msg_array_to_int64(int[] msg_nums) {
private static int64[] seq_array_to_int64(SequenceNumber[] seq_nums) {
int64[] ret = new int64[0];
foreach (int num in msg_nums)
ret += (int64) num;
foreach (SequenceNumber seq_num in seq_nums)
ret += (int64) seq_num.value;
return ret;
}
@ -252,6 +266,10 @@ public class Geary.Imap.MessageSet : BaseObject {
return ret;
}
/**
* Returns the {@link MessageSet} as a {@link Parameter} suitable for inclusion in a
* {@link Command}.
*/
public Parameter to_parameter() {
// Message sets are not quoted, even if they use an atom-special character (this *might*
// be a Gmailism...)

View file

@ -0,0 +1,40 @@
/* Copyright 2013 Yorba Foundation
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
/**
* A representation of IMAP's sequence number, i.e. positional addressing within a mailbox.
*
* See [[http://tools.ietf.org/html/rfc3501#section-2.3.1.2]]
*
* @see UID
*/
public class Geary.Imap.SequenceNumber : Geary.MessageData.IntMessageData, Geary.Imap.MessageData,
Gee.Comparable<SequenceNumber> {
public SequenceNumber(int value) {
base (value);
}
/**
* Converts an array of ints into an array of {@link SequenceNumber}s.
*/
public static SequenceNumber[] to_list(int[] value_array) {
SequenceNumber[] list = new SequenceNumber[0];
foreach (int value in value_array)
list += new SequenceNumber(value);
return list;
}
public virtual int compare_to(SequenceNumber other) {
return value - other.value;
}
public string serialize() {
return value.to_string();
}
}

View file

@ -4,6 +4,14 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
/**
* A representation of the types of data to be found in a STATUS response.
*
* See [[http://tools.ietf.org/html/rfc3501#section-7.2.4]]
*
* @see StatusData
*/
public enum Geary.Imap.StatusDataType {
MESSAGES,
RECENT,

View file

@ -4,6 +4,14 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
/**
* The decoded response to a STATUS command.
*
* See [[http://tools.ietf.org/html/rfc3501#section-7.2.4]]
*
* @see StatusCommand
*/
public class Geary.Imap.StatusData : Object {
// NOTE: This must be negative one; other values won't work well due to how the values are
// decoded
@ -15,18 +23,33 @@ public class Geary.Imap.StatusData : Object {
* See {@link MailboxParameter} for the encoded version of this string.
*/
public string mailbox { get; private set; }
/**
* UNSET if not set.
* {@link UNSET} if not set.
*/
public int messages { get; private set; }
/**
* UNSET if not set.
* {@link UNSET} if not set.
*/
public int recent { get; private set; }
public UID? uid_next { get; private set; }
public UIDValidity? uid_validity { get; private set; }
/**
* UNSET if not set.
* The UIDNEXT of the mailbox, if returned.
*
* See [[http://tools.ietf.org/html/rfc3501#section-2.3.1.1]]
*/
public UID? uid_next { get; private set; }
/**
* The UIDVALIDITY of the mailbox, if returned.
*
* See [[http://tools.ietf.org/html/rfc3501#section-2.3.1.1]]
*/
public UIDValidity? uid_validity { get; private set; }
/**
* {@link UNSET} if not set.
*/
public int unseen { get; private set; }
@ -40,12 +63,23 @@ public class Geary.Imap.StatusData : Object {
this.unseen = unseen;
}
/**
* Decodes {@link ServerData} into a StatusData representation.
*
* The ServerData must be the response to a STATUS command.
*
* @see StatusCommand
* @see ServerData.get_status
*/
public static StatusData decode(ServerData server_data) throws ImapError {
if (!server_data.get_as_string(1).equals_ci(StatusCommand.NAME)) {
throw new ImapError.PARSE_ERROR("Bad STATUS command name in response \"%s\"",
server_data.to_string());
}
MailboxParameter mailbox_param = new MailboxParameter.from_string_parameter(
server_data.get_as_string(2));
int messages = UNSET;
int recent = UNSET;
UID? uid_next = null;
@ -92,8 +126,14 @@ public class Geary.Imap.StatusData : Object {
}
}
return new StatusData(server_data.get_as_string(2).value, messages, recent, uid_next,
uid_validity, unseen);
return new StatusData(mailbox_param.decode(), messages, recent, uid_next, uid_validity,
unseen);
}
public string to_string() {
return "%s/%d/UIDNEXT=%s/UIDVALIDITY=%s".printf(mailbox, messages,
(uid_next != null) ? uid_next.to_string() : "(none)",
(uid_validity != null) ? uid_validity.to_string() : "(none)");
}
}

View file

@ -4,6 +4,17 @@
* (version 2.1 or later). See the COPYING file in this distribution.
*/
/**
* A representation of an IMAP command tag.
*
* Tags are assigned by the client for each {@link Command} it sends to the server. Tags have
* a general form of <a-z><000-999>, although that's only by convention and is not required.
*
* Special tags exist, namely to indicated an untagged response and continuations.
*
* See [[http://tools.ietf.org/html/rfc3501#section-2.2.1]]
*/
public class Geary.Imap.Tag : StringParameter, Gee.Hashable<Geary.Imap.Tag> {
public const string UNTAGGED_VALUE = "*";
public const string CONTINUATION_VALUE = "+";

View file

@ -0,0 +1,25 @@
/* Copyright 2013 Yorba Foundation
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
/*
* A representation of IMAP's UIDVALIDITY.
*
* See [[tools.ietf.org/html/rfc3501#section-2.3.1.1]]
*
* @see UID
*/
public class Geary.Imap.UIDValidity : Geary.MessageData.Int64MessageData, Geary.Imap.MessageData {
// Using statics because int32.MAX is static, not const (??)
public static int64 MIN = 1;
public static int64 MAX = int32.MAX;
public static int64 INVALID = -1;
public UIDValidity(int64 value) {
base (value);
}
}

View file

@ -0,0 +1,73 @@
/* Copyright 2013 Yorba Foundation
*
* This software is licensed under the GNU Lesser General Public License
* (version 2.1 or later). See the COPYING file in this distribution.
*/
/**
* An IMAP UID.
*
* See [[tools.ietf.org/html/rfc3501#section-2.3.1.1]]
*
* @see SequenceNumber
*/
public class Geary.Imap.UID : Geary.MessageData.Int64MessageData, Geary.Imap.MessageData,
Gee.Comparable<Geary.Imap.UID> {
// Using statics because int32.MAX is static, not const (??)
public static int64 MIN = 1;
public static int64 MAX = int32.MAX;
public static int64 INVALID = -1;
public UID(int64 value) {
base (value);
}
public bool is_valid() {
return is_value_valid(value);
}
public static bool is_value_valid(int64 val) {
return Numeric.int64_in_range_inclusive(val, MIN, MAX);
}
/**
* Returns a valid UID, which means returning MIN or MAX if the value is out of range (either
* direction) or MAX if this value is already MAX.
*/
public UID next() {
if (value < MIN)
return new UID(MIN);
else if (value > MAX)
return new UID(MAX);
else
return new UID(Numeric.int64_ceiling(value + 1, MAX));
}
/**
* Returns a valid UID, which means returning MIN or MAX if the value is out of range (either
* direction) or MIN if this value is already MIN.
*/
public UID previous() {
if (value < MIN)
return new UID(MIN);
else if (value > MAX)
return new UID(MAX);
else
return new UID(Numeric.int64_floor(value - 1, MIN));
}
public virtual int compare_to(Geary.Imap.UID other) {
if (value < other.value)
return -1;
else if (value > other.value)
return 1;
else
return 0;
}
public string serialize() {
return value.to_string();
}
}

View file

@ -83,15 +83,15 @@ public class Geary.Imap.ServerData : ServerResponse {
}
/**
* Parses the {@link ServerData} into an expunged {@link MessageNumber}, if possible.
* Parses the {@link ServerData} into an expunged {@link SequenceNumber}, if possible.
*
* @throws ImapError.INVALID if not an expunged MessageNumber.
*/
public MessageNumber get_expunge() throws ImapError {
public SequenceNumber get_expunge() throws ImapError {
if (server_data_type != ServerDataType.EXPUNGE)
throw new ImapError.INVALID("Not EXPUNGE data: %s", to_string());
return new MessageNumber(get_as_string(1).as_int());
return new SequenceNumber(get_as_string(1).as_int());
}
/**

View file

@ -202,7 +202,7 @@ public class Geary.Imap.ClientSession : BaseObject {
public signal void exists(int count);
public signal void expunge(MessageNumber msg_num);
public signal void expunge(SequenceNumber seq_num);
public signal void fetch(FetchedData fetched_data);
@ -1304,9 +1304,6 @@ public class Geary.Imap.ClientSession : BaseObject {
assert(completion_response != null);
if (completion_response.status != Status.OK)
throw new ImapError.SERVER_ERROR("Command %s failed: %s", cmd.name, completion_response.to_string());
return completion_response;
}
@ -1387,8 +1384,7 @@ public class Geary.Imap.ClientSession : BaseObject {
// update ClientSession capabilities before firing signal, so external signal
// handlers that refer back to property aren't surprised
capabilities = server_data.get_capabilities(ref next_capabilities_revision);
debug("[%s] #%d %s", to_string(), next_capabilities_revision,
capabilities.to_string());
debug("[%s] %s", to_string(), capabilities.to_string());
capability(capabilities);
break;

View file

@ -6,13 +6,15 @@
/**
* The Deserializer performs asynchronous I/O on a supplied input stream and transforms the raw
* bytes into IMAP Parameters (which can then be converted into ServerResponses or ServerData).
* The Deserializer will only begin reading from the stream when start_async() is called. Calling
* stop_async() will halt reading without closing the stream itself. A Deserializer may not be
* reused once stop_async() has been invoked.
* bytes into IMAP {@link Parameter}s (which can then be converted into {@link ServerResponse}s or
* {@link ServerData}).
*
* The Deserializer will only begin reading from the stream when {@link start_async} is called.
* Calling {@link stop_async} will halt reading without closing the stream itself. A Deserializer
* may not be reused once stop_async has been invoked.
*
* Since all results from the Deserializer are reported via signals, those signals should be
* connected to prior to calling start_async(), or the caller risks missing early messages. (Note
* connected to prior to calling start_async, or the caller risks missing early messages. (Note
* that since Deserializer uses async I/O, this isn't technically possible unless the signals are
* connected after the Idle loop has a chance to run; however, this is an implementation detail and
* shouldn't be relied upon.)
@ -81,25 +83,34 @@ public class Geary.Imap.Deserializer : BaseObject {
private char[] atom_specials_exceptions = { ' ', ' ', '\0' };
/**
* Fired when a complete set of Parameters have been received. Note that RootParameters may
* contain StringParameters, ListParameters, NilParameters, and so on. One special Parameter
* decoded by Deserializer is ResponseCode, which is structured internally as a ListParameter
* subclass for convenience when decoding.
* Fired when a complete set of IMAP {@link Parameter}s have been received.
*
* Note that {@link RootParameters} may contain {@link StringParameter}s, {@link ListParameter}s,
* {@link NilParameter}s, and so on. One special Parameter decoded by Deserializer is
* {@link ResponseCode}, which is structured internally as a ListParameter subclass for
* convenience when decoding and can be deduced at the syntax level.
*/
public signal void parameters_ready(RootParameters root);
/**
* "eos" is fired when the underlying InputStream is closed, whether due to normal EOS or input
* error. Subscribe to "receive-failure" to be notified of errors.
* Fired when the underlying InputStream is closed, whether due to normal EOS or input error.
*
* @see receive_failure
*/
public signal void eos();
/**
* Fired when an Error is trapped on the input stream.
*
* This is nonrecoverable and means the stream should be closed and this Deserializer destroyed.
*/
public signal void receive_failure(Error err);
/**
* "data-received" is fired as data blocks are received during download. The bytes themselves
* may be partial and unusable out of context, so they're not provided, but their size is, to allow
* monitoring of speed and such.
* Fired as data blocks are received during download.
*
* The bytes themselves may be partial and unusable out of context, so they're not provided,
* but their size is, to allow monitoring of speed and such.
*
* Note that this is fired for both line data (i.e. responses, status, etc.) and literal data
* (block transfers).
@ -109,6 +120,12 @@ public class Geary.Imap.Deserializer : BaseObject {
*/
public signal void bytes_received(size_t bytes);
/**
* Fired when a syntax error has occurred.
*
* This generally means the data looks like garbage and further deserialization is unlikely
* or impossible.
*/
public signal void deserialize_failure();
public Deserializer(InputStream ins) {
@ -174,10 +191,20 @@ public class Geary.Imap.Deserializer : BaseObject {
fsm = new Geary.State.Machine(machine_desc, mappings, on_bad_transition);
}
/**
* Install a custom Converter into the input stream.
*
* Can be used for decompression, decryption, and so on.
*/
public bool install_converter(Converter converter) {
return midstream.install(converter);
}
/**
* Begin deserializing IMAP responses from the input stream.
*
* Subscribe to the various signals before starting to ensure that all responses are trapped.
*/
public async void start_async(int priority = GLib.Priority.DEFAULT) throws Error {
if (cancellable != null)
throw new EngineError.ALREADY_OPEN("Deserializer already open");