Squashed commit of many patches that merged Eric's outbox patch as well as additional changes to upgrade the database rather than require it be wiped and some refactoring suggested by the Outbox implementation. Also updated Outbox to be fully atomic via Transactions.
524 lines
25 KiB
Vala
524 lines
25 KiB
Vala
/* Copyright 2011-2012 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 delegate void Geary.EmailCallback(Gee.List<Geary.Email>? emails, Error? err);
|
|
|
|
public interface Geary.Folder : Object {
|
|
public enum OpenState {
|
|
CLOSED,
|
|
OPENING,
|
|
REMOTE,
|
|
LOCAL,
|
|
BOTH
|
|
}
|
|
|
|
public enum OpenFailed {
|
|
LOCAL_FAILED,
|
|
REMOTE_FAILED
|
|
}
|
|
|
|
/**
|
|
* The "closed" signal will be fired multiple times after a Folder is opened. It is fired
|
|
* after the remote and local sessions close for various reasons, and fires once and only
|
|
* once when the folder is completely closed.
|
|
*
|
|
* LOCAL_CLOSE or LOCAL_ERROR is only called once, depending on the situation determining the
|
|
* value. The same is true for REMOTE_CLOSE and REMOTE_ERROR. A REMOTE_ERROR can trigger
|
|
* a LOCAL_CLOSE and vice-versa. The values may be called in any order.
|
|
*
|
|
* When the local and remote stores have closed (either normally or due to errors, FOLDER_CLOSED
|
|
* will be sent.
|
|
*/
|
|
public enum CloseReason {
|
|
LOCAL_CLOSE,
|
|
LOCAL_ERROR,
|
|
REMOTE_CLOSE,
|
|
REMOTE_ERROR,
|
|
FOLDER_CLOSED;
|
|
|
|
public bool is_error() {
|
|
return (this == LOCAL_ERROR) || (this == REMOTE_ERROR);
|
|
}
|
|
}
|
|
|
|
public enum CountChangeReason {
|
|
ADDED,
|
|
REMOVED
|
|
}
|
|
|
|
/**
|
|
* Flags used for retrieving email.
|
|
* LOCAL_ONLY: fetch from the local store only
|
|
* FORCE_UPDATE: fetch from remote only (results merged into local store)
|
|
* EXCLUDING_ID: exclude the provided ID
|
|
*/
|
|
[Flags]
|
|
public enum ListFlags {
|
|
NONE = 0,
|
|
LOCAL_ONLY,
|
|
FORCE_UPDATE,
|
|
EXCLUDING_ID;
|
|
|
|
public bool is_any_set(ListFlags flags) {
|
|
return (this & flags) != 0;
|
|
}
|
|
|
|
public bool is_all_set(ListFlags flags) {
|
|
return (this & flags) == flags;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This is fired when the Folder is successfully opened by a caller. It will only fire once
|
|
* until the Folder is closed, with the OpenState indicating what has been opened and the count
|
|
* indicating the number of messages in the folder (in the case of OpenState.BOTH or
|
|
* OpenState.REMOTE, it refers to the authoritative number).
|
|
*
|
|
* In general, OpenState.REMOTE won't fire as that indicates an underlying error in the local
|
|
* store, which is problematic for synchronization.
|
|
*
|
|
* This signal will never fire with Geary.OpenState.CLOSED as a parameter.
|
|
*/
|
|
public signal void opened(OpenState state, int count);
|
|
|
|
/**
|
|
* This is fired when open_async() fails for one or more reasons.
|
|
*
|
|
* See open_async() and "opened" for more information on how opening a Folder works, in particular
|
|
* how open_async() may return immediately although the remote has not completely opened.
|
|
* This signal may be called in the context of, or after completion of, open_async(). It will
|
|
* *not* be called after close_async() has completed, however.
|
|
*
|
|
* Note that this signal may be fired *and* open_async() throw an Error.
|
|
*
|
|
* This signal may be fired more than once before the Folder is closed. It will only fire once
|
|
* for each type of failure, however.
|
|
*/
|
|
public signal void open_failed(OpenFailed failure, Error? err);
|
|
|
|
/**
|
|
* This is fired when the Folder is closed, either by the caller or due to errors in the local
|
|
* or remote store(s). It will fire three times: to report how the local store closed
|
|
* (gracefully or due to error), how the remote closed (similarly) and finally with
|
|
* FOLDER_CLOSED. The first two may come in either order; the third is always the last.
|
|
*/
|
|
public signal void closed(CloseReason reason);
|
|
|
|
/**
|
|
* "email-appended" is fired when new messages have been appended to the list of messages in
|
|
* the folder (and therefore old message position numbers remain valid, but the total count of
|
|
* the messages in the folder has changed).
|
|
*/
|
|
public signal void email_appended(Gee.Collection<Geary.EmailIdentifier> ids);
|
|
|
|
/**
|
|
* "email-locally-appended" is fired when previously unknown messages have been appended to the
|
|
* list of messages in the folder. This is similar to "email-appended", but that signal
|
|
* lists all messages appended to the folder. "email-locally-appended" only reports emails that
|
|
* have not been seen prior. Hence, an email that is removed from the folder and returned
|
|
* later will not be listed here (unless it was removed from the local store in the meantime).
|
|
*
|
|
* Note that these messages were appended as well, hence their positional addressing may have
|
|
* changed since last seen in this folder. However, it's important to realize that this list
|
|
* does *not* represent all newly appended messages.
|
|
*/
|
|
public signal void email_locally_appended(Gee.Collection<Geary.EmailIdentifier> ids);
|
|
|
|
/**
|
|
* "email-removed" is fired when a message has been removed (deleted or moved) from the
|
|
* folder (and therefore old message position numbers may no longer be valid, i.e. those after
|
|
* the removed message).
|
|
*
|
|
* NOTE: It's possible for the remote server to report a message has been removed that is not
|
|
* known locally (and therefore the caller could not have record of). If this happens, this
|
|
* signal will *not* fire, although "email-count-changed" will.
|
|
*/
|
|
public signal void email_removed(Gee.Collection<Geary.EmailIdentifier> ids);
|
|
|
|
/**
|
|
* "email-count-changed" is fired when the total count of email in a folder has changed in any way.
|
|
*
|
|
* Note that this signal will be fired alongside "messages-appended" or "message-removed".
|
|
* That is, do not use both signals to process email count changes; one will suffice.
|
|
* This signal will fire after those (although see the note at "messages-removed").
|
|
*/
|
|
public signal void email_count_changed(int new_count, CountChangeReason reason);
|
|
|
|
/**
|
|
* "email-flags-changed" is fired when an email's flag changed.
|
|
*
|
|
* This signal will be fired both when changes occur on the client side via the
|
|
* mark_email_async() method as well as changes occur remotely.
|
|
*/
|
|
public signal void email_flags_changed(Gee.Map<Geary.EmailIdentifier, Geary.EmailFlags> map);
|
|
|
|
protected abstract void notify_opened(OpenState state, int count);
|
|
|
|
protected abstract void notify_open_failed(OpenFailed failure, Error? err);
|
|
|
|
protected abstract void notify_closed(CloseReason reason);
|
|
|
|
protected abstract void notify_email_appended(Gee.Collection<Geary.EmailIdentifier> ids);
|
|
|
|
protected abstract void notify_email_locally_appended(Gee.Collection<Geary.EmailIdentifier> ids);
|
|
|
|
protected abstract void notify_email_removed(Gee.Collection<Geary.EmailIdentifier> ids);
|
|
|
|
protected abstract void notify_email_count_changed(int new_count, CountChangeReason reason);
|
|
|
|
protected abstract void notify_email_flags_changed(Gee.Map<Geary.EmailIdentifier,
|
|
Geary.EmailFlags> flag_map);
|
|
|
|
public abstract Geary.FolderPath get_path();
|
|
|
|
public abstract Geary.Trillian has_children();
|
|
|
|
/**
|
|
* Returns the special folder type of the folder. If the the folder is not a special one then
|
|
* null is returned.
|
|
*/
|
|
public abstract Geary.SpecialFolderType? get_special_folder_type();
|
|
|
|
/**
|
|
* Returns the state of the Folder's connections to the local and remote stores.
|
|
*/
|
|
public abstract OpenState get_open_state();
|
|
|
|
/**
|
|
* The Folder must be opened before most operations may be performed on it. Depending on the
|
|
* implementation this might entail opening a network connection or setting the connection to
|
|
* a particular state, opening a file or database, and so on.
|
|
*
|
|
* In the case of a Folder that is aggregating the contents of synchronized folder, it's possible
|
|
* for this method to complete even though all internal opens haven't completed. The "opened"
|
|
* signal is the final say on when a Folder is fully opened with its OpenState parameter
|
|
* indicating how open it really is. In general, a Folder's local store will open immediately
|
|
* while it may take time (if ever) for the remote state to open. Thus, it's possible for
|
|
* the "opened" signal to fire some time *after* this method completes.
|
|
*
|
|
* However, even if the method returns before the Folder's OpenState is BOTH, this Folder is
|
|
* ready for operation if this method returns without error. The messages the folder returns
|
|
* may not reflect the full state of the Folder, however, and returned emails may subsequently
|
|
* have their state changed (such as their EmailLocation). Making a call that requires
|
|
* accessing the remote store before OpenState.BOTH has been signalled will result in that
|
|
* call blocking until the remote is open or an error state has occurred.
|
|
*
|
|
* If there's an error while opening, "open-failed" will be fired. (See that signal for more
|
|
* information on how many times it may fire, and when.) To prevent the Folder from going into
|
|
* a halfway state, it will immediately schedule a close_async() to cleanup, and those
|
|
* associated signals will be fired as well.
|
|
*
|
|
* If the Folder has been opened previously, EngineError.ALREADY_OPEN is thrown. There are no
|
|
* other side-effects.
|
|
*
|
|
* A Folder may be reopened after it has been closed. This allows for Folder objects to be
|
|
* emitted by the Account object cheaply, but the client should only have a few open at a time,
|
|
* as each may represent an expensive resource (such as a network connection).
|
|
*/
|
|
public abstract async void open_async(bool readonly, Cancellable? cancellable = null) throws Error;
|
|
|
|
/**
|
|
* The Folder should be closed when operations on it are concluded. Depending on the
|
|
* implementation this might entail closing a network connection or reverting it to another
|
|
* state, or closing file handles or database connections.
|
|
*
|
|
* If the Folder is already closed, the method silently returns.
|
|
*/
|
|
public abstract async void close_async(Cancellable? cancellable = null) throws Error;
|
|
|
|
/**
|
|
* Returns the number of messages in the Folder. They can be addressed by their position,
|
|
* from 1 to n.
|
|
*
|
|
* Note that this only returns the number of messages available to the backing medium. In the
|
|
* case of the local store, this might differ from the number on the network server. Folders
|
|
* created by Engine are aggregating objects and will return the true count. However, this
|
|
* might require a round-trip to the server.
|
|
*
|
|
* Also note that local folders may be sparsely populated. get_email_count_async() returns the
|
|
* total number of recorded emails, but it's possible none of them have more than placeholder
|
|
* information.
|
|
*
|
|
* The Folder must be opened prior to attempting this operation.
|
|
*/
|
|
public abstract async int get_email_count_async(Cancellable? cancellable = null) throws Error;
|
|
|
|
/**
|
|
* If the Folder object detects that the supplied Email does not have sufficient fields for
|
|
* writing it, it should throw an EngineError.INCOMPLETE_MESSAGE. Use
|
|
* get_required_fields_for_writing() to determine which fields must be present to create the
|
|
* email.
|
|
*
|
|
* If the Folder supports duplicate detection, it may merge in additional fields from this Email
|
|
* and associate the revised Email with this Folder. See LocalFolder for specific calls that
|
|
* deal with this. Callers from outside the Engine don't need to worry about this; it's taken
|
|
* care of under the covers.
|
|
*
|
|
* Returns true if the email was created in the folder, false if it was merged.
|
|
*
|
|
* The Folder must be opened prior to attempting this operation.
|
|
*/
|
|
public abstract async bool create_email_async(Geary.RFC822.Message rfc822, Cancellable? cancellable = null)
|
|
throws Error;
|
|
|
|
/**
|
|
* Returns a list of messages that fulfill the required_fields flags starting at the low
|
|
* position and moving up to (low + count). If count is -1, the returned list starts at low
|
|
* and proceeds to all available emails. If low is -1, the *last* (most recent) 'count' emails
|
|
* are returned. If both low and count are -1, it's no different than calling with low as
|
|
* 1 and count -1, that is, all emails are returned. (See normalize_span_specifiers() for
|
|
* a utility function that handles all aspects of these requirements.)
|
|
*
|
|
* The returned list is not guaranteed to be in any particular order. The position index
|
|
* (starting from low) *is* ordered, however, from oldest to newest (in terms of receipt by the
|
|
* SMTP server, not necessarily the Sent: field), so if the caller wants the latest emails,
|
|
* they should calculate low by subtracting from get_email_count() or set low to -1 and use
|
|
* count to fetch the last n emails.
|
|
*
|
|
* If any position in low to (low + count) are out of range, only the email within range are
|
|
* reported. No error is thrown. This allows callers to blindly request the first or last n
|
|
* emails in a folder without determining the count first.
|
|
*
|
|
* Note that this only returns the emails with the required fields that are available to the
|
|
* Folder's backing medium. The local store may have fewer or incomplete messages, meaning that
|
|
* this will return an incomplete list. It is up to the caller to determine what's missing
|
|
* and take the appropriate steps.
|
|
*
|
|
* In the case of a Folder returned by Engine, it will use what's available in the local store
|
|
* and fetch from the network only what it needs, so that the caller gets a full list.
|
|
* Note that this means the call may require a round-trip to the server.
|
|
*
|
|
* If the caller would prefer the Folder return emails it has immediately available rather than
|
|
* make an expensive I/O call to "properly" fetch the emails, it should pass ListFlags.FAST.
|
|
* However, this also means avoiding a full synchronization, so it's possible the fetched
|
|
* emails do not correspond to what's actually available on the server.
|
|
* The best use of this method is to quickly retrieve a block of email for display or processing
|
|
* purposes, immediately followed by a non-fast list operation and then merging the two results.
|
|
*
|
|
* The Folder must be opened prior to attempting this operation.
|
|
*
|
|
* low is one-based, unless -1 is specified, as explained above.
|
|
*/
|
|
public abstract async Gee.List<Geary.Email>? list_email_async(int low, int count,
|
|
Geary.Email.Field required_fields, ListFlags flags, Cancellable? cancellable = null)
|
|
throws Error;
|
|
|
|
/**
|
|
* Similar in contract to list_email_async(), however instead of the emails being returned all
|
|
* at once at completion time, the emails are delivered to the caller in chunks via the
|
|
* EmailCallback. The method indicates when all the message have been fetched by passing a null
|
|
* for the first parameter. If an Error occurs while processing, it will be passed as the
|
|
* second parameter. There's no guarantess of the order the messages will be delivered to the
|
|
* caller.
|
|
*
|
|
* The Folder must be opened prior to attempting this operation.
|
|
*/
|
|
public abstract void lazy_list_email(int low, int count, Geary.Email.Field required_fields,
|
|
ListFlags flags, EmailCallback cb, Cancellable? cancellable = null);
|
|
|
|
/**
|
|
* Similar in contract to list_email_async(), but uses Geary.EmailIdentifier rather than
|
|
* positional addressing. This allows for a batch of messages to be listed from a starting
|
|
* identifier, going up and down the stack depending on the count parameter.
|
|
*
|
|
* The count parameter is exclusive of the Email at initial_id. That is, if count is one,
|
|
* two Emails may be returned: the one for initial_id and the next one. If count is zero,
|
|
* only the Email with the specified initial_id will be listed, making this method operate
|
|
* like fetch_email_async().
|
|
*
|
|
* If count is positive, initial_id is the *lowest* identifier and the returned list is going
|
|
* up the stack (toward the most recently added). If the count is negative, initial_id is
|
|
* the *highest* identifier and the returned list is going down the stack (toward the earliest
|
|
* added).
|
|
*
|
|
* To fetch all available messages in one direction or another, use int.MIN or int.MAX.
|
|
*
|
|
* initial_id *must* be an EmailIdentifier available to the Folder for this to work, as listing
|
|
* a range inevitably requires positional addressing under the covers. However, since it's
|
|
* some times desirable to list messages excluding the specified EmailIdentifier, callers may
|
|
* use ListFlags.EXCLUDING_ID (which is a flag only recognized by this method and
|
|
* lazy_list_email_by_id()). If the count is zero or one (or the number of messages remaining
|
|
* on the stack from the initial ID's position is zero or one) *and* this flag is set, no
|
|
* messages will be returned.
|
|
*
|
|
* There's no guarantee of the returned messages' order.
|
|
*
|
|
* There is (currently) no sparse version of list_email_by_id_async().
|
|
*
|
|
* The Folder must be opened prior to attempting this operation.
|
|
*/
|
|
public abstract async Gee.List<Geary.Email>? list_email_by_id_async(Geary.EmailIdentifier initial_id,
|
|
int count, Geary.Email.Field required_fields, ListFlags flags, Cancellable? cancellable = null)
|
|
throws Error;
|
|
|
|
/**
|
|
* Similar in contract to lazy_list_email_async(), but uses Geary.EmailIdentifier rather than
|
|
* positional addressing, much like list_email_by_id_async(). See that method for more
|
|
* information on its contract and how the count and flags parameters work.
|
|
*
|
|
* Like the other "lazy" methods, this method will call EmailCallback while the operation is
|
|
* processing. This method does not block.
|
|
*
|
|
* The Folder must be opened prior to attempting this operation.
|
|
*/
|
|
public abstract void lazy_list_email_by_id(Geary.EmailIdentifier initial_id, int count,
|
|
Geary.Email.Field required_fields, ListFlags flags, EmailCallback cb,
|
|
Cancellable? cancellable = null);
|
|
|
|
/**
|
|
* Similar in contract to list_email_async(), but uses a list of Geary.EmailIdentifiers rather
|
|
* than positional addressing, much like list_email_by_id_async(). See that method for more
|
|
* information on its contract and how the flags parameter works.
|
|
*
|
|
* Any Gee.Collection is accepted for EmailIdentifiers, but the returned list will only contain
|
|
* one email for each requested; duplicates are ignored. ListFlags.EXCLUDING_ID is ignored
|
|
* for this call and lazy_list_email_by_sparse_id().
|
|
*
|
|
* The Folder must be opened prior to attempting this operation.
|
|
*/
|
|
public abstract async Gee.List<Geary.Email>? list_email_by_sparse_id_async(
|
|
Gee.Collection<Geary.EmailIdentifier> ids, Geary.Email.Field required_fields, ListFlags flags,
|
|
Cancellable? cancellable = null) throws Error;
|
|
|
|
/**
|
|
* Similar in contract to lazy_list_email(), but uses a list of Geary.EmailIdentifiers rather
|
|
* than positional addressing. See list_email_by_id_async() and list_email_by_sparse_id_async()
|
|
* for more information on their contracts and how the flags and callback parameter works.
|
|
*
|
|
* Like the other "lazy" methods, this method will call EmailCallback while the operation is
|
|
* processing. This method does not block.
|
|
*
|
|
* The Folder must be opened prior to attempting this operation.
|
|
*/
|
|
public abstract void lazy_list_email_by_sparse_id(Gee.Collection<Geary.EmailIdentifier> ids,
|
|
Geary.Email.Field required_fields, ListFlags flags, EmailCallback cb,
|
|
Cancellable? cancellable = null);
|
|
|
|
/**
|
|
* Returns the locally available Geary.Email.Field fields for the specified emails. If a
|
|
* list or fetch operation occurs on the emails that specifies a field not returned here,
|
|
* the Engine will either have to go out to the remote server to get it, or (if
|
|
* ListFlags.LOCAL_ONLY is specified) not return it to the caller.
|
|
*
|
|
* If the EmailIdentifier is unknown locally, it will not be present in the returned Map.
|
|
*
|
|
* The Folder must be opened prior to attempting this operation.
|
|
*/
|
|
public abstract async Gee.Map<Geary.EmailIdentifier, Geary.Email.Field>? list_local_email_fields_async(
|
|
Gee.Collection<Geary.EmailIdentifier> ids, Cancellable? cancellable = null) throws Error;
|
|
|
|
/**
|
|
* Returns a single email that fulfills the required_fields flag at the ordered position in
|
|
* the folder. If the email_id is invalid for the folder's contents, an EngineError.NOT_FOUND
|
|
* error is thrown. If the requested fields are not available, EngineError.INCOMPLETE_MESSAGE
|
|
* is thrown.
|
|
*
|
|
* Because fetch_email_async() is a form of listing (listing exactly one email), it takes
|
|
* ListFlags as a parameter. See list_email_async() for more information. Note that one
|
|
* flag (ListFlags.EXCLUDING_ID) makes no sense in this context.
|
|
*
|
|
* The Folder must be opened prior to attempting this operation.
|
|
*/
|
|
public abstract async Geary.Email fetch_email_async(Geary.EmailIdentifier email_id,
|
|
Geary.Email.Field required_fields, ListFlags flags, Cancellable? cancellable = null) throws Error;
|
|
|
|
/**
|
|
* Removes the specified emails from the folder.
|
|
*
|
|
* The Folder must be opened prior to attempting this operation.
|
|
*/
|
|
public abstract async void remove_email_async(Gee.List<Geary.EmailIdentifier> email_ids,
|
|
Cancellable? cancellable = null) throws Error;
|
|
|
|
/**
|
|
* Removes one email from the folder.
|
|
*
|
|
* The Folder must be opened prior to attempting this operation.
|
|
*/
|
|
public abstract async void remove_single_email_async(Geary.EmailIdentifier email_id,
|
|
Cancellable? cancellable = null) throws Error;
|
|
|
|
/**
|
|
* Adds or removes a flag from a list of messages.
|
|
*
|
|
* The Folder must be opened prior to attempting this operation.
|
|
*/
|
|
public abstract async void mark_email_async(Gee.List<Geary.EmailIdentifier> to_mark,
|
|
Geary.EmailFlags? flags_to_add, Geary.EmailFlags? flags_to_remove,
|
|
Cancellable? cancellable = null) throws Error;
|
|
|
|
/**
|
|
* Copies messages into another folder.
|
|
*
|
|
* The Folder must be opened prior to attempting this operation.
|
|
*/
|
|
public abstract async void copy_email_async(Gee.List<Geary.EmailIdentifier> to_copy,
|
|
Geary.FolderPath destination, Cancellable? cancellable = null) throws Error;
|
|
|
|
/**
|
|
* Moves messages to another folder.
|
|
*
|
|
* The Folder must be opened prior to attempting this operation.
|
|
*/
|
|
public abstract async void move_email_async(Gee.List<Geary.EmailIdentifier> to_move,
|
|
Geary.FolderPath destination, Cancellable? cancellable = null) throws Error;
|
|
|
|
/**
|
|
* check_span_specifiers() verifies that the span specifiers match the requirements set by
|
|
* list_email_async() and lazy_list_email_async(). If not, this method throws
|
|
* EngineError.BAD_PARAMETERS.
|
|
*/
|
|
protected static void check_span_specifiers(int low, int count) throws EngineError {
|
|
if ((low < 1 && low != -1) || (count < 0 && count != -1))
|
|
throw new EngineError.BAD_PARAMETERS("low=%d count=%d", low, count);
|
|
}
|
|
|
|
/**
|
|
* normalize_span_specifiers() deals with the varieties of span specifiers that can be passed
|
|
* to list_email_async() and lazy_list_email_async(). Note that this function is for
|
|
* implementations to convert 'low' and 'count' into positive values (1-based in the case of
|
|
* low) that are within an appropriate range.
|
|
*
|
|
* If total is zero, low and count will return as zero as well.
|
|
*
|
|
* The caller should plug in 'low' and 'count' passed from the user as well as the total
|
|
* number of emails available (i.e. the complete span is 1..total).
|
|
*/
|
|
internal static void normalize_span_specifiers(ref int low, ref int count, int total)
|
|
throws EngineError {
|
|
check_span_specifiers(low, count);
|
|
|
|
if (total < 0)
|
|
throw new EngineError.BAD_PARAMETERS("total=%d", total);
|
|
|
|
if (total == 0) {
|
|
low = 0;
|
|
count = 0;
|
|
|
|
return;
|
|
}
|
|
|
|
// if both are -1, it's no different than low=1 count=-1 (that is, return all email)
|
|
if (low == -1 && count == -1)
|
|
low = 1;
|
|
|
|
// if count is -1, it's like a globbed star (return everything starting at low)
|
|
if (count == -1)
|
|
count = total;
|
|
|
|
if (low == -1)
|
|
low = ((total - count) + 1).clamp(1, total);
|
|
|
|
if ((low + (count - 1)) > total)
|
|
count = ((total - low) + 1).clamp(1, total);
|
|
}
|
|
|
|
/**
|
|
* Used for debugging. Should not be used for user-visible labels.
|
|
*/
|
|
public abstract string to_string();
|
|
}
|
|
|