Closes #7119 Single folder properties

This commit is contained in:
Eric Gregory 2013-06-21 18:24:12 -07:00
parent dc26b19aac
commit 5806f1eae4
11 changed files with 142 additions and 50 deletions

View file

@ -14,6 +14,7 @@ engine/abstract/geary-abstract-folder.vala
engine/api/geary-account.vala
engine/api/geary-account-information.vala
engine/api/geary-aggregated-folder-properties.vala
engine/api/geary-attachment.vala
engine/api/geary-base-object.vala
engine/api/geary-composed-email.vala
@ -230,6 +231,7 @@ engine/util/util-imap-utf7.vala
engine/util/util-inet.vala
engine/util/util-memory.vala
engine/util/util-numeric.vala
engine/util/util-object.vala
engine/util/util-reference-semantics.vala
engine/util/util-scheduler.vala
engine/util/util-single-item.vala

View file

@ -9,24 +9,30 @@ public class FolderList.FolderEntry : Geary.BaseObject, Sidebar.Entry, Sidebar.I
Sidebar.SelectableEntry, Sidebar.EmphasizableEntry {
public Geary.Folder folder { get; private set; }
private bool has_new;
private int unread_count;
public FolderEntry(Geary.Folder folder) {
this.folder = folder;
has_new = false;
unread_count = 0;
folder.properties.notify[Geary.FolderProperties.PROP_NAME_EMAIL_UNDREAD].connect(
on_email_unread_count_changed);
}
~FolderEntry() {
folder.properties.notify[Geary.FolderProperties.PROP_NAME_EMAIL_UNDREAD].disconnect(
on_email_unread_count_changed);
}
public virtual string get_sidebar_name() {
return (unread_count == 0 ? folder.get_display_name() :
return (folder.properties.email_unread == 0 ? folder.get_display_name() :
/// This string gets the folder name and the unread messages count,
/// e.g. All Mail (5).
_("%s (%d)").printf(folder.get_display_name(), unread_count));
_("%s (%d)").printf(folder.get_display_name(), folder.properties.email_unread));
}
public string? get_sidebar_tooltip() {
return (unread_count == 0 ? null :
ngettext("%d unread message", "%d unread messages", unread_count).printf(unread_count));
return (folder.properties.email_unread == 0 ? null :
ngettext("%d unread message", "%d unread messages", folder.properties.email_unread).
printf(folder.properties.email_unread));
}
public Icon? get_sidebar_icon() {
@ -82,15 +88,6 @@ public class FolderList.FolderEntry : Geary.BaseObject, Sidebar.Entry, Sidebar.I
is_emphasized_changed(has_new);
}
public void set_unread_count(int unread_count) {
if (this.unread_count == unread_count)
return;
this.unread_count = unread_count;
sidebar_name_changed(get_sidebar_name());
sidebar_tooltip_changed(get_sidebar_tooltip());
}
public bool internal_drop_received(Gdk.DragContext context, Gtk.SelectionData data) {
// Copy or move?
Gdk.ModifierType mask;
@ -105,4 +102,9 @@ public class FolderList.FolderEntry : Geary.BaseObject, Sidebar.Entry, Sidebar.I
return true;
}
private void on_email_unread_count_changed() {
sidebar_name_changed(get_sidebar_name());
sidebar_tooltip_changed(get_sidebar_tooltip());
}
}

View file

