Skip to content

Commit

Permalink
Notifications (#679)
Browse files Browse the repository at this point in the history
  • Loading branch information
marbetschar authored Sep 9, 2021
1 parent b9130a0 commit 3203533
Show file tree
Hide file tree
Showing 7 changed files with 287 additions and 1 deletion.
12 changes: 12 additions & 0 deletions data/io.elementary.mail-daemon.desktop.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[Desktop Entry]
Name=Mail Daemon
Comment=Send and receive mail
Exec=io.elementary.mail --background
Icon=io.elementary.mail
Keywords=Email;E-mail;Mail;
Terminal=false
Type=Application
NoDisplay=true
X-GNOME-AutoRestart=true
X-GNOME-Autostart-Delay=5
X-GNOME-Autostart-Phase=Applications
9 changes: 9 additions & 0 deletions data/io.elementary.mail.appdata.xml.in
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@
<binary>io.elementary.mail</binary>
</provides>
<releases>
<release version="6.2.0" date="2021-09-09" urgency="medium">
<description>
<p>Improvements:</p>
<ul>
<li>Send a notification when new messages arrive</li>
<li>Updated translations</li>
</ul>
</description>
</release>
<release version="6.1.1" date="2021-08-30" urgency="medium">
<description>
<p>Fixes:</p>
Expand Down
8 changes: 8 additions & 0 deletions data/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,14 @@ foreach i : icon_sizes
)
endforeach

daemon_desktop_config = configuration_data()
configure_file(
input: meson.project_name() + '-daemon.desktop.in',
output: meson.project_name() + '-daemon.desktop',
configuration: daemon_desktop_config,
install_dir: join_paths(get_option('sysconfdir'), 'xdg', 'autostart')
)

