[geary/wip/774603-configurable-gravatar-uri: 1/2] Break out AvatarStore to a top-level class so it can be re-used
- From: Michael Gratton <mjog src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [geary/wip/774603-configurable-gravatar-uri: 1/2] Break out AvatarStore to a top-level class so it can be re-used
- Date: Sun, 7 Oct 2018 02:58:01 +0000 (UTC)
commit 3eb40c5e7dd3a454b98b81550a8abc242c1df8d2
Author: Michael Gratton <mike vee net>
Date: Sun Oct 7 13:18:23 2018 +1100
Break out AvatarStore to a top-level class so it can be re-used
Make the controller manage the store's lifecycle, and pass that around
to the conversation viewer and notifications to use, instead of
a soup cache and/or doing HTTP calls themselves.
po/POTFILES.in | 1 +
.../application/application-avatar-store.vala | 130 +++++++++++++++++++++
src/client/application/geary-controller.vala | 42 +++----
.../conversation-viewer/conversation-email.vala | 2 +-
.../conversation-viewer/conversation-list-box.vala | 105 +----------------
.../conversation-viewer/conversation-message.vala | 2 +-
.../conversation-viewer/conversation-viewer.vala | 4 +-
src/client/meson.build | 1 +
src/client/notification/libnotify.vala | 38 +++---
9 files changed, 169 insertions(+), 156 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index e04b93c6..e23b079a 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -20,6 +20,7 @@ src/client/accounts/add-edit-page.vala
src/client/accounts/goa-service-information.vala
src/client/accounts/local-service-information.vala
src/client/accounts/login-dialog.vala
+src/client/application/application-avatar-store.vala
src/client/application/autostart-manager.vala
src/client/application/geary-application.vala
src/client/application/geary-args.vala
diff --git a/src/client/application/application-avatar-store.vala
b/src/client/application/application-avatar-store.vala
new file mode 100644
index 00000000..a5a2495b
--- /dev/null
+++ b/src/client/application/application-avatar-store.vala
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2016-2018 Michael Gratton <mike vee net>
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later). See the COPYING file in this distribution.
+ */
+
+
+/**
+ * Email address avatar loader and cache.
+ */
+public class Application.AvatarStore : Geary.BaseObject {
+
+
+ // Initiates and manages an avatar load using Gravatar
+ private class AvatarLoader : Geary.BaseObject {
+
+ internal Gdk.Pixbuf? avatar = null;
+ internal Geary.Nonblocking.Semaphore lock =
+ new Geary.Nonblocking.Semaphore();
+
+ private string base_url;
+ private Geary.RFC822.MailboxAddress address;
+ private int pixel_size;
+
+
+ internal AvatarLoader(Geary.RFC822.MailboxAddress address,
+ string base_url,
+ int pixel_size) {
+ this.address = address;
+ this.base_url = base_url;
+ this.pixel_size = pixel_size;
+ }
+
+ internal async void load(Soup.Session session,
+ Cancellable load_cancelled)
+ throws GLib.Error {
+ Error? workaround_err = null;
+ if (!Geary.String.is_empty_or_whitespace(this.base_url)) {
+ string md5 = GLib.Checksum.compute_for_string(
+ GLib.ChecksumType.MD5, this.address.address.strip().down()
+ );
+ Soup.Message message = new Soup.Message(
+ "GET",
+ "%s/%s?d=%s&s=%d".printf(
+ this.base_url, md5, "404", this.pixel_size
+ )
+ );
+
+ try {
+ // We want to just pass load_cancelled to send_async
+ // here, but per Bug 778720 this is causing some
+ // crashy race in libsoup's cache implementation, so
+ // for now just let the load go through and manually
+ // check to see if the load has been cancelled before
+ // setting the avatar
+ InputStream data = yield session.send_async(
+ message,
+ null // should be 'load_cancelled'
+ );
+ if (message.status_code == 200 &&
+ data != null &&
+ !load_cancelled.is_cancelled()) {
+ this.avatar = yield new Gdk.Pixbuf.from_stream_at_scale_async(
+ data, pixel_size, pixel_size, true, load_cancelled
+ );
+ }
+ } catch (Error err) {
+ workaround_err = err;
+ }
+ }
+
+ this.lock.blind_notify();
+
+ if (workaround_err != null) {
+ throw workaround_err;
+ }
+ }
+
+ }
+
+
+ private Configuration config;
+
+ private Soup.Session session;
+ private Soup.Cache cache;
+ private Gee.Map<string,AvatarLoader> loaders =
+ new Gee.HashMap<string,AvatarLoader>();
+
+
+ public AvatarStore(Configuration config, GLib.File cache_root) {
+ this.config = config;
+
+ File avatar_cache_dir = cache_root.get_child("avatars");
+ this.cache = new Soup.Cache(
+ avatar_cache_dir.get_path(),
+ Soup.CacheType.SINGLE_USER
+ );
+ this.cache.load();
+ this.cache.set_max_size(16 * 1024 * 1024); // 16MB
+ this.session = new Soup.Session();
+ this.session.add_feature(this.cache);
+ }
+
+ public void close() {
+ this.cache.flush();
+ this.cache.dump();
+ }
+
+ public async Gdk.Pixbuf? load(Geary.RFC822.MailboxAddress address,
+ int pixel_size,
+ Cancellable load_cancelled)
+ throws Error {
+ string key = address.to_string();
+ AvatarLoader loader = this.loaders.get(key);
+ if (loader == null) {
+ // Haven't started loading the avatar, so do it now
+ loader = new AvatarLoader(
+ address, this.config.avatar_url, pixel_size
+ );
+ this.loaders.set(key, loader);
+ yield loader.load(this.session, load_cancelled);
+ } else {
+ // Load has already started, so wait for it to finish
+ yield loader.lock.wait_async();
+ }
+ return loader.avatar;
+ }
+
+}
diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala
index 91462e91..2f496491 100644
--- a/src/client/application/geary-controller.vala
+++ b/src/client/application/geary-controller.vala
@@ -86,8 +86,9 @@ public class GearyController : Geary.BaseObject {
public LoginDialog? login_dialog { get; private set; default = null; }
- public Soup.Session? avatar_session { get; private set; default = null; }
- private Soup.Cache? avatar_cache = null;
+ public Application.AvatarStore? avatar_store {
+ get; private set; default = null;
+ }
private Geary.Account? current_account = null;
private Gee.Map<Geary.AccountInformation,AccountContext> accounts =
@@ -219,21 +220,10 @@ public class GearyController : Geary.BaseObject {
error("Error loading web resources: %s", err.message);
}
- // Use a global avatar session because a cache must be used
- // per-session, and we don't want to have to load the cache
- // for each conversation load.
- File avatar_cache_dir = this.application.get_user_cache_directory()
- .get_child("avatars");
- this.avatar_cache = new Soup.Cache(
- avatar_cache_dir.get_path(),
- Soup.CacheType.SINGLE_USER
- );
- this.avatar_cache.load();
- this.avatar_cache.set_max_size(10 * 1024 * 1024); // 4MB
- this.avatar_session = new Soup.Session.with_options(
- Soup.SESSION_USER_AGENT, "Geary/" + GearyApplication.VERSION
+ this.avatar_store = new Application.AvatarStore(
+ this.application.config,
+ this.application.get_user_cache_directory()
);
- this.avatar_session.add_feature(avatar_cache);
// Create the main window (must be done after creating actions.)
main_window = new MainWindow(this.application);
@@ -271,12 +261,13 @@ public class GearyController : Geary.BaseObject {
new_messages_indicator.application_activated.connect(on_indicator_activated_application);
new_messages_indicator.composer_activated.connect(on_indicator_activated_composer);
new_messages_indicator.inbox_activated.connect(on_indicator_activated_inbox);
-
+
unity_launcher = new UnityLauncher(new_messages_monitor);
-
- // libnotify
- libnotify = new Libnotify(new_messages_monitor);
- libnotify.invoked.connect(on_libnotify_invoked);
+
+ this.libnotify = new Libnotify(
+ this.new_messages_monitor, this.avatar_store
+ );
+ this.libnotify.invoked.connect(on_libnotify_invoked);
this.main_window.conversation_list_view.grab_focus();
@@ -402,7 +393,7 @@ public class GearyController : Geary.BaseObject {
foreach (AccountContext context in accounts) {
Geary.Folder? inbox = context.inbox;
if (inbox != null) {
- debug("Closing %s...", inbox.to_string());
+ debug("Closing inbox: %s...", inbox.to_string());
inbox.close_async.begin(null, (obj, ret) => {
try {
inbox.close_async.end(ret);
@@ -481,8 +472,9 @@ public class GearyController : Geary.BaseObject {
this.autostart_manager = null;
- this.avatar_cache.flush();
- this.avatar_cache.dump();
+ this.avatar_store.close();
+ this.avatar_store = null;
+
debug("Closed GearyController");
}
@@ -1536,7 +1528,7 @@ public class GearyController : Geary.BaseObject {
Geary.Collection.get_first(selected),
this.current_folder,
this.application.config,
- this.avatar_session,
+ this.avatar_store,
(obj, ret) => {
try {
viewer.load_conversation.end(ret);
diff --git a/src/client/conversation-viewer/conversation-email.vala
b/src/client/conversation-viewer/conversation-email.vala
index fa940841..d19af9e4 100644
--- a/src/client/conversation-viewer/conversation-email.vala
+++ b/src/client/conversation-viewer/conversation-email.vala
@@ -526,7 +526,7 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
* primary message and any attached messages, as well as
* attachment names, types and icons.
*/
- public async void start_loading(ConversationListBox.AvatarStore avatars,
+ public async void start_loading(Application.AvatarStore avatars,
Cancellable load_cancelled) {
foreach (ConversationMessage view in this) {
if (load_cancelled.is_cancelled()) {
diff --git a/src/client/conversation-viewer/conversation-list-box.vala
b/src/client/conversation-viewer/conversation-list-box.vala
index 928952f5..6ee9059f 100644
--- a/src/client/conversation-viewer/conversation-list-box.vala
+++ b/src/client/conversation-viewer/conversation-list-box.vala
@@ -226,105 +226,6 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
}
- /**
- * Email address avatar loader and cache.
- */
- public class AvatarStore {
-
-
- private Soup.Session session;
- private Gee.Map<string,AvatarLoader> loaders =
- new Gee.HashMap<string,AvatarLoader>();
-
-
- internal AvatarStore(Soup.Session session) {
- this.session = session;
- }
-
- internal async Gdk.Pixbuf? load(Geary.RFC822.MailboxAddress address,
- int pixel_size,
- Cancellable load_cancelled)
- throws Error {
- string key = address.to_string();
- AvatarLoader loader = this.loaders.get(key);
- if (loader == null) {
- // Haven't started loading the avatar, so do it now
- loader = new AvatarLoader(address, pixel_size);
- this.loaders.set(key, loader);
- yield loader.load(this.session, load_cancelled);
- } else {
- // Load has already started, so wait for it to finish
- yield loader.lock.wait_async();
- }
- return loader.avatar;
- }
-
- }
-
-
- // Initiates and manages an avatar load
- private class AvatarLoader : Geary.BaseObject {
-
-
- internal Gdk.Pixbuf? avatar = null;
- internal Geary.Nonblocking.Semaphore lock =
- new Geary.Nonblocking.Semaphore();
-
- private Geary.RFC822.MailboxAddress address;
- private int pixel_size;
-
-
- internal AvatarLoader(Geary.RFC822.MailboxAddress address,
- int pixel_size) {
- this.address = address;
- this.pixel_size = pixel_size;
- }
-
- internal async void load(Soup.Session session,
- Cancellable load_cancelled)
- throws Error {
- Soup.Message message = new Soup.Message(
- "GET",
- Gravatar.get_image_uri(
- this.address,
- Gravatar.Default.NOT_FOUND,
- this.pixel_size
- )
- );
-
- Error? workaround_err = null;
- try {
- // We want to just pass load_cancelled to send_async
- // here, but per Bug 778720 this is causing some
- // crashy race in libsoup's cache implementation, so
- // for now just let the load go through and manually
- // check to see if the load has been cancelled before
- // setting the avatar
- InputStream data = yield session.send_async(
- message,
- null // should be 'load_cancelled'
- );
- if (message.status_code == 200 &&
- data != null &&
- !load_cancelled.is_cancelled()) {
- this.avatar = yield new Gdk.Pixbuf.from_stream_at_scale_async(
- data, pixel_size, pixel_size, true, load_cancelled
- );
- }
- } catch (Error err) {
- workaround_err = err;
- }
-
- this.lock.blind_notify();
-
- if (workaround_err != null) {
- throw workaround_err;
- }
- }
-
- }
-
-
static construct {
// Set up custom keybindings
unowned Gtk.BindingSet bindings = Gtk.BindingSet.by_class(
@@ -396,7 +297,7 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
private Geary.ContactStore contact_store;
// Avatars for this conversation
- private AvatarStore avatar_store;
+ private Application.AvatarStore avatar_store;
// Account this conversation belongs to
private Geary.AccountInformation account_info;
@@ -497,14 +398,14 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
Geary.AccountInformation account_info,
bool is_draft_folder,
Configuration config,
- Soup.Session avatar_session,
+ Application.AvatarStore avatar_store,
Gtk.Adjustment adjustment) {
base_ref();
this.conversation = conversation;
this.location = location;
this.email_store = email_store;
this.contact_store = contact_store;
- this.avatar_store = new AvatarStore(avatar_session);
+ this.avatar_store = avatar_store;
this.account_info = account_info;
this.is_draft_folder = is_draft_folder;
this.config = config;
diff --git a/src/client/conversation-viewer/conversation-message.vala
b/src/client/conversation-viewer/conversation-message.vala
index dfbce2ce..60faa8ac 100644
--- a/src/client/conversation-viewer/conversation-message.vala
+++ b/src/client/conversation-viewer/conversation-message.vala
@@ -436,7 +436,7 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
/**
* Starts loading the avatar for the message's sender.
*/
- public async void load_avatar(ConversationListBox.AvatarStore loader,
+ public async void load_avatar(Application.AvatarStore loader,
Cancellable load_cancelled) {
const int PIXEL_SIZE = 32;
Geary.RFC822.MailboxAddress? primary = message.get_primary_originator();
diff --git a/src/client/conversation-viewer/conversation-viewer.vala
b/src/client/conversation-viewer/conversation-viewer.vala
index 8e92a513..f53b719a 100644
--- a/src/client/conversation-viewer/conversation-viewer.vala
+++ b/src/client/conversation-viewer/conversation-viewer.vala
@@ -199,7 +199,7 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
public async void load_conversation(Geary.App.Conversation conversation,
Geary.Folder location,
Configuration config,
- Soup.Session avatar_session)
+ Application.AvatarStore avatars)
throws Error {
remove_current_list();
@@ -212,7 +212,7 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
account.information,
location.special_folder_type == Geary.SpecialFolderType.DRAFTS,
config,
- avatar_session,
+ avatars,
this.conversation_scroller.get_vadjustment()
);
diff --git a/src/client/meson.build b/src/client/meson.build
index 7fba7c62..ba97b8e3 100644
--- a/src/client/meson.build
+++ b/src/client/meson.build
@@ -1,5 +1,6 @@
# Geary client
geary_client_vala_sources = files(
+ 'application/application-avatar-store.vala',
'application/autostart-manager.vala',
'application/geary-application.vala',
'application/geary-args.vala',
diff --git a/src/client/notification/libnotify.vala b/src/client/notification/libnotify.vala
index 4207bc8c..059e1978 100644
--- a/src/client/notification/libnotify.vala
+++ b/src/client/notification/libnotify.vala
@@ -8,10 +8,13 @@
public class Libnotify : Geary.BaseObject {
public const Geary.Email.Field REQUIRED_FIELDS =
Geary.Email.Field.ORIGINATORS | Geary.Email.Field.SUBJECT;
-
+
+ private const int AVATAR_SIZE = 32;
+
private static Canberra.Context? sound_context = null;
-
+
private weak NewMessagesMonitor monitor;
+ private weak Application.AvatarStore avatars;
private Notify.Notification? current_notification = null;
private Notify.Notification? error_notification = null;
private Geary.Folder? folder = null;
@@ -19,10 +22,12 @@ public class Libnotify : Geary.BaseObject {
private List<string>? caps = null;
public signal void invoked(Geary.Folder? folder, Geary.Email? email);
-
- public Libnotify(NewMessagesMonitor monitor) {
+
+ public Libnotify(NewMessagesMonitor monitor,
+ Application.AvatarStore avatars) {
this.monitor = monitor;
-
+ this.avatars = avatars;
+
monitor.add_required_fields(REQUIRED_FIELDS);
if (!Notify.is_initted()) {
@@ -106,26 +111,9 @@ public class Libnotify : Geary.BaseObject {
EmailUtil.strip_subject_prefixes(email), count - 1, folder.account.information.display_name);
}
- // get the avatar
- Gdk.Pixbuf? avatar = null;
- InputStream? ins = null;
- File file = File.new_for_uri(Gravatar.get_image_uri(primary, Gravatar.Default.MYSTERY_MAN));
- try {
- ins = yield file.read_async(GLib.Priority.DEFAULT, cancellable);
- avatar = yield new Gdk.Pixbuf.from_stream_async(ins, cancellable);
- } catch (Error err) {
- debug("Failed to get avatar for notification: %s", err.message);
- }
-
- if (ins != null) {
- try {
- yield ins.close_async(Priority.DEFAULT, cancellable);
- } catch (Error close_err) {
- // ignored
- }
-
- ins = null;
- }
+ Gdk.Pixbuf? avatar = yield this.avatars.load(
+ primary, AVATAR_SIZE, cancellable
+ );
issue_current_notification(primary.to_short_display(), body, avatar);
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]