@ -16,7 +16,10 @@ public class FolderList.InboxFolderEntry : FolderList.FolderEntry {
}
public override string get_sidebar_name() {
return folder.account.information.nickname;
return (folder.properties.email_unread == 0 ? folder.account.information.nickname :
/// This string gets the account nickname and the unread messages count,
/// e.g. Work (5).
_("%s (%d)").printf(folder.account.information.nickname, folder.properties.email_unread));
}
public Geary.AccountInformation get_account_information() {

View file

@ -50,9 +50,9 @@ public abstract class Geary.AbstractFolder : BaseObject, Geary.Folder {
public abstract Geary.Account account { get; }
public abstract Geary.FolderPath get_path();
public abstract Geary.FolderProperties properties { get; }
public abstract Geary.FolderProperties get_properties();
public abstract Geary.FolderPath get_path();
public abstract Geary.SpecialFolderType get_special_folder_type();

View file

@ -0,0 +1,54 @@
/* 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.
*/
/**
* Aggregates multiple FolderProperties into one. This way a Geary.Folder can
* present one stable FolderProperties object that the client can register
* change listeners on, etc. despite most Geary.Folders having both a local
* and remote version of FolderProperties.
*
* The class relies on GObject bindings and the fact that FolderProperties
* contains only propertiess.
*/
private class Geary.AggregatedFolderProperties : Geary.FolderProperties {
// Map of child FolderProperties to their bindings.
private Gee.Map<FolderProperties, Gee.List<Binding>> child_bindings
= new Gee.HashMap<FolderProperties, Gee.List<Binding>>();
/**
* Creates an aggregate FolderProperties.
*/
public AggregatedFolderProperties() {
// Set defaults.
base(0, 0, Trillian.UNKNOWN, Trillian.UNKNOWN, Trillian.UNKNOWN);
}
/**
* Adds a child FolderProperties. The child's property values will overwrite
* this class's property values.
*/
public void add(FolderProperties child) {
// Create a binding for all properties.
Gee.List<Binding>? bindings = Geary.ObjectUtils.mirror_properties(child, this);
assert(bindings != null);
child_bindings.set(child, bindings);
}
/**
* Removes a child FolderProperties.
*/
public bool remove(FolderProperties child) {
Gee.List<Binding> bindings;
if (child_bindings.unset(child, out bindings)) {
Geary.ObjectUtils.unmirror_properties(bindings);
return true;
}
return false;
}
}

View file

@ -5,6 +5,12 @@
*/
public abstract class Geary.FolderProperties : BaseObject {
public const string PROP_NAME_EMAIL_TOTAL = "email-total";
public const string PROP_NAME_EMAIL_UNDREAD = "email-unread";
public const string PROP_NAME_HAS_CHILDREN = "has-children";
public const string PROP_NAME_SUPPORTS_CHILDREN = "supports-children";
public const string PROP_NAME_IS_OPENABLE = "is-openable";
/**
* The total count of email in the Folder.
*/

View file

@ -106,6 +106,8 @@ public interface Geary.Folder : BaseObject {
public abstract Geary.Account account { get; }
public abstract Geary.FolderProperties properties { get; }
/**
* Fired when the folder is successfully opened by a caller.
*
@ -233,19 +235,6 @@ public interface Geary.Folder : BaseObject {
public abstract Geary.FolderPath get_path();
/**
* Returns a FolderProperties that represents, if fully open, accurate values for this Folder,
* and if not, values that represent the last time the Folder was opened or examined by the
* Engine.
*
* The returned object is not guaranteed to be long-lived. If the Folder's state changes, it's
* possible a new FolderProperties will be set in its place. Instead of monitoring the fields
* of the FolderProperties for changes, use Account.folders_contents_changed() to be notified
* of changes and use the (potentially new) FolderProperties returned by this method at that
* point.
*/
public abstract Geary.FolderProperties get_properties();
/**
* Returns the special folder type of the folder.
*/

View file

@ -45,10 +45,12 @@ private class Geary.SmtpOutboxFolder : Geary.AbstractFolder, Geary.FolderSupport
private Geary.Smtp.ClientSession smtp;
private int open_count = 0;
private Nonblocking.Mailbox<OutboxRow> outbox_queue = new Nonblocking.Mailbox<OutboxRow>();
private SmtpOutboxFolderProperties properties = new SmtpOutboxFolderProperties(0, 0);
private SmtpOutboxFolderProperties _properties = new SmtpOutboxFolderProperties(0, 0);
public override Account account { get { return _account; } }
public override FolderProperties properties { get { return _properties; } }
// Requires the Database from the get-go because it runs a background task that access it
// whether open or not
public SmtpOutboxFolder(ImapDB.Database db, Account account) {
@ -91,7 +93,7 @@ private class Geary.SmtpOutboxFolder : Geary.AbstractFolder, Geary.FolderSupport
if (list.size > 0) {
// set properties now (can't do yield in ctor)
properties.set_total(list.size);
_properties.set_total(list.size);
debug("Priming outbox postman with %d stored messages", list.size);
foreach (OutboxRow row in list)
@ -178,7 +180,7 @@ private class Geary.SmtpOutboxFolder : Geary.AbstractFolder, Geary.FolderSupport
// update properties
try {
properties.set_total(yield get_email_count_async(null));
_properties.set_total(yield get_email_count_async(null));
} catch (Error err) {
debug("Outbox postman: Unable to fetch updated email count for properties: %s",
err.message);
@ -198,10 +200,6 @@ private class Geary.SmtpOutboxFolder : Geary.AbstractFolder, Geary.FolderSupport
return path;
}
public override Geary.FolderProperties get_properties() {
return properties;
}
public override Geary.SpecialFolderType get_special_folder_type() {
return Geary.SpecialFolderType.OUTBOX;
}
@ -286,7 +284,7 @@ private class Geary.SmtpOutboxFolder : Geary.AbstractFolder, Geary.FolderSupport
assert(row != null);
// update properties
properties.set_total(yield get_email_count_async(cancellable));
_properties.set_total(yield get_email_count_async(cancellable));
// immediately add to outbox queue for delivery
outbox_queue.send(row);

View file

@ -227,7 +227,7 @@ private class Geary.ImapEngine.AccountSynchronizer : Geary.BaseObject {
debug("Oldest local email in %s not old enough (%s vs. %s), synchronizing...",
folder.to_string(), oldest_local.to_string(), epoch.to_string());
}
} else if (folder.get_properties().email_total == 0) {
} else if (folder.properties.email_total == 0) {
// no local messages, no remote messages -- this is as good as having everything up
// to the epoch
return true;

View file

@ -15,12 +15,14 @@ private class Geary.ImapEngine.GenericFolder : Geary.AbstractFolder, Geary.Folde
Geary.Email.Field.PROPERTIES | ImapDB.Folder.REQUIRED_FOR_DUPLICATE_DETECTION;
public override Account account { get { return _account; } }
public override FolderProperties properties { get { return _properties; } }
internal ImapDB.Folder local_folder { get; protected set; }
internal Imap.Folder? remote_folder { get; protected set; default = null; }
internal EmailPrefetcher email_prefetcher { get; private set; }
internal EmailFlagWatcher email_flag_watcher;
private weak GenericAccount _account;
private Geary.AggregatedFolderProperties _properties = new Geary.AggregatedFolderProperties();
private Imap.Account remote;
private ImapDB.Account local;
private SpecialFolderType special_folder_type;
@ -37,6 +39,7 @@ private class Geary.ImapEngine.GenericFolder : Geary.AbstractFolder, Geary.Folde
this.local = local;
this.local_folder = local_folder;
this.special_folder_type = special_folder_type;
_properties.add(local_folder.get_properties());
email_flag_watcher = new EmailFlagWatcher(this);
email_flag_watcher.email_flags_changed.connect(on_email_flags_changed);
@ -53,16 +56,6 @@ private class Geary.ImapEngine.GenericFolder : Geary.AbstractFolder, Geary.Folde
return local_folder.get_path();
}
public override Geary.FolderProperties get_properties() {
// Get properties in order of authoritativeness:
// - From open remote folder
// - Fetch from local store
if (remote_folder != null && get_open_state() == OpenState.BOTH)
return remote_folder.properties;
return local_folder.get_properties();
}
public override Geary.SpecialFolderType get_special_folder_type() {
return special_folder_type;
}
@ -581,6 +574,8 @@ private class Geary.ImapEngine.GenericFolder : Geary.AbstractFolder, Geary.Folde
return;
}
_properties.add(remote_folder.properties);
// notify any subscribers with similar information
notify_opened(
(remote_folder != null) ? Geary.Folder.OpenState.BOTH : Geary.Folder.OpenState.LOCAL,
@ -591,6 +586,7 @@ private class Geary.ImapEngine.GenericFolder : Geary.AbstractFolder, Geary.Folde
if (open_count == 0 || --open_count > 0)
return;
_properties.remove(remote_folder.properties);
yield close_internal_async(CloseReason.LOCAL_CLOSE, CloseReason.REMOTE_CLOSE, cancellable);
}

View file

@ -0,0 +1,42 @@
/* 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.
*/
namespace Geary.ObjectUtils {
/**
* Creates a set of property bindings from source to dest with the given binding flags.
*/
public Gee.List<Binding>? mirror_properties(Object source, Object dest, BindingFlags
flags = GLib.BindingFlags.DEFAULT | GLib.BindingFlags.SYNC_CREATE) {
// Make sets of both object's properties.
Gee.HashSet<ParamSpec> source_properties = new Gee.HashSet<ParamSpec>();
source_properties.add_all_array(source.get_class().list_properties());
Gee.HashSet<ParamSpec> dest_properties = new Gee.HashSet<ParamSpec>();
dest_properties.add_all_array(dest.get_class().list_properties());
// Remove properties from source_properties that are not in both sets.
source_properties.retain_all(dest_properties);
// Create all bindings.
Gee.List<Binding> bindings = new Gee.ArrayList<Binding>();
foreach(ParamSpec ps in source_properties)
bindings.add(source.bind_property(ps.name, dest, ps.name, flags));
return bindings.size > 0 ? bindings : null;
}
/**
* Removes a property mirror created by mirror_properties
*/
public void unmirror_properties(Gee.List<Binding> bindings) {
foreach(Binding b in bindings)
b.unref();
bindings.clear();
}
}