install_data(
meson.project_name() + '.gschema.xml',
install_dir: join_paths(get_option('datadir'), 'glib-2.0', 'schemas')
Expand Down
17 changes: 17 additions & 0 deletions src/Application.vala
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,13 @@
*/

public class Mail.Application : Gtk.Application {
const OptionEntry[] OPTIONS = {
{ "background", 'b', 0, OptionArg.NONE, out run_in_background, "Run the Application in background", null},
{ null }
};

public static GLib.Settings settings;
public static bool run_in_background;

private MainWindow? main_window = null;

Expand All @@ -40,6 +46,8 @@ public class Mail.Application : Gtk.Application {
Intl.bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
Intl.textdomain (GETTEXT_PACKAGE);

add_main_option_entries (OPTIONS);

var quit_action = new SimpleAction ("quit", null);
quit_action.activate.connect (() => {
if (main_window != null) {
Expand Down Expand Up @@ -81,6 +89,13 @@ public class Mail.Application : Gtk.Application {
}

public override void activate () {
if (run_in_background) {
run_in_background = false;
new InboxMonitor ().start.begin ();
hold ();
return;
}

if (main_window == null) {
Gtk.IconTheme.get_default ().add_resource_path ("/io/elementary/mail");

Expand Down Expand Up @@ -117,6 +132,8 @@ public class Mail.Application : Gtk.Application {
css_provider.load_from_resource ("io/elementary/mail/application.css");
Gtk.StyleContext.add_provider_for_screen (Gdk.Screen.get_default (), css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION);
}

main_window.present ();
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/Backend/Session.vala
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public class Mail.Backend.Session : Camel.Session {
public signal void account_added (Mail.Backend.Account account);
public signal void account_removed ();

private Session () {
public Session () {
Object (user_data_dir: Path.build_filename (E.get_user_data_dir (), "mail"), user_cache_dir: Path.build_filename (E.get_user_cache_dir (), "mail"));
}

Expand Down
239 changes: 239 additions & 0 deletions src/InboxMonitor.vala
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
/*
* Copyright 2021 elementary, Inc. (https://elementary.io)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301 USA
*/

public class Mail.InboxMonitor : GLib.Object {

private NetworkMonitor network_monitor;
private Mail.Backend.Session session;
private HashTable<E.Source, Camel.Folder> inbox_folders;
private HashTable<E.Source, uint> synchronize_timeout_ids;
private E.SourceRegistry registry;

construct {
inbox_folders = new HashTable<E.Source, Camel.Folder> (E.Source.hash, E.Source.equal);
synchronize_timeout_ids = new HashTable<E.Source, uint> (E.Source.hash, E.Source.equal);

network_monitor = GLib.NetworkMonitor.get_default ();
session = new Mail.Backend.Session ();
}

public async void start () {
yield session.start ();
try {
registry = yield new E.SourceRegistry (null);

} catch (Error e) {
critical ("Error starting inbox monitor: %s", e.message);
return;
}

var sources = registry.list_sources (E.SOURCE_EXTENSION_MAIL_ACCOUNT);
foreach (var source in sources) {
add_source (source);
}

registry.source_added.connect (add_source);
registry.source_removed.connect (remove_source);

registry.source_changed.connect ((source) => {
remove_source (source);
add_source (source);
});
}

private void add_source (E.Source source) {
if (!source.has_extension (E.SOURCE_EXTENSION_MAIL_ACCOUNT)) {
return;
}
unowned string uid = source.get_uid ();
unowned string display_name = source.get_display_name ();

if (uid == "vfolder") {
debug ("[%s] Is a vfolder. Ignoring it…", display_name);
return;
}

unowned var extension = (E.SourceMailAccount) source.get_extension (E.SOURCE_EXTENSION_MAIL_ACCOUNT);
if (extension.backend_name == "mbox") {
debug ("[%s] Is a local inbox. Ignoring it…", display_name);
return;
}

Camel.Store? store = null;
try {
store = (Camel.Store) session.add_service (uid, extension.backend_name, Camel.ProviderType.STORE);
} catch (Error e) {
warning ("[%s] Error adding service: %s", display_name, e.message);
}

if (store != null) {
try {
var folder = store.get_inbox_folder_sync (null);

if (folder != null) {
var inbox_folder = store.get_folder_sync (folder.full_name, Camel.StoreGetFolderFlags.NONE, null);

if (inbox_folder != null) {
inbox_folder.changed.connect ((change_info) => {
inbox_folder_changed (source, change_info);
});
inbox_folders.insert (source, inbox_folder);

uint refresh_interval_in_minutes = 15;
if (source.has_extension (E.SOURCE_EXTENSION_REFRESH)) {
unowned var refresh_extension = (E.SourceRefresh) source.get_extension (E.SOURCE_EXTENSION_REFRESH);

if (!refresh_extension.enabled) {
refresh_interval_in_minutes = 0;

} else if (refresh_extension.interval_minutes > 0) {
refresh_interval_in_minutes = refresh_extension.interval_minutes;
}
}

if (refresh_interval_in_minutes > 0) {
debug ("[%s] Checking inbox for new mail every %u minutes…", display_name, refresh_interval_in_minutes);
var refresh_timeout_id = GLib.Timeout.add_seconds (refresh_interval_in_minutes * 60, () => {
inbox_folder_synchronize_sync.begin (source);
return GLib.Source.CONTINUE;
});
synchronize_timeout_ids.insert (source, refresh_timeout_id);

inbox_folder_synchronize_sync.begin (source);

} else {
debug ("[%s] Automatically checking inbox for new mail is disabled.", display_name);
}
}

} else {
debug ("[%s] Inbox folder not found. Can't automatically check for new messages.", display_name);
}

} catch (Error e) {
warning ("[%s] Error getting inbox folder: %s", display_name, e.message);
}

} else {
debug ("[%s] No store available.", display_name);
}
}

private void remove_source (E.Source source) {
if (!source.has_extension (E.SOURCE_EXTENSION_MAIL_ACCOUNT)) {
return;
}
debug ("[%s] Removing…", source.display_name);

bool timeout_id_exists;
var timeout_id = synchronize_timeout_ids.take (source, out timeout_id_exists);
if (timeout_id_exists) {
GLib.Source.remove (timeout_id);
}

bool exists;
var inbox_folder = inbox_folders.take (source, out exists);
if (exists) {
session.remove_service (inbox_folder.parent_store);
}
}

private async void inbox_folder_synchronize_sync (E.Source source) {
if (!network_monitor.network_available) {
debug ("[%s] Network is not avaible. Skipping…", source.display_name);
return;
}

var inbox_folder = inbox_folders.get (source);
if (inbox_folder != null) {
debug ("[%s] Refreshing…", source.display_name);

try {
inbox_folder.refresh_info_sync (null);

} catch (Error e) {
warning ("[%s] Error refreshing: %s", source.display_name, e.message);
}
}
}

private void inbox_folder_changed (E.Source source, Camel.FolderChangeInfo changes) {
var inbox_folder = inbox_folders.get (source);
if (inbox_folder == null) {
return;
}

unowned var added_uids = changes.get_added_uids ();
if (added_uids != null) {
var sender_names = new GenericSet<string> (str_hash, str_equal);
var unseen_message_infos = new SList<Camel.MessageInfo> ();

added_uids.foreach ((added_uid) => {
var message_info = inbox_folder.get_message_info (added_uid);

if (!(Camel.MessageFlags.SEEN in message_info.flags)) {
unowned string? sender_address;
unowned string? sender_name;

var camel_address = new Camel.InternetAddress ();
camel_address.unformat (message_info.from);
camel_address.get (0, out sender_name, out sender_address);

if (sender_name == null) {
sender_name = sender_address;
}

sender_names.add (sender_name);
unseen_message_infos.append (message_info);
}
});

var unseen_message_infos_length = unseen_message_infos.length ();
if (unseen_message_infos_length == 1) {
var unseen_message_info = unseen_message_infos.nth_data (0);

var notification = new GLib.Notification (_("%s to %s").printf (sender_names.iterator ().next_value (), inbox_folder.parent_store.display_name));
notification.set_body (unseen_message_info.subject);
GLib.Application.get_default ().send_notification (unseen_message_info.uid, notification);

} else if (unseen_message_infos_length > 1) {
GLib.Notification notification;

///TRANSLATORS: The %s represents the number of new messages translated in your language, e.g. "2 new messages"
string messages_count = ngettext ("%u new message", "%u new messages", unseen_message_infos_length).printf (unseen_message_infos_length);

if (sender_names.length == 1) {
var sender_name = sender_names.iterator ().next_value ();

notification = new GLib.Notification (_("%s to %s").printf (sender_name, inbox_folder.parent_store.display_name));
notification.set_body (messages_count);

} else {
notification = new GLib.Notification (inbox_folder.parent_store.display_name);

///TRANSLATORS: The first %s represents the number of new messages translated in your language, e.g. "2 new messages"
///The next %s represents the number of senders
notification.set_body (ngettext ("%s from %u sender", "%s from %u senders", sender_names.length).printf (messages_count, sender_names.length));
}

GLib.Application.get_default ().send_notification (unseen_message_infos.nth_data (0).uid, notification);
}
}
}
}
1 change: 1 addition & 0 deletions src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ vala_files = files(
'Application.vala',
'MainWindow.vala',
'HeaderBar.vala',
'InboxMonitor.vala',
'Utils.vala',
'WebView.vala',
'WelcomeView.vala',
Expand Down

0 comments on commit 3203533

Please sign in to comment.