Implement News & Blogs (RSS) reader

This commit is contained in:
Milan Crha
2022-07-05 21:21:59 +02:00
parent fcddfd27c9
commit 854b61ecbf
29 changed files with 6347 additions and 2 deletions

View File

@ -180,10 +180,12 @@ set(private_icons
hicolor_places_24x24_mail-outbox.png
hicolor_places_24x24_mail-sent.png
hicolor_status_16x16_wrapped.png
hicolor_status_32x32_offline.png
hicolor_status_32x32_online.png
hicolor_status_32x32_aspect-ratio-lock.png
hicolor_status_32x32_aspect-ratio-unlock.png
hicolor_status_32x32_offline.png
hicolor_status_32x32_online.png
hicolor_status_scalable_rss.svg
hicolor_status_scalable_rss-symbolic.svg
)
# These icons were in gnome-icon-theme prior to GNOME 2.30.

View File

@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="32"
height="32"
viewBox="0 0 8.4666665 8.4666669"
version="1.1"
id="svg5"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs2">
<linearGradient
id="grad">
<stop
style="stop-color:#f6af7a;stop-opacity:1;"
offset="0"
id="stop6983" />
<stop
style="stop-color:#d94027;stop-opacity:1;"
offset="1"
id="stop6985" />
</linearGradient>
<linearGradient
xlink:href="#grad"
id="linearGradient7143"
x1="-10.318749"
y1="8.4666662"
x2="-3.7041667"
y2="14.816667"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.9,0,0,0.9,10.424574,-6.2441669)" />
<filter
style="color-interpolation-filters:sRGB"
id="filter9649"
x="-0.66035874"
y="-0.66035874"
width="2.3207175"
height="2.3207175">
<feGaussianBlur
stdDeviation="0.053035311"
id="feGaussianBlur9651" />
</filter>
<filter
style="color-interpolation-filters:sRGB"
id="filter9641-6"
x="-0.12872887"
y="-0.12287756"
width="1.2574583"
height="1.2457551">
<feGaussianBlur
stdDeviation="0.053035311"
id="feGaussianBlur9643-7" />
</filter>
<filter
style="color-interpolation-filters:sRGB"
id="filter9641-6-3"
x="-0.19927554"
y="-0.19021757"
width="1.3985535"
height="1.3804351">
<feGaussianBlur
stdDeviation="0.053035311"
id="feGaussianBlur9643-7-5" />
</filter>
<linearGradient
xlink:href="#grad"
id="linearGradient10019"
x1="0.59531248"
y1="4.2333331"
x2="7.8713541"
y2="4.2333331"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.9,0,0,0.9,0.42332501,0.42333333)" />
</defs>
<g
id="layer1">
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.17593;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter9641-6)"
d="m -10.583333,-0.7937499 c 0.440964,0 0.8819353,0 1.4555821,0.13243357 0.5736469,0.13243356 1.2783243,0.39668758 1.8519057,0.79389359 0.5735815,0.39720602 1.0143588,0.92613874 1.3670214,1.49931474 0.3526627,0.5731761 0.6172246,1.190487 0.7494827,1.7638745 0.132258,0.5733874 0.1322581,1.1021462 0.1322581,1.6313167"
id="path1371-5"
transform="matrix(0.729,0,0,0.729,9.8048219,2.7623248)" />
<path
style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1.95988;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter9641-6-3)"
d="m -10.583333,-0.7937499 c 0.440964,0 0.8819353,0 1.4555821,0.13243357 0.5736469,0.13243356 1.2783243,0.39668758 1.8519057,0.79389359 0.5735815,0.39720602 1.0143588,0.92613874 1.3670214,1.49931474 0.3526627,0.5731761 0.6172246,1.190487 0.7494827,1.7638745 0.132258,0.5733874 0.1322581,1.1021462 0.1322581,1.6313167"
id="path1371-5-6"
transform="matrix(0.4374,0,0,0.4374,6.6753693,4.2499621)" />
<circle
style="fill:#000000;stroke:#000000;stroke-width:0.79375;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;paint-order:markers stroke fill;filter:url(#filter9649)"
id="path1690"
cx="1.911677"
cy="6.4105716"
r="0.39687499"
transform="matrix(0.81,0,0,0.81,0.74481282,0.91615312)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

View File

@ -0,0 +1,110 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="32"
height="32"
viewBox="0 0 8.4666665 8.4666669"
version="1.1"
id="svg5"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs2">
<linearGradient
id="grad">
<stop
style="stop-color:#f6af7a;stop-opacity:1;"
offset="0"
id="stop6983" />
<stop
style="stop-color:#d94027;stop-opacity:1;"
offset="1"
id="stop6985" />
</linearGradient>
<linearGradient
xlink:href="#grad"
id="linearGradient7143"
x1="-10.318749"
y1="8.4666662"
x2="-3.7041667"
y2="14.816667"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.9,0,0,0.9,10.424574,-6.2441669)" />
<filter
style="color-interpolation-filters:sRGB"
id="filter9649"
x="-0.66035874"
y="-0.66035874"
width="2.3207175"
height="2.3207175">
<feGaussianBlur
stdDeviation="0.053035311"
id="feGaussianBlur9651" />
</filter>
<filter
style="color-interpolation-filters:sRGB"
id="filter9641-6"
x="-0.12872887"
y="-0.12287756"
width="1.2574583"
height="1.2457551">
<feGaussianBlur
stdDeviation="0.053035311"
id="feGaussianBlur9643-7" />
</filter>
<filter
style="color-interpolation-filters:sRGB"
id="filter9641-6-3"
x="-0.19927554"
y="-0.19021757"
width="1.3985535"
height="1.3804351">
<feGaussianBlur
stdDeviation="0.053035311"
id="feGaussianBlur9643-7-5" />
</filter>
<linearGradient
xlink:href="#grad"
id="linearGradient10019"
x1="0.59531248"
y1="4.2333331"
x2="7.8713541"
y2="4.2333331"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.9,0,0,0.9,0.42332501,0.42333333)" />
</defs>
<g
id="layer1">
<g
id="g1263">
<rect
style="opacity:1;fill:url(#linearGradient7143);fill-opacity:1;fill-rule:evenodd;stroke:url(#linearGradient10019);stroke-width:0.47625;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:markers stroke fill"
id="rect6981"
width="6.1911001"
height="6.1912498"
x="1.1377"
y="1.1377083"
rx="1.1691136"
ry="1.1611228" />
<path
style="fill:none;fill-rule:evenodd;stroke:#f9f9f9;stroke-width:1.17593;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter9641-6)"
d="m -10.583333,-0.7937499 c 0.440964,0 0.8819353,0 1.4555821,0.13243357 0.5736469,0.13243356 1.2783243,0.39668758 1.8519057,0.79389359 0.5735815,0.39720602 1.0143588,0.92613874 1.3670214,1.49931474 0.3526627,0.5731761 0.6172246,1.190487 0.7494827,1.7638745 0.132258,0.5733874 0.1322581,1.1021462 0.1322581,1.6313167"
id="path1371-5"
transform="matrix(0.729,0,0,0.729,9.8048219,2.7623248)" />
<path
style="fill:none;fill-rule:evenodd;stroke:#f9f9f9;stroke-width:1.95988;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter9641-6-3)"
d="m -10.583333,-0.7937499 c 0.440964,0 0.8819353,0 1.4555821,0.13243357 0.5736469,0.13243356 1.2783243,0.39668758 1.8519057,0.79389359 0.5735815,0.39720602 1.0143588,0.92613874 1.3670214,1.49931474 0.3526627,0.5731761 0.6172246,1.190487 0.7494827,1.7638745 0.132258,0.5733874 0.1322581,1.1021462 0.1322581,1.6313167"
id="path1371-5-6"
transform="matrix(0.4374,0,0,0.4374,6.6753693,4.2499621)" />
<circle
style="fill:#f9f9f9;stroke:#f9f9f9;stroke-width:0.79375;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;paint-order:markers stroke fill;filter:url(#filter9649)"
id="path1690"
cx="1.911677"
cy="6.4105716"
r="0.39687499"
transform="matrix(0.81,0,0,0.81,0.74481282,0.91615312)" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -482,6 +482,14 @@ src/modules/prefer-plain/e-mail-display-popup-prefer-plain.c
src/modules/prefer-plain/e-mail-parser-prefer-plain.c
src/modules/prefer-plain/plugin/config-ui.c
src/modules/prefer-plain/plugin/org-gnome-prefer-plain.eplug.xml
src/modules/rss/camel/camel-rss-folder.c
src/modules/rss/camel/camel-rss-folder-summary.c
src/modules/rss/camel/camel-rss-provider.c
src/modules/rss/camel/camel-rss-store.c
src/modules/rss/e-rss-parser.c
src/modules/rss/evolution/e-rss-preferences.c
src/modules/rss/evolution/e-rss-shell-extension.c
src/modules/rss/evolution/e-rss-shell-view-extension.c
src/modules/spamassassin/evolution-spamassassin.c
src/modules/spamassassin/org.gnome.Evolution-spamassassin.metainfo.xml.in
src/modules/startup-wizard/e-mail-config-import-page.c

View File

@ -85,6 +85,7 @@ add_subdirectory(offline-alert)
add_subdirectory(plugin-lib)
add_subdirectory(plugin-manager)
add_subdirectory(prefer-plain)
add_subdirectory(rss)
add_subdirectory(settings)
add_subdirectory(startup-wizard)
add_subdirectory(vcard-inline)

View File

@ -0,0 +1,2 @@
add_subdirectory(camel)
add_subdirectory(evolution)

View File

@ -0,0 +1,852 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* SPDX-FileCopyrightText: (C) 2022 Red Hat (www.redhat.com)
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "evolution-config.h"
#include <errno.h>
#include <glib.h>
#include <glib/gstdio.h>
#include <libedataserver/libedataserver.h>
#include "camel-rss-store-summary.h"
struct _CamelRssStoreSummaryPrivate {
GRecMutex mutex;
gboolean dirty;
gchar *filename;
GHashTable *feeds; /* gchar *uid ~> RssFeed * */
};
enum {
FEED_CHANGED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL];
G_DEFINE_TYPE_WITH_PRIVATE (CamelRssStoreSummary, camel_rss_store_summary, G_TYPE_OBJECT)
typedef struct _RssFeed {
guint index; /* to preserve order of adding */
gchar *href;
gchar *display_name;
gchar *icon_filename;
CamelRssContentType content_type;
guint32 total_count;
guint32 unread_count;
gint64 last_updated;
} RssFeed;
static void
rss_feed_free (gpointer ptr)
{
RssFeed *feed = ptr;
if (feed) {
g_free (feed->href);
g_free (feed->display_name);
g_free (feed->icon_filename);
g_free (feed);
}
}
typedef struct _EmitIdleData {
GWeakRef *weak_ref;
gchar *id;
} EmitIdleData;
static void
emit_idle_data_free (gpointer ptr)
{
EmitIdleData *eid = ptr;
if (eid) {
e_weak_ref_free (eid->weak_ref);
g_free (eid->id);
g_slice_free (EmitIdleData, eid);
}
}
static gboolean
camel_rss_store_summary_emit_feed_changed_cb (gpointer user_data)
{
EmitIdleData *eid = user_data;
CamelRssStoreSummary *self;
self = g_weak_ref_get (eid->weak_ref);
if (self) {
g_signal_emit (self, signals[FEED_CHANGED], 0, eid->id, NULL);
g_object_unref (self);
}
return G_SOURCE_REMOVE;
}
static void
camel_rss_store_summary_schedule_feed_changed (CamelRssStoreSummary *self,
const gchar *id)
{
EmitIdleData *eid;
eid = g_slice_new (EmitIdleData);
eid->weak_ref = e_weak_ref_new (self);
eid->id = g_strdup (id);
g_idle_add_full (G_PRIORITY_HIGH,
camel_rss_store_summary_emit_feed_changed_cb,
eid, emit_idle_data_free);
}
static void
rss_store_summary_finalize (GObject *object)
{
CamelRssStoreSummary *self = CAMEL_RSS_STORE_SUMMARY (object);
g_hash_table_destroy (self->priv->feeds);
g_free (self->priv->filename);
g_rec_mutex_clear (&self->priv->mutex);
/* Chain up to parent's method. */
G_OBJECT_CLASS (camel_rss_store_summary_parent_class)->finalize (object);
}
static void
camel_rss_store_summary_class_init (CamelRssStoreSummaryClass *klass)
{
GObjectClass *object_class;
object_class = G_OBJECT_CLASS (klass);
object_class->finalize = rss_store_summary_finalize;
signals[FEED_CHANGED] = g_signal_new (
"feed-changed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_LAST |
G_SIGNAL_ACTION,
0,
NULL, NULL, NULL,
G_TYPE_NONE, 1,
G_TYPE_STRING);
}
static void
camel_rss_store_summary_init (CamelRssStoreSummary *self)
{
self->priv = camel_rss_store_summary_get_instance_private (self);
self->priv->dirty = FALSE;
self->priv->feeds = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, rss_feed_free);
g_rec_mutex_init (&self->priv->mutex);
}
CamelRssStoreSummary *
camel_rss_store_summary_new (const gchar *filename)
{
CamelRssStoreSummary *self = g_object_new (CAMEL_TYPE_RSS_STORE_SUMMARY, NULL);
self->priv->filename = g_strdup (filename);
return self;
}
void
camel_rss_store_summary_lock (CamelRssStoreSummary *self)
{
g_return_if_fail (CAMEL_IS_RSS_STORE_SUMMARY (self));
g_rec_mutex_lock (&self->priv->mutex);
}
void
camel_rss_store_summary_unlock (CamelRssStoreSummary *self)
{
g_return_if_fail (CAMEL_IS_RSS_STORE_SUMMARY (self));
g_rec_mutex_unlock (&self->priv->mutex);
}
static gint
compare_feeds_by_index (gconstpointer fd1,
gconstpointer fd2)
{
const RssFeed *feed1 = fd1, *feed2 = fd2;
if (!feed1 || !feed2)
return 0;
return feed1->index - feed2->index;
}
gboolean
camel_rss_store_summary_load (CamelRssStoreSummary *self,
GError **error)
{
GKeyFile *key_file;
GError *local_error = NULL;
gboolean success;
g_return_val_if_fail (CAMEL_IS_RSS_STORE_SUMMARY (self), FALSE);
camel_rss_store_summary_lock (self);
g_hash_table_remove_all (self->priv->feeds);
key_file = g_key_file_new ();
success = g_key_file_load_from_file (key_file, self->priv->filename, G_KEY_FILE_NONE, &local_error);
if (success) {
GSList *feeds = NULL, *link;
gchar **groups;
guint ii;
groups = g_key_file_get_groups (key_file, NULL);
for (ii = 0; groups && groups[ii]; ii++) {
const gchar *group = groups[ii];
if (g_str_has_prefix (group, "feed:")) {
RssFeed *feed;
feed = g_new0 (RssFeed, 1);
feed->href = g_key_file_get_string (key_file, group, "href", NULL);
feed->display_name = g_key_file_get_string (key_file, group, "display-name", NULL);
feed->icon_filename = g_key_file_get_string (key_file, group, "icon-filename", NULL);
feed->content_type = g_key_file_get_integer (key_file, group, "content-type", NULL);
feed->total_count = (guint32) g_key_file_get_uint64 (key_file, group, "total-count", NULL);
feed->unread_count = (guint32) g_key_file_get_uint64 (key_file, group, "unread-count", NULL);
feed->last_updated = g_key_file_get_int64 (key_file, group, "last-updated", NULL);
feed->index = (gint) g_key_file_get_int64 (key_file, group, "index", NULL);
if (feed->href && *feed->href && feed->display_name && *feed->display_name) {
if (feed->icon_filename && !*feed->icon_filename)
g_clear_pointer (&feed->icon_filename, g_free);
g_hash_table_insert (self->priv->feeds, g_strdup (group + 5 /* strlen ("feed:") */), feed);
feeds = g_slist_prepend (feeds, feed);
} else {
rss_feed_free (feed);
}
}
}
/* renumber indexes on load */
feeds = g_slist_sort (feeds, compare_feeds_by_index);
for (ii = 1, link = feeds; link; ii++, link = g_slist_next (link)) {
RssFeed *feed = link->data;
feed->index = ii;
}
g_slist_free (feeds);
g_strfreev (groups);
} else {
if (g_error_matches (local_error, G_FILE_ERROR, G_FILE_ERROR_NOENT)) {
success = TRUE;
g_clear_error (&local_error);
} else {
g_propagate_error (error, local_error);
}
}
g_key_file_free (key_file);
self->priv->dirty = FALSE;
camel_rss_store_summary_unlock (self);
return success;
}
gboolean
camel_rss_store_summary_save (CamelRssStoreSummary *self,
GError **error)
{
gboolean success = TRUE;
g_return_val_if_fail (CAMEL_IS_RSS_STORE_SUMMARY (self), FALSE);
camel_rss_store_summary_lock (self);
if (self->priv->dirty) {
GKeyFile *key_file;
GHashTableIter iter;
gpointer key, value;
key_file = g_key_file_new ();
g_hash_table_iter_init (&iter, self->priv->feeds);
while (g_hash_table_iter_next (&iter, &key, &value)) {
const gchar *id = key;
const RssFeed *feed = value;
gchar *group = g_strconcat ("feed:", id, NULL);
g_key_file_set_string (key_file, group, "href", feed->href);
g_key_file_set_string (key_file, group, "display-name", feed->display_name);
g_key_file_set_string (key_file, group, "icon-filename", feed->icon_filename ? feed->icon_filename : "");
g_key_file_set_integer (key_file, group, "content-type", feed->content_type);
g_key_file_set_uint64 (key_file, group, "total-count", feed->total_count);
g_key_file_set_uint64 (key_file, group, "unread-count", feed->unread_count);
g_key_file_set_int64 (key_file, group, "last-updated", feed->last_updated);
g_key_file_set_int64 (key_file, group, "index", feed->index);
g_free (group);
}
success = g_key_file_save_to_file (key_file, self->priv->filename, error);
g_key_file_free (key_file);
self->priv->dirty = !success;
}
camel_rss_store_summary_unlock (self);
return success;
}
const gchar *
camel_rss_store_summary_add (CamelRssStoreSummary *self,
const gchar *href,
const gchar *display_name,
const gchar *icon_filename,
CamelRssContentType content_type)
{
RssFeed *feed;
gchar *id;
guint index = 1;
g_return_val_if_fail (CAMEL_IS_RSS_STORE_SUMMARY (self), NULL);
g_return_val_if_fail (href != NULL, NULL);
g_return_val_if_fail (display_name != NULL, NULL);
camel_rss_store_summary_lock (self);
self->priv->dirty = TRUE;
id = g_compute_checksum_for_string (G_CHECKSUM_SHA1, href, -1);
while (g_hash_table_contains (self->priv->feeds, id) && index != 0) {
gchar *tmp;
tmp = g_strdup_printf ("%s::%u", href, index);
g_free (id);
id = g_compute_checksum_for_string (G_CHECKSUM_SHA1, tmp, -1);
g_free (tmp);
index++;
}
feed = g_new0 (RssFeed, 1);
feed->href = g_strdup (href);
feed->display_name = g_strdup (display_name);
feed->icon_filename = g_strdup (icon_filename);
feed->content_type = content_type;
feed->index = g_hash_table_size (self->priv->feeds) + 1;
g_hash_table_insert (self->priv->feeds, id, feed);
camel_rss_store_summary_unlock (self);
camel_rss_store_summary_schedule_feed_changed (self, id);
return id;
}
static void
camel_rss_store_summary_maybe_remove_filename (CamelRssStoreSummary *self,
const gchar *filename)
{
if (filename && *filename) {
gchar *prefix, *dirsep;
prefix = g_strdup (self->priv->filename);
dirsep = strrchr (prefix, G_DIR_SEPARATOR);
if (dirsep) {
dirsep[1] = '\0';
if (g_str_has_prefix (filename, prefix) &&
g_unlink (filename) == -1) {
gint errn = errno;
if (errn != ENOENT && camel_debug ("rss"))
g_printerr ("%s: Failed to delete '%s': %s", G_STRFUNC, filename, g_strerror (errn));
}
}
g_free (prefix);
}
}
gboolean
camel_rss_store_summary_remove (CamelRssStoreSummary *self,
const gchar *id)
{
RssFeed *feed;
gboolean result = FALSE;
g_return_val_if_fail (CAMEL_IS_RSS_STORE_SUMMARY (self), FALSE);
g_return_val_if_fail (id != NULL, FALSE);
camel_rss_store_summary_lock (self);
feed = g_hash_table_lookup (self->priv->feeds, id);
if (feed) {
guint removed_index = feed->index;
camel_rss_store_summary_maybe_remove_filename (self, feed->icon_filename);
result = g_hash_table_remove (self->priv->feeds, id);
/* Correct indexes of the left feeds */
if (result) {
GHashTableIter iter;
gpointer value;
g_hash_table_iter_init (&iter, self->priv->feeds);
while (g_hash_table_iter_next (&iter, NULL, &value)) {
RssFeed *feed2 = value;
if (feed2 && feed2->index > removed_index)
feed2->index--;
}
}
}
if (result)
self->priv->dirty = TRUE;
camel_rss_store_summary_unlock (self);
if (result)
camel_rss_store_summary_schedule_feed_changed (self, id);
return result;
}
gboolean
camel_rss_store_summary_contains (CamelRssStoreSummary *self,
const gchar *id)
{
gboolean result = FALSE;
g_return_val_if_fail (CAMEL_IS_RSS_STORE_SUMMARY (self), FALSE);
g_return_val_if_fail (id != NULL, FALSE);
camel_rss_store_summary_lock (self);
result = g_hash_table_contains (self->priv->feeds, id);
camel_rss_store_summary_unlock (self);
return result;
}
static gint
compare_ids_by_index (gconstpointer id1,
gconstpointer id2,
gpointer user_data)
{
GHashTable *feeds = user_data;
RssFeed *feed1, *feed2;
feed1 = g_hash_table_lookup (feeds, id1);
feed2 = g_hash_table_lookup (feeds, id2);
if (!feed1 || !feed2)
return 0;
return feed1->index - feed2->index;
}
GSList * /* gchar *id */
camel_rss_store_summary_dup_feeds (CamelRssStoreSummary *self)
{
GSList *ids = NULL;
GHashTableIter iter;
gpointer key;
g_return_val_if_fail (CAMEL_IS_RSS_STORE_SUMMARY (self), NULL);
camel_rss_store_summary_lock (self);
g_hash_table_iter_init (&iter, self->priv->feeds);
while (g_hash_table_iter_next (&iter, &key, NULL)) {
ids = g_slist_prepend (ids, g_strdup (key));
}
ids = g_slist_sort_with_data (ids, compare_ids_by_index, self->priv->feeds);
camel_rss_store_summary_unlock (self);
return ids;
}
CamelFolderInfo *
camel_rss_store_summary_dup_folder_info (CamelRssStoreSummary *self,
const gchar *id)
{
RssFeed *feed;
CamelFolderInfo *fi = NULL;
g_return_val_if_fail (CAMEL_IS_RSS_STORE_SUMMARY (self), NULL);
g_return_val_if_fail (id != NULL, NULL);
camel_rss_store_summary_lock (self);
feed = g_hash_table_lookup (self->priv->feeds, id);
if (feed) {
fi = camel_folder_info_new ();
fi->full_name = g_strdup (id);
fi->display_name = g_strdup (feed->display_name);
fi->flags = CAMEL_FOLDER_NOCHILDREN;
fi->unread = feed->unread_count;
fi->total = feed->total_count;
}
camel_rss_store_summary_unlock (self);
return fi;
}
CamelFolderInfo *
camel_rss_store_summary_dup_folder_info_for_display_name (CamelRssStoreSummary *self,
const gchar *display_name)
{
CamelFolderInfo *fi = NULL;
GHashTableIter iter;
gpointer key, value;
g_return_val_if_fail (CAMEL_IS_RSS_STORE_SUMMARY (self), NULL);
g_return_val_if_fail (display_name != NULL, NULL);
camel_rss_store_summary_lock (self);
g_hash_table_iter_init (&iter, self->priv->feeds);
while (g_hash_table_iter_next (&iter, &key, &value)) {
const gchar *id = key;
RssFeed *feed = value;
if (g_strcmp0 (display_name, feed->display_name) == 0) {
fi = camel_rss_store_summary_dup_folder_info (self, id);
break;
}
}
camel_rss_store_summary_unlock (self);
return fi;
}
const gchar *
camel_rss_store_summary_get_href (CamelRssStoreSummary *self,
const gchar *id)
{
RssFeed *feed;
const gchar *result = NULL;
g_return_val_if_fail (CAMEL_IS_RSS_STORE_SUMMARY (self), NULL);
g_return_val_if_fail (id != NULL, NULL);
camel_rss_store_summary_lock (self);
feed = g_hash_table_lookup (self->priv->feeds, id);
if (feed)
result = feed->href;
camel_rss_store_summary_unlock (self);
return result;
}
const gchar *
camel_rss_store_summary_get_display_name (CamelRssStoreSummary *self,
const gchar *id)
{
RssFeed *feed;
const gchar *result = NULL;
g_return_val_if_fail (CAMEL_IS_RSS_STORE_SUMMARY (self), NULL);
g_return_val_if_fail (id != NULL, NULL);
camel_rss_store_summary_lock (self);
feed = g_hash_table_lookup (self->priv->feeds, id);
if (feed)
result = feed->display_name;
camel_rss_store_summary_unlock (self);
return result;
}
void
camel_rss_store_summary_set_display_name (CamelRssStoreSummary *self,
const gchar *id,
const gchar *display_name)
{
RssFeed *feed;
gboolean changed = FALSE;
g_return_if_fail (CAMEL_IS_RSS_STORE_SUMMARY (self));
g_return_if_fail (id != NULL);
g_return_if_fail (display_name && *display_name);
camel_rss_store_summary_lock (self);
feed = g_hash_table_lookup (self->priv->feeds, id);
if (feed) {
if (g_strcmp0 (feed->display_name, display_name) != 0) {
g_free (feed->display_name);
feed->display_name = g_strdup (display_name);
self->priv->dirty = TRUE;
changed = TRUE;
}
}
camel_rss_store_summary_unlock (self);
if (changed)
camel_rss_store_summary_schedule_feed_changed (self, id);
}
const gchar *
camel_rss_store_summary_get_icon_filename (CamelRssStoreSummary *self,
const gchar *id)
{
RssFeed *feed;
const gchar *result = NULL;
g_return_val_if_fail (CAMEL_IS_RSS_STORE_SUMMARY (self), NULL);
g_return_val_if_fail (id != NULL, NULL);
camel_rss_store_summary_lock (self);
feed = g_hash_table_lookup (self->priv->feeds, id);
if (feed)
result = feed->icon_filename;
camel_rss_store_summary_unlock (self);
return result;
}
void
camel_rss_store_summary_set_icon_filename (CamelRssStoreSummary *self,
const gchar *id,
const gchar *icon_filename)
{
RssFeed *feed;
gboolean changed = FALSE;
g_return_if_fail (CAMEL_IS_RSS_STORE_SUMMARY (self));
g_return_if_fail (id != NULL);
camel_rss_store_summary_lock (self);
feed = g_hash_table_lookup (self->priv->feeds, id);
if (feed) {
if (g_strcmp0 (feed->icon_filename, icon_filename) != 0) {
camel_rss_store_summary_maybe_remove_filename (self, feed->icon_filename);
g_free (feed->icon_filename);
feed->icon_filename = g_strdup (icon_filename);
self->priv->dirty = TRUE;
changed = TRUE;
}
}
camel_rss_store_summary_unlock (self);
if (changed)
camel_rss_store_summary_schedule_feed_changed (self, id);
}
CamelRssContentType
camel_rss_store_summary_get_content_type (CamelRssStoreSummary *self,
const gchar *id)
{
RssFeed *feed;
CamelRssContentType result = CAMEL_RSS_CONTENT_TYPE_HTML;
g_return_val_if_fail (CAMEL_IS_RSS_STORE_SUMMARY (self), result);
g_return_val_if_fail (id != NULL, result);
camel_rss_store_summary_lock (self);
feed = g_hash_table_lookup (self->priv->feeds, id);
if (feed)
result = feed->content_type;
camel_rss_store_summary_unlock (self);
return result;
}
void
camel_rss_store_summary_set_content_type (CamelRssStoreSummary *self,
const gchar *id,
CamelRssContentType content_type)
{
RssFeed *feed;
gboolean changed = FALSE;
g_return_if_fail (CAMEL_IS_RSS_STORE_SUMMARY (self));
g_return_if_fail (id != NULL);
camel_rss_store_summary_lock (self);
feed = g_hash_table_lookup (self->priv->feeds, id);
if (feed) {
if (feed->content_type != content_type) {
feed->content_type = content_type;
self->priv->dirty = TRUE;
changed = TRUE;
}
}
camel_rss_store_summary_unlock (self);
if (changed)
camel_rss_store_summary_schedule_feed_changed (self, id);
}
guint32
camel_rss_store_summary_get_total_count (CamelRssStoreSummary *self,
const gchar *id)
{
RssFeed *feed;
guint32 result = 0;
g_return_val_if_fail (CAMEL_IS_RSS_STORE_SUMMARY (self), 0);
g_return_val_if_fail (id != NULL, 0);
camel_rss_store_summary_lock (self);
feed = g_hash_table_lookup (self->priv->feeds, id);
if (feed)
result = feed->total_count;
camel_rss_store_summary_unlock (self);
return result;
}
void
camel_rss_store_summary_set_total_count (CamelRssStoreSummary *self,
const gchar *id,
guint32 total_count)
{
RssFeed *feed;
g_return_if_fail (CAMEL_IS_RSS_STORE_SUMMARY (self));
g_return_if_fail (id != NULL);
camel_rss_store_summary_lock (self);
feed = g_hash_table_lookup (self->priv->feeds, id);
if (feed) {
if (feed->total_count != total_count) {
feed->total_count = total_count;
self->priv->dirty = TRUE;
}
}
camel_rss_store_summary_unlock (self);
}
guint32
camel_rss_store_summary_get_unread_count (CamelRssStoreSummary *self,
const gchar *id)
{
RssFeed *feed;
guint32 result = 0;
g_return_val_if_fail (CAMEL_IS_RSS_STORE_SUMMARY (self), 0);
g_return_val_if_fail (id != NULL, 0);
camel_rss_store_summary_lock (self);
feed = g_hash_table_lookup (self->priv->feeds, id);
if (feed)
result = feed->unread_count;
camel_rss_store_summary_unlock (self);
return result;
}
void
camel_rss_store_summary_set_unread_count (CamelRssStoreSummary *self,
const gchar *id,
guint32 unread_count)
{
RssFeed *feed;
g_return_if_fail (CAMEL_IS_RSS_STORE_SUMMARY (self));
g_return_if_fail (id != NULL);
camel_rss_store_summary_lock (self);
feed = g_hash_table_lookup (self->priv->feeds, id);
if (feed) {
if (feed->unread_count != unread_count) {
feed->unread_count = unread_count;
self->priv->dirty = TRUE;
}
}
camel_rss_store_summary_unlock (self);
}
gint64
camel_rss_store_summary_get_last_updated (CamelRssStoreSummary *self,
const gchar *id)
{
RssFeed *feed;
gint64 result = 0;
g_return_val_if_fail (CAMEL_IS_RSS_STORE_SUMMARY (self), 0);
g_return_val_if_fail (id != NULL, 0);
camel_rss_store_summary_lock (self);
feed = g_hash_table_lookup (self->priv->feeds, id);
if (feed)
result = feed->last_updated;
camel_rss_store_summary_unlock (self);
return result;
}
void
camel_rss_store_summary_set_last_updated (CamelRssStoreSummary *self,
const gchar *id,
gint64 last_updated)
{
RssFeed *feed;
g_return_if_fail (CAMEL_IS_RSS_STORE_SUMMARY (self));
g_return_if_fail (id != NULL);
camel_rss_store_summary_lock (self);
feed = g_hash_table_lookup (self->priv->feeds, id);
if (feed) {
if (feed->last_updated != last_updated) {
feed->last_updated = last_updated;
self->priv->dirty = TRUE;
}
}
camel_rss_store_summary_unlock (self);
}

View File

@ -0,0 +1,116 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* SPDX-FileCopyrightText: (C) 2022 Red Hat (www.redhat.com)
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef CAMEL_RSS_STORE_SUMMARY_H
#define CAMEL_RSS_STORE_SUMMARY_H
#include <camel/camel.h>
/* Standard GObject macros */
#define CAMEL_TYPE_RSS_STORE_SUMMARY \
(camel_rss_store_summary_get_type ())
#define CAMEL_RSS_STORE_SUMMARY(obj) \
(G_TYPE_CHECK_INSTANCE_CAST \
((obj), CAMEL_TYPE_RSS_STORE_SUMMARY, CamelRssStoreSummary))
#define CAMEL_RSS_STORE_SUMMARY_CLASS(cls) \
(G_TYPE_CHECK_CLASS_CAST \
((cls), CAMEL_TYPE_RSS_STORE_SUMMARY, CamelRssStoreSummaryClass))
#define CAMEL_IS_RSS_STORE_SUMMARY(obj) \
(G_TYPE_CHECK_INSTANCE_TYPE \
((obj), CAMEL_TYPE_RSS_STORE_SUMMARY))
#define CAMEL_IS_RSS_STORE_SUMMARY_CLASS(cls) \
(G_TYPE_CHECK_CLASS_TYPE \
((cls), CAMEL_TYPE_RSS_STORE_SUMMARY))
#define CAMEL_RSS_STORE_SUMMARY_GET_CLASS(obj) \
(G_TYPE_INSTANCE_GET_CLASS \
((obj), CAMEL_TYPE_RSS_STORE_SUMMARY, CamelRssStoreSummaryClass))
G_BEGIN_DECLS
typedef enum {
CAMEL_RSS_CONTENT_TYPE_HTML,
CAMEL_RSS_CONTENT_TYPE_PLAIN_TEXT,
CAMEL_RSS_CONTENT_TYPE_MARKDOWN
} CamelRssContentType;
typedef struct _CamelRssStoreSummary CamelRssStoreSummary;
typedef struct _CamelRssStoreSummaryClass CamelRssStoreSummaryClass;
typedef struct _CamelRssStoreSummaryPrivate CamelRssStoreSummaryPrivate;
struct _CamelRssStoreSummary {
GObject object;
CamelRssStoreSummaryPrivate *priv;
};
struct _CamelRssStoreSummaryClass {
GObjectClass object_class;
};
GType camel_rss_store_summary_get_type (void);
CamelRssStoreSummary *
camel_rss_store_summary_new (const gchar *filename);
void camel_rss_store_summary_lock (CamelRssStoreSummary *self);
void camel_rss_store_summary_unlock (CamelRssStoreSummary *self);
gboolean camel_rss_store_summary_load (CamelRssStoreSummary *self,
GError **error);
gboolean camel_rss_store_summary_save (CamelRssStoreSummary *self,
GError **error);
const gchar * camel_rss_store_summary_add (CamelRssStoreSummary *self,
const gchar *href,
const gchar *display_name,
const gchar *icon_filename,
CamelRssContentType content_type);
gboolean camel_rss_store_summary_remove (CamelRssStoreSummary *self,
const gchar *id);
gboolean camel_rss_store_summary_contains (CamelRssStoreSummary *self,
const gchar *id);
GSList * camel_rss_store_summary_dup_feeds (CamelRssStoreSummary *self); /* gchar *id */
CamelFolderInfo *
camel_rss_store_summary_dup_folder_info (CamelRssStoreSummary *self,
const gchar *id);
CamelFolderInfo *
camel_rss_store_summary_dup_folder_info_for_display_name
(CamelRssStoreSummary *self,
const gchar *display_name);
const gchar * camel_rss_store_summary_get_href (CamelRssStoreSummary *self,
const gchar *id);
const gchar * camel_rss_store_summary_get_display_name(CamelRssStoreSummary *self,
const gchar *id);
void camel_rss_store_summary_set_display_name(CamelRssStoreSummary *self,
const gchar *id,
const gchar *display_name);
const gchar * camel_rss_store_summary_get_icon_filename
(CamelRssStoreSummary *self,
const gchar *id);
void camel_rss_store_summary_set_icon_filename
(CamelRssStoreSummary *self,
const gchar *id,
const gchar *filename);
CamelRssContentType
camel_rss_store_summary_get_content_type(CamelRssStoreSummary *self,
const gchar *id);
void camel_rss_store_summary_set_content_type(CamelRssStoreSummary *self,
const gchar *id,
CamelRssContentType content_type);
guint32 camel_rss_store_summary_get_total_count (CamelRssStoreSummary *self,
const gchar *id);
void camel_rss_store_summary_set_total_count (CamelRssStoreSummary *self,
const gchar *id,
guint32 total_count);
guint32 camel_rss_store_summary_get_unread_count(CamelRssStoreSummary *self,
const gchar *id);
void camel_rss_store_summary_set_unread_count(CamelRssStoreSummary *self,
const gchar *id,
guint32 unread_count);
gint64 camel_rss_store_summary_get_last_updated(CamelRssStoreSummary *self,
const gchar *id);
void camel_rss_store_summary_set_last_updated(CamelRssStoreSummary *self,
const gchar *id,
gint64 last_updated);
G_END_DECLS
#endif /* CAMEL_RSS_STORE_SUMMARY_H */

View File

@ -0,0 +1,50 @@
pkg_check_modules(LIBEDATASERVER libedataserver-1.2)
pkg_check_modules(CAMEL camel-1.2)
pkg_check_variable(camel_providerdir camel-1.2 camel_providerdir)
set(sources
camel-rss-folder.c
camel-rss-folder.h
camel-rss-folder-summary.c
camel-rss-folder-summary.h
camel-rss-provider.c
camel-rss-settings.c
camel-rss-settings.h
camel-rss-store.c
camel-rss-store.h
../camel-rss-store-summary.c
../camel-rss-store-summary.h
../e-rss-parser.h
../e-rss-parser.c
)
add_library(camelrss MODULE ${sources})
target_compile_definitions(camelrss PRIVATE
-DG_LOG_DOMAIN=\"camel-rss-provider\"
)
target_compile_options(camelrss PUBLIC
${CAMEL_CFLAGS}
${LIBEDATASERVER_CFLAGS}
)
target_include_directories(camelrss PUBLIC
${CMAKE_BINARY_DIR}
${CMAKE_CURRENT_SOURCE_DIR}/..
${CAMEL_INCLUDE_DIRS}
${LIBEDATASERVER_INCLUDE_DIRS}
)
target_link_libraries(camelrss
${CAMEL_LDFLAGS}
${LIBEDATASERVER_LDFLAGS}
)
install(TARGETS camelrss
DESTINATION ${camel_providerdir}
)
install(FILES libcamelrss.urls
DESTINATION ${camel_providerdir}
)

View File

@ -0,0 +1,412 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* SPDX-FileCopyrightText: (C) 2022 Red Hat (www.redhat.com)
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "evolution-config.h"
#include <glib/gi18n-lib.h>
#include <camel/camel.h>
#include <libedataserver/libedataserver.h>
#include "camel-rss-folder.h"
#include "camel-rss-store.h"
#include "camel-rss-folder-summary.h"
struct _CamelRssFolderSummaryPrivate {
gulong saved_count_id;
gulong unread_count_id;
};
G_DEFINE_TYPE_WITH_PRIVATE (CamelRssFolderSummary, camel_rss_folder_summary, CAMEL_TYPE_FOLDER_SUMMARY)
static void
rss_folder_summary_sync_counts_cb (GObject *object,
GParamSpec *param,
gpointer user_data)
{
CamelRssFolderSummary *self = CAMEL_RSS_FOLDER_SUMMARY (object);
CamelFolder *folder;
CamelStore *parent_store;
CamelRssStoreSummary *rss_store_summary;
const gchar *id;
folder = camel_folder_summary_get_folder (CAMEL_FOLDER_SUMMARY (self));
parent_store = camel_folder_get_parent_store (folder);
if (!parent_store)
return;
rss_store_summary = camel_rss_store_get_summary (CAMEL_RSS_STORE (parent_store));
if (!rss_store_summary)
return;
id = camel_rss_folder_get_id (CAMEL_RSS_FOLDER (folder));
if (g_strcmp0 (g_param_spec_get_name (param), "saved-count") == 0)
camel_rss_store_summary_set_total_count (rss_store_summary, id, camel_folder_summary_get_saved_count (CAMEL_FOLDER_SUMMARY (self)));
else if (g_strcmp0 (g_param_spec_get_name (param), "unread-count") == 0)
camel_rss_store_summary_set_unread_count (rss_store_summary, id, camel_folder_summary_get_unread_count (CAMEL_FOLDER_SUMMARY (self)));
}
static void
rss_folder_summary_constructed (GObject *object)
{
CamelRssFolderSummary *self = CAMEL_RSS_FOLDER_SUMMARY (object);
/* Chain up to parent's method. */
G_OBJECT_CLASS (camel_rss_folder_summary_parent_class)->constructed (object);
self->priv->saved_count_id = g_signal_connect (self, "notify::saved-count",
G_CALLBACK (rss_folder_summary_sync_counts_cb), NULL);
self->priv->unread_count_id = g_signal_connect (self, "notify::unread-count",
G_CALLBACK (rss_folder_summary_sync_counts_cb), NULL);
}
static void
rss_folder_summary_dispose (GObject *object)
{
CamelRssFolderSummary *self = CAMEL_RSS_FOLDER_SUMMARY (object);
if (self->priv->saved_count_id) {
g_signal_handler_disconnect (self, self->priv->saved_count_id);
self->priv->saved_count_id = 0;
}
if (self->priv->unread_count_id) {
g_signal_handler_disconnect (self, self->priv->unread_count_id);
self->priv->unread_count_id = 0;
}
/* Chain up to parent's method. */
G_OBJECT_CLASS (camel_rss_folder_summary_parent_class)->dispose (object);
}
static void
camel_rss_folder_summary_class_init (CamelRssFolderSummaryClass *class)
{
GObjectClass *object_class;
object_class = G_OBJECT_CLASS (class);
object_class->constructed = rss_folder_summary_constructed;
object_class->dispose = rss_folder_summary_dispose;
}
static void
camel_rss_folder_summary_init (CamelRssFolderSummary *rss_folder_summary)
{
rss_folder_summary->priv = camel_rss_folder_summary_get_instance_private (rss_folder_summary);
}
CamelFolderSummary *
camel_rss_folder_summary_new (CamelFolder *folder)
{
return g_object_new (CAMEL_TYPE_RSS_FOLDER_SUMMARY, "folder", folder, NULL);
}
CamelMimeMessage *
camel_rss_folder_summary_dup_message (CamelRssFolderSummary *self,
const gchar *uid,
CamelDataCache **out_rss_cache,
CamelRssContentType *out_content_type,
GCancellable *cancellable,
GError **error)
{
CamelFolder *folder;
CamelDataCache *rss_cache;
CamelMimeMessage *message = NULL;
CamelRssStore *rss_store;
GIOStream *base_stream;
g_return_val_if_fail (CAMEL_IS_RSS_FOLDER_SUMMARY (self), NULL);
g_return_val_if_fail (uid != NULL, NULL);
folder = camel_folder_summary_get_folder (CAMEL_FOLDER_SUMMARY (self));
rss_store = CAMEL_RSS_STORE (camel_folder_get_parent_store (folder));
if (out_content_type) {
*out_content_type = camel_rss_store_summary_get_content_type (
camel_rss_store_get_summary (rss_store),
camel_rss_folder_get_id (CAMEL_RSS_FOLDER (folder)));
}
rss_cache = camel_rss_store_get_cache (rss_store);
base_stream = camel_data_cache_get (rss_cache, camel_rss_folder_get_id (CAMEL_RSS_FOLDER (folder)), uid, error);
if (base_stream) {
CamelStream *stream;
stream = camel_stream_new (base_stream);
g_object_unref (base_stream);
message = camel_mime_message_new ();
if (!camel_data_wrapper_construct_from_stream_sync (CAMEL_DATA_WRAPPER (message), stream, cancellable, error)) {
g_object_unref (message);
message = NULL;
}
g_object_unref (stream);
}
if (out_rss_cache)
*out_rss_cache = g_object_ref (rss_cache);
return message;
}
gboolean
camel_rss_folder_summary_add_or_update_feed_sync (CamelRssFolderSummary *self,
const gchar *href,
ERssFeed *feed,
GBytes *complete_article,
CamelFolderChangeInfo **inout_changes,
GCancellable *cancellable,
GError **error)
{
CamelDataCache *rss_cache = NULL;
CamelDataWrapper *body_wrapper;
CamelMimeMessage *message;
CamelRssContentType content_type = CAMEL_RSS_CONTENT_TYPE_HTML;
gchar *uid, *received, *received_tm;
GSList *link;
gboolean has_downloaded_eclosure = FALSE;
gboolean existing_message;
gboolean success = TRUE;
g_return_val_if_fail (CAMEL_IS_RSS_FOLDER_SUMMARY (self), FALSE);
g_return_val_if_fail (href != NULL, FALSE);
g_return_val_if_fail (feed != NULL, FALSE);
g_return_val_if_fail (feed->link != NULL, FALSE);
g_return_val_if_fail (inout_changes != NULL, FALSE);
uid = g_compute_checksum_for_string (G_CHECKSUM_SHA1, feed->id ? feed->id : feed->link, -1);
g_return_val_if_fail (uid != NULL, FALSE);
message = camel_rss_folder_summary_dup_message (self, uid, &rss_cache, &content_type, cancellable, NULL);
existing_message = camel_folder_summary_get_info_flags (CAMEL_FOLDER_SUMMARY (self), uid) != (~0);
if (!existing_message)
g_clear_object (&message);
if (!message) {
gchar *msg_id;
msg_id = g_strconcat (uid, "@localhost", NULL);
message = camel_mime_message_new ();
camel_mime_message_set_message_id (message, msg_id);
camel_mime_message_set_date (message, feed->last_modified, 0);
camel_medium_set_header (CAMEL_MEDIUM (message), "From", feed->author);
camel_medium_set_header (CAMEL_MEDIUM (message), "X-RSS-Feed", href);
g_free (msg_id);
}
camel_mime_message_set_subject (message, feed->title);
received_tm = camel_header_format_date (time (NULL), 0);
received = g_strconcat ("from ", href, " by localhost; ", received_tm, NULL);
camel_medium_add_header (CAMEL_MEDIUM (message), "Received", received);
for (link = feed->enclosures; link && !has_downloaded_eclosure; link = g_slist_next (link)) {
ERssEnclosure *enclosure = link->data;
if (enclosure->data && g_bytes_get_size (enclosure->data) > 0)
has_downloaded_eclosure = TRUE;
}
body_wrapper = camel_data_wrapper_new ();
if (complete_article && g_bytes_get_size (complete_article) > 0) {
camel_data_wrapper_set_encoding (body_wrapper, CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE);
camel_data_wrapper_set_mime_type (body_wrapper, "text/html; charset=utf-8");
success = camel_data_wrapper_construct_from_data_sync (body_wrapper, g_bytes_get_data (complete_article, NULL), g_bytes_get_size (complete_article), cancellable, error);
} else {
GString *body;
const gchar *ct;
gboolean first_enclosure = TRUE;
body = g_string_new (NULL);
if (content_type == CAMEL_RSS_CONTENT_TYPE_PLAIN_TEXT) {
ct = "text/plain; charset=utf-8";
g_string_append (body, feed->link);
g_string_append_c (body, '\n');
g_string_append_c (body, '\n');
} else if (content_type == CAMEL_RSS_CONTENT_TYPE_MARKDOWN) {
ct = "text/markdown; charset=utf-8";
} else {
ct = "text/html; charset=utf-8";
}
if (content_type != CAMEL_RSS_CONTENT_TYPE_PLAIN_TEXT) {
gchar *tmp;
tmp = g_markup_printf_escaped ("<h4><a href=\"%s\">%s</a></h4><div><br></div>", feed->link, feed->title);
g_string_append (body, tmp);
g_free (tmp);
}
if (feed->body)
g_string_append (body, feed->body);
for (link = feed->enclosures; link; link = g_slist_next (link)) {
ERssEnclosure *enclosure = link->data;
gchar *tmp;
if (enclosure->data && g_bytes_get_size (enclosure->data) > 0)
continue;
if (first_enclosure) {
first_enclosure = FALSE;
g_string_append (body, "<br><hr><br>\n");
g_string_append (body, _("Enclosures:"));
g_string_append (body, "<br>\n");
}
tmp = g_markup_printf_escaped ("<div><a href=\"%s\">%s</a></div>\n",
enclosure->href, enclosure->title ? enclosure->title : enclosure->href);
g_string_append (body, tmp);
g_free (tmp);
}
camel_data_wrapper_set_encoding (body_wrapper, CAMEL_TRANSFER_ENCODING_QUOTEDPRINTABLE);
camel_data_wrapper_set_mime_type (body_wrapper, ct);
success = camel_data_wrapper_construct_from_data_sync (body_wrapper, body->str, body->len, cancellable, error);
g_string_free (body, TRUE);
}
if (success && has_downloaded_eclosure) {
CamelMultipart *mixed;
CamelMimePart *subpart;
mixed = camel_multipart_new ();
camel_data_wrapper_set_mime_type (CAMEL_DATA_WRAPPER (mixed), "multipart/mixed");
camel_multipart_set_boundary (mixed, NULL);
subpart = camel_mime_part_new ();
camel_medium_set_content (CAMEL_MEDIUM (subpart), body_wrapper);
camel_multipart_add_part (mixed, subpart);
g_object_unref (subpart);
for (link = feed->enclosures; link; link = g_slist_next (link)) {
ERssEnclosure *enclosure = link->data;
GUri *link_uri;
if (!enclosure->data || !g_bytes_get_size (enclosure->data))
continue;
subpart = camel_mime_part_new ();
camel_mime_part_set_content (subpart, (const gchar *) g_bytes_get_data (enclosure->data, NULL), g_bytes_get_size (enclosure->data),
enclosure->content_type ? enclosure->content_type : "application/octet-stream");
camel_mime_part_set_disposition (subpart, "inline");
link_uri = g_uri_parse (enclosure->href, G_URI_FLAGS_PARSE_RELAXED, NULL);
if (link_uri) {
const gchar *path = g_uri_get_path (link_uri);
const gchar *slash = path ? strrchr (path, '/') : NULL;
if (slash && *slash && slash[1])
camel_mime_part_set_filename (subpart, slash + 1);
g_uri_unref (link_uri);
}
camel_mime_part_set_encoding (subpart, CAMEL_TRANSFER_ENCODING_BASE64);
camel_multipart_add_part (mixed, subpart);
g_object_unref (subpart);
}
g_object_unref (body_wrapper);
body_wrapper = CAMEL_DATA_WRAPPER (mixed);
}
if (CAMEL_IS_MIME_PART (body_wrapper)) {
CamelDataWrapper *content;
CamelMedium *imedium, *omedium;
const CamelNameValueArray *headers;
imedium = CAMEL_MEDIUM (body_wrapper);
omedium = CAMEL_MEDIUM (message);
content = camel_medium_get_content (imedium);
camel_medium_set_content (omedium, content);
camel_data_wrapper_set_encoding (CAMEL_DATA_WRAPPER (omedium), camel_data_wrapper_get_encoding (CAMEL_DATA_WRAPPER (imedium)));
headers = camel_medium_get_headers (imedium);
if (headers) {
gint ii, length;
length = camel_name_value_array_get_length (headers);
for (ii = 0; ii < length; ii++) {
const gchar *header_name = NULL;
const gchar *header_value = NULL;
if (camel_name_value_array_get (headers, ii, &header_name, &header_value))
camel_medium_set_header (omedium, header_name, header_value);
}
}
} else {
camel_medium_set_content (CAMEL_MEDIUM (message), body_wrapper);
}
if (success) {
CamelRssFolder *rss_folder;
GIOStream *io_stream;
rss_folder = CAMEL_RSS_FOLDER (camel_folder_summary_get_folder (CAMEL_FOLDER_SUMMARY (self)));
io_stream = camel_data_cache_add (rss_cache, camel_rss_folder_get_id (rss_folder), uid, error);
success = io_stream != NULL;
if (io_stream) {
success = camel_data_wrapper_write_to_output_stream_sync (CAMEL_DATA_WRAPPER (message),
g_io_stream_get_output_stream (io_stream), cancellable, error);
}
g_clear_object (&io_stream);
}
if (success) {
if (!*inout_changes)
*inout_changes = camel_folder_change_info_new ();
if (existing_message) {
camel_folder_change_info_change_uid (*inout_changes, uid);
} else {
CamelFolderSummary *folder_summary = CAMEL_FOLDER_SUMMARY (self);
CamelMessageInfo *info;
info = camel_folder_summary_info_new_from_message (folder_summary, message);
g_warn_if_fail (info != NULL);
camel_message_info_set_uid (info, uid);
camel_folder_summary_add (folder_summary, info, TRUE);
g_clear_object (&info);
camel_folder_change_info_add_uid (*inout_changes, uid);
camel_folder_change_info_recent_uid (*inout_changes, uid);
}
}
g_clear_object (&rss_cache);
g_clear_object (&body_wrapper);
g_clear_object (&message);
g_free (received_tm);
g_free (received);
g_free (uid);
return success;
}

View File

@ -0,0 +1,69 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* SPDX-FileCopyrightText: (C) 2022 Red Hat (www.redhat.com)
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef CAMEL_RSS_FOLDER_SUMMARY_H
#define CAMEL_RSS_FOLDER_SUMMARY_H
#include <camel/camel.h>
#include "camel-rss-store-summary.h"
#include "e-rss-parser.h"
/* Standard GObject macros */
#define CAMEL_TYPE_RSS_FOLDER_SUMMARY \
(camel_rss_folder_summary_get_type ())
#define CAMEL_RSS_FOLDER_SUMMARY(obj) \
(G_TYPE_CHECK_INSTANCE_CAST \
((obj), CAMEL_TYPE_RSS_FOLDER_SUMMARY, CamelRssFolderSummary))
#define CAMEL_RSS_FOLDER_SUMMARY_CLASS(cls) \
(G_TYPE_CHECK_CLASS_CAST \
((cls), CAMEL_TYPE_RSS_FOLDER_SUMMARY, CamelRssFolderSummaryClass))
#define CAMEL_IS_RSS_FOLDER_SUMMARY(obj) \
(G_TYPE_CHECK_INSTANCE_TYPE \
((obj), CAMEL_TYPE_RSS_FOLDER_SUMMARY))
#define CAMEL_IS_RSS_FOLDER_SUMMARY_CLASS(cls) \
(G_TYPE_CHECK_CLASS_TYPE \
((cls), CAMEL_TYPE_RSS_FOLDER_SUMMARY))
#define CAMEL_RSS_FOLDER_SUMMARY_GET_CLASS(obj) \
(G_TYPE_INSTANCE_GET_CLASS \
((obj), CAMEL_TYPE_RSS_FOLDER_SUMMARY, CamelRssFolderSummaryClass))
G_BEGIN_DECLS
typedef struct _CamelRssFolderSummary CamelRssFolderSummary;
typedef struct _CamelRssFolderSummaryClass CamelRssFolderSummaryClass;
typedef struct _CamelRssFolderSummaryPrivate CamelRssFolderSummaryPrivate;
struct _CamelRssFolderSummary {
CamelFolderSummary parent;
CamelRssFolderSummaryPrivate *priv;
};
struct _CamelRssFolderSummaryClass {
CamelFolderSummaryClass parent_class;
};
GType camel_rss_folder_summary_get_type (void);
CamelFolderSummary *
camel_rss_folder_summary_new (CamelFolder *folder);
CamelMimeMessage *
camel_rss_folder_summary_dup_message (CamelRssFolderSummary *self,
const gchar *uid,
CamelDataCache **out_rss_cache,
CamelRssContentType *out_content_type,
GCancellable *cancellable,
GError **error);
gboolean camel_rss_folder_summary_add_or_update_feed_sync(CamelRssFolderSummary *self,
const gchar *href,
ERssFeed *feed,
GBytes *complete_article,
CamelFolderChangeInfo **inout_changes,
GCancellable *cancellable,
GError **error);
G_END_DECLS
#endif /* CAMEL_RSS_FOLDER_SUMMARY_H */

View File

@ -0,0 +1,778 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* SPDX-FileCopyrightText: (C) 2022 Red Hat (www.redhat.com)
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "evolution-config.h"
#include <glib.h>
#include <glib/gi18n-lib.h>
#include <libsoup/soup.h>
#include "camel-rss-folder-summary.h"
#include "camel-rss-settings.h"
#include "camel-rss-store.h"
#include "camel-rss-store-summary.h"
#include "camel-rss-folder.h"
struct _CamelRssFolderPrivate {
gboolean apply_filters;
CamelThreeState complete_articles;
CamelThreeState feed_enclosures;
gchar *id;
};
/* The custom property ID is a CamelArg artifact.
* It still identifies the property in state files. */
enum {
PROP_0,
PROP_APPLY_FILTERS = 0x2501,
PROP_COMPLETE_ARTICLES = 0x2502,
PROP_FEED_ENCLOSURES = 0x2503
};
G_DEFINE_TYPE_WITH_PRIVATE (CamelRssFolder, camel_rss_folder, CAMEL_TYPE_FOLDER)
static GPtrArray *
rss_folder_search_by_expression (CamelFolder *folder,
const gchar *expression,
GCancellable *cancellable,
GError **error)
{
CamelFolderSearch *search;
GPtrArray *matches;
search = camel_folder_search_new ();
camel_folder_search_set_folder (search, folder);
matches = camel_folder_search_search (search, expression, NULL, cancellable, error);
g_clear_object (&search);
return matches;
}
static guint32
rss_folder_count_by_expression (CamelFolder *folder,
const gchar *expression,
GCancellable *cancellable,
GError **error)
{
CamelFolderSearch *search;
guint32 count;
search = camel_folder_search_new ();
camel_folder_search_set_folder (search, folder);
count = camel_folder_search_count (search, expression, cancellable, error);
g_clear_object (&search);
return count;
}
static GPtrArray *
rss_folder_search_by_uids (CamelFolder *folder,
const gchar *expression,
GPtrArray *uids,
GCancellable *cancellable,
GError **error)
{
CamelFolderSearch *search;
GPtrArray *matches;
if (uids->len == 0)
return g_ptr_array_new ();
search = camel_folder_search_new ();
camel_folder_search_set_folder (search, folder);
matches = camel_folder_search_search (search, expression, uids, cancellable, error);
g_clear_object (&search);
return matches;
}
static void
rss_folder_search_free (CamelFolder *folder,
GPtrArray *result)
{
camel_folder_search_free_result (NULL, result);
}
static gchar *
rss_get_filename (CamelFolder *folder,
const gchar *uid,
GError **error)
{
CamelStore *parent_store;
CamelDataCache *rss_cache;
CamelRssFolder *rss_folder;
CamelRssStore *rss_store;
parent_store = camel_folder_get_parent_store (folder);
rss_folder = CAMEL_RSS_FOLDER (folder);
rss_store = CAMEL_RSS_STORE (parent_store);
rss_cache = camel_rss_store_get_cache (rss_store);
return camel_data_cache_get_filename (rss_cache, rss_folder->priv->id, uid);
}
static gboolean
rss_folder_append_message_sync (CamelFolder *folder,
CamelMimeMessage *message,
CamelMessageInfo *info,
gchar **appended_uid,
GCancellable *cancellable,
GError **error)
{
g_set_error (error, CAMEL_FOLDER_ERROR, CAMEL_FOLDER_ERROR_INVALID,
_("Cannot add message into News and Blogs folder"));
return FALSE;
}
static gboolean
rss_folder_expunge_sync (CamelFolder *folder,
GCancellable *cancellable,
GError **error)
{
CamelDataCache *cache;
CamelFolderSummary *summary;
CamelFolderChangeInfo *changes;
CamelRssFolder *rss_folder;
CamelStore *store;
GPtrArray *known_uids;
GList *to_remove = NULL;
guint ii;
summary = camel_folder_get_folder_summary (folder);
store = camel_folder_get_parent_store (folder);
if (!store)
return TRUE;
camel_folder_summary_prepare_fetch_all (summary, NULL);
known_uids = camel_folder_summary_get_array (summary);
if (known_uids == NULL)
return TRUE;
rss_folder = CAMEL_RSS_FOLDER (folder);
cache = camel_rss_store_get_cache (CAMEL_RSS_STORE (store));
changes = camel_folder_change_info_new ();
for (ii = 0; ii < known_uids->len; ii++) {
guint32 flags;
const gchar *uid;
uid = g_ptr_array_index (known_uids, ii);
flags = camel_folder_summary_get_info_flags (summary, uid);
if ((flags & CAMEL_MESSAGE_DELETED) != 0) {
/* ignore cache removal error */
camel_data_cache_remove (cache, rss_folder->priv->id, uid, NULL);
camel_folder_change_info_remove_uid (changes, uid);
to_remove = g_list_prepend (to_remove, (gpointer) camel_pstring_strdup (uid));
}
}
if (to_remove) {
camel_folder_summary_remove_uids (summary, to_remove);
camel_folder_summary_save (summary, NULL);
camel_folder_changed (folder, changes);
g_list_free_full (to_remove, (GDestroyNotify) camel_pstring_free);
}
camel_folder_change_info_free (changes);
camel_folder_summary_free_array (known_uids);
return TRUE;
}
static CamelMimeMessage *
rss_folder_get_message_cached (CamelFolder *folder,
const gchar *uid,
GCancellable *cancellable)
{
CamelRssFolderSummary *rss_summary;
g_return_val_if_fail (CAMEL_IS_RSS_FOLDER (folder), NULL);
g_return_val_if_fail (uid != NULL, NULL);
rss_summary = CAMEL_RSS_FOLDER_SUMMARY (camel_folder_get_folder_summary (folder));
return camel_rss_folder_summary_dup_message (rss_summary, uid, NULL, NULL, cancellable, NULL);
}
static CamelMimeMessage *
rss_folder_get_message_sync (CamelFolder *folder,
const gchar *uid,
GCancellable *cancellable,
GError **error)
{
CamelMimeMessage *message;
message = rss_folder_get_message_cached (folder, uid, cancellable);
if (!message) {
g_set_error_literal (error, CAMEL_SERVICE_ERROR, CAMEL_SERVICE_ERROR_UNAVAILABLE,
_("Message is not available"));
}
return message;
}
static gboolean
rss_folder_refresh_info_sync (CamelFolder *folder,
GCancellable *cancellable,
GError **error)
{
CamelRssFolder *self;
CamelRssFolderSummary *rss_folder_summary;
CamelRssStore *rss_store;
CamelRssStoreSummary *rss_store_summary;
CamelFolderChangeInfo *changes = NULL;
CamelSession *session;
gchar *href;
gint64 last_updated;
gboolean success = TRUE;
self = CAMEL_RSS_FOLDER (folder);
rss_store = CAMEL_RSS_STORE (camel_folder_get_parent_store (folder));
session = camel_service_ref_session (CAMEL_SERVICE (rss_store));
if (!session || !camel_session_get_online (session)) {
g_clear_object (&session);
return TRUE;
}
g_clear_object (&session);
rss_store_summary = camel_rss_store_get_summary (rss_store);
rss_folder_summary = CAMEL_RSS_FOLDER_SUMMARY (camel_folder_get_folder_summary (folder));
camel_rss_store_summary_lock (rss_store_summary);
href = g_strdup (camel_rss_store_summary_get_href (rss_store_summary, self->priv->id));
last_updated = camel_rss_store_summary_get_last_updated (rss_store_summary, self->priv->id);
camel_rss_store_summary_unlock (rss_store_summary);
if (href && *href) {
SoupSession *soup_session;
SoupMessage *message;
GBytes *bytes;
message = soup_message_new (SOUP_METHOD_GET, href);
if (!message) {
g_set_error (error, CAMEL_FOLDER_ERROR, CAMEL_FOLDER_ERROR_INVALID, _("Invalid Feed URL “%s”."), href);
g_free (href);
return FALSE;
}
soup_session = soup_session_new_with_options (
"timeout", 30,
"user-agent", "Evolution/" VERSION,
NULL);
if (camel_debug ("rss")) {
SoupLogger *logger;
logger = soup_logger_new (SOUP_LOGGER_LOG_BODY);
soup_session_add_feature (soup_session, SOUP_SESSION_FEATURE (logger));
g_object_unref (logger);
}
bytes = soup_session_send_and_read (soup_session, message, cancellable, error);
if (bytes) {
GSList *feeds = NULL;
success = SOUP_STATUS_IS_SUCCESSFUL (soup_message_get_status (message));
if (success && e_rss_parser_parse ((const gchar *) g_bytes_get_data (bytes, NULL), g_bytes_get_size (bytes), NULL, NULL, NULL, NULL, &feeds)) {
CamelSettings *settings;
CamelRssSettings *rss_settings;
gboolean download_complete_article;
gboolean feed_enclosures, limit_feed_enclosure_size;
guint32 max_feed_enclosure_size;
gint64 max_last_modified = last_updated;
GSList *link;
settings = camel_service_ref_settings (CAMEL_SERVICE (rss_store));
rss_settings = CAMEL_RSS_SETTINGS (settings);
limit_feed_enclosure_size = camel_rss_settings_get_limit_feed_enclosure_size (rss_settings);
max_feed_enclosure_size = camel_rss_settings_get_max_feed_enclosure_size (rss_settings);
switch (self->priv->complete_articles) {
case CAMEL_THREE_STATE_ON:
download_complete_article = TRUE;
break;
case CAMEL_THREE_STATE_OFF:
download_complete_article = FALSE;
break;
default:
download_complete_article = camel_rss_settings_get_complete_articles (rss_settings);
break;
}
switch (self->priv->feed_enclosures) {
case CAMEL_THREE_STATE_ON:
feed_enclosures = TRUE;
break;
case CAMEL_THREE_STATE_OFF:
feed_enclosures = FALSE;
break;
default:
feed_enclosures = camel_rss_settings_get_feed_enclosures (rss_settings);
break;
}
g_clear_object (&settings);
for (link = feeds; link && success; link = g_slist_next (link)) {
ERssFeed *feed = link->data;
if (feed->last_modified > last_updated) {
GBytes *complete_article = NULL;
if (max_last_modified < feed->last_modified)
max_last_modified = feed->last_modified;
if (download_complete_article) {
g_clear_object (&message);
g_clear_pointer (&bytes, g_bytes_unref);
message = soup_message_new (SOUP_METHOD_GET, feed->link);
if (message) {
complete_article = soup_session_send_and_read (soup_session, message, cancellable, NULL);
if (!SOUP_STATUS_IS_SUCCESSFUL (soup_message_get_status (message)))
g_clear_pointer (&complete_article, g_bytes_unref);
}
}
if (success && feed_enclosures && feed->enclosures) {
GSList *elink;
for (elink = feed->enclosures; elink && success; elink = g_slist_next (elink)) {
ERssEnclosure *enclosure = elink->data;
if (limit_feed_enclosure_size && enclosure->size > max_feed_enclosure_size)
continue;
g_clear_object (&message);
g_clear_pointer (&bytes, g_bytes_unref);
message = soup_message_new (SOUP_METHOD_GET, enclosure->href);
if (message) {
enclosure->data = soup_session_send_and_read (soup_session, message, cancellable, NULL);
if (!SOUP_STATUS_IS_SUCCESSFUL (soup_message_get_status (message)))
g_clear_pointer (&enclosure->data, g_bytes_unref);
}
}
}
success = success && camel_rss_folder_summary_add_or_update_feed_sync (rss_folder_summary, href, feed, complete_article, &changes, cancellable, error);
g_clear_pointer (&complete_article, g_bytes_unref);
}
}
if (success && max_last_modified != last_updated) {
camel_rss_store_summary_lock (rss_store_summary);
camel_rss_store_summary_set_last_updated (rss_store_summary, self->priv->id, max_last_modified);
camel_rss_store_summary_unlock (rss_store_summary);
success = camel_rss_store_summary_save (rss_store_summary, error);
}
}
g_slist_free_full (feeds, e_rss_feed_free);
} else {
success = FALSE;
}
g_clear_pointer (&bytes, g_bytes_unref);
g_clear_object (&soup_session);
g_clear_object (&message);
} else {
g_set_error (error, CAMEL_FOLDER_ERROR, CAMEL_FOLDER_ERROR_INVALID, _("Invalid Feed URL."));
success = FALSE;
}
g_free (href);
if (changes) {
GError *local_error = NULL;
if (!camel_folder_summary_save (CAMEL_FOLDER_SUMMARY (rss_folder_summary), (error && !*error) ? error : &local_error)) {
if (local_error)
g_warning ("Failed to save RSS folder summary: %s", local_error->message);
}
g_clear_error (&local_error);
camel_folder_changed (folder, changes);
camel_folder_change_info_free (changes);
}
return success;
}
static void
rss_unset_flagged_flag (const gchar *uid,
CamelFolderSummary *summary)
{
CamelMessageInfo *info;
info = camel_folder_summary_get (summary, uid);
if (info) {
camel_message_info_set_folder_flagged (info, FALSE);
g_clear_object (&info);
}
}
static gboolean
rss_folder_synchronize_sync (CamelFolder *folder,
gboolean expunge,
GCancellable *cancellable,
GError **error)
{
CamelFolderSummary *summary;
GPtrArray *changed;
if (expunge) {
if (!camel_folder_expunge_sync (folder, cancellable, error))
return FALSE;
}
summary = camel_folder_get_folder_summary (folder);
changed = camel_folder_summary_get_changed (summary);
if (changed) {
g_ptr_array_foreach (changed, (GFunc) rss_unset_flagged_flag, summary);
g_ptr_array_foreach (changed, (GFunc) camel_pstring_free, NULL);
camel_folder_summary_touch (summary);
g_ptr_array_free (changed, TRUE);
}
return camel_folder_summary_save (summary, error);
}
static void
rss_folder_changed (CamelFolder *folder,
CamelFolderChangeInfo *info)
{
g_return_if_fail (CAMEL_IS_RSS_FOLDER (folder));
if (info && info->uid_removed && info->uid_removed->len) {
CamelDataCache *rss_cache;
rss_cache = camel_rss_store_get_cache (CAMEL_RSS_STORE (camel_folder_get_parent_store (folder)));
if (rss_cache) {
CamelRssFolder *self = CAMEL_RSS_FOLDER (folder);
guint ii;
for (ii = 0; ii < info->uid_removed->len; ii++) {
const gchar *message_uid = info->uid_removed->pdata[ii], *real_uid;
if (!message_uid)
continue;
real_uid = strchr (message_uid, ',');
if (real_uid)
camel_data_cache_remove (rss_cache, self->priv->id, real_uid + 1, NULL);
}
}
}
/* Chain up to parent's method. */
CAMEL_FOLDER_CLASS (camel_rss_folder_parent_class)->changed (folder, info);
}
static gboolean
rss_folder_get_apply_filters (CamelRssFolder *folder)
{
g_return_val_if_fail (CAMEL_IS_RSS_FOLDER (folder), FALSE);
return folder->priv->apply_filters;
}
static void
rss_folder_set_apply_filters (CamelRssFolder *folder,
gboolean apply_filters)
{
g_return_if_fail (CAMEL_IS_RSS_FOLDER (folder));
if ((folder->priv->apply_filters ? 1 : 0) == (apply_filters ? 1 : 0))
return;
folder->priv->apply_filters = apply_filters;
g_object_notify (G_OBJECT (folder), "apply-filters");
}
static CamelThreeState
rss_folder_get_complete_articles (CamelRssFolder *folder)
{
g_return_val_if_fail (CAMEL_IS_RSS_FOLDER (folder), CAMEL_THREE_STATE_INCONSISTENT);
return folder->priv->complete_articles;
}
static void
rss_folder_set_complete_articles (CamelRssFolder *folder,
CamelThreeState value)
{
g_return_if_fail (CAMEL_IS_RSS_FOLDER (folder));
if (folder->priv->complete_articles == value)
return;
folder->priv->complete_articles = value;
g_object_notify (G_OBJECT (folder), "complete-articles");
}
static CamelThreeState
rss_folder_get_feed_enclosures (CamelRssFolder *folder)
{
g_return_val_if_fail (CAMEL_IS_RSS_FOLDER (folder), CAMEL_THREE_STATE_INCONSISTENT);
return folder->priv->feed_enclosures;
}
static void
rss_folder_set_feed_enclosures (CamelRssFolder *folder,
CamelThreeState value)
{
g_return_if_fail (CAMEL_IS_RSS_FOLDER (folder));
if (folder->priv->feed_enclosures == value)
return;
folder->priv->feed_enclosures = value;
g_object_notify (G_OBJECT (folder), "feed-enclosures");
}
static void
rss_folder_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
switch (property_id) {
case PROP_APPLY_FILTERS:
rss_folder_set_apply_filters (CAMEL_RSS_FOLDER (object), g_value_get_boolean (value));
return;
case PROP_COMPLETE_ARTICLES:
rss_folder_set_complete_articles (CAMEL_RSS_FOLDER (object), g_value_get_enum (value));
return;
case PROP_FEED_ENCLOSURES:
rss_folder_set_feed_enclosures (CAMEL_RSS_FOLDER (object), g_value_get_enum (value));
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
rss_folder_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
switch (property_id) {
case PROP_APPLY_FILTERS:
g_value_set_boolean (value, rss_folder_get_apply_filters (CAMEL_RSS_FOLDER (object)));
return;
case PROP_COMPLETE_ARTICLES:
g_value_set_enum (value, rss_folder_get_complete_articles (CAMEL_RSS_FOLDER (object)));
return;
case PROP_FEED_ENCLOSURES:
g_value_set_enum (value, rss_folder_get_feed_enclosures (CAMEL_RSS_FOLDER (object)));
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
rss_folder_dispose (GObject *object)
{
camel_folder_summary_save (camel_folder_get_folder_summary (CAMEL_FOLDER (object)), NULL);
/* Chain up to parent's method. */
G_OBJECT_CLASS (camel_rss_folder_parent_class)->dispose (object);
}
static void
rss_folder_finalize (GObject *object)
{
CamelRssFolder *self = CAMEL_RSS_FOLDER (object);
g_free (self->priv->id);
/* Chain up to parent's method. */
G_OBJECT_CLASS (camel_rss_folder_parent_class)->finalize (object);
}
static void
camel_rss_folder_class_init (CamelRssFolderClass *class)
{
GObjectClass *object_class;
CamelFolderClass *folder_class;
object_class = G_OBJECT_CLASS (class);
object_class->set_property = rss_folder_set_property;
object_class->get_property = rss_folder_get_property;
object_class->dispose = rss_folder_dispose;
object_class->finalize = rss_folder_finalize;
folder_class = CAMEL_FOLDER_CLASS (class);
folder_class->search_by_expression = rss_folder_search_by_expression;
folder_class->count_by_expression = rss_folder_count_by_expression;
folder_class->search_by_uids = rss_folder_search_by_uids;
folder_class->search_free = rss_folder_search_free;
folder_class->get_filename = rss_get_filename;
folder_class->append_message_sync = rss_folder_append_message_sync;
folder_class->expunge_sync = rss_folder_expunge_sync;
folder_class->get_message_cached = rss_folder_get_message_cached;
folder_class->get_message_sync = rss_folder_get_message_sync;
folder_class->refresh_info_sync = rss_folder_refresh_info_sync;
folder_class->synchronize_sync = rss_folder_synchronize_sync;
folder_class->changed = rss_folder_changed;
g_object_class_install_property (
object_class,
PROP_APPLY_FILTERS,
g_param_spec_boolean (
"apply-filters",
"Apply Filters",
_("Apply message _filters to this folder"),
FALSE,
G_PARAM_READWRITE |
G_PARAM_EXPLICIT_NOTIFY |
G_PARAM_STATIC_STRINGS |
CAMEL_PARAM_PERSISTENT));
g_object_class_install_property (
object_class,
PROP_COMPLETE_ARTICLES,
g_param_spec_enum (
"complete-articles",
"Complete Articles",
_("_Download complete articles"),
CAMEL_TYPE_THREE_STATE,
CAMEL_THREE_STATE_INCONSISTENT,
G_PARAM_READWRITE |
G_PARAM_EXPLICIT_NOTIFY |
CAMEL_PARAM_PERSISTENT));
g_object_class_install_property (
object_class,
PROP_FEED_ENCLOSURES,
g_param_spec_enum (
"feed-enclosures",
"Feed Enclosures",
_("Download feed _enclosures"),
CAMEL_TYPE_THREE_STATE,
CAMEL_THREE_STATE_INCONSISTENT,
G_PARAM_READWRITE |
G_PARAM_EXPLICIT_NOTIFY |
CAMEL_PARAM_PERSISTENT));
}
static void
camel_rss_folder_init (CamelRssFolder *self)
{
self->priv = camel_rss_folder_get_instance_private (self);
self->priv->complete_articles = CAMEL_THREE_STATE_INCONSISTENT;
self->priv->feed_enclosures = CAMEL_THREE_STATE_INCONSISTENT;
}
CamelFolder *
camel_rss_folder_new (CamelStore *parent,
const gchar *id,
GCancellable *cancellable,
GError **error)
{
CamelFolder *folder;
CamelRssFolder *self;
CamelFolderSummary *folder_summary;
CamelRssStoreSummary *store_summary;
gchar *storage_path, *root;
CamelService *service;
CamelSettings *settings;
const gchar *user_data_dir;
gboolean filter_all = FALSE;
g_return_val_if_fail (id != NULL, NULL);
store_summary = camel_rss_store_get_summary (CAMEL_RSS_STORE (parent));
g_return_val_if_fail (store_summary != NULL, NULL);
service = CAMEL_SERVICE (parent);
user_data_dir = camel_service_get_user_data_dir (service);
settings = camel_service_ref_settings (service);
g_object_get (
settings,
"filter-all", &filter_all,
NULL);
g_object_unref (settings);
camel_rss_store_summary_lock (store_summary);
folder = g_object_new (
CAMEL_TYPE_RSS_FOLDER,
"display-name", camel_rss_store_summary_get_display_name (store_summary, id),
"full-name", id,
"parent-store", parent, NULL);
camel_rss_store_summary_unlock (store_summary);
self = CAMEL_RSS_FOLDER (folder);
self->priv->id = g_strdup (id);
camel_folder_set_flags (folder, camel_folder_get_flags (folder) | CAMEL_FOLDER_HAS_SUMMARY_CAPABILITY);
storage_path = g_build_filename (user_data_dir, id, NULL);
root = g_strdup_printf ("%s.cmeta", storage_path);
camel_object_set_state_filename (CAMEL_OBJECT (self), root);
camel_object_state_read (CAMEL_OBJECT (self));
g_free (root);
g_free (storage_path);
folder_summary = camel_rss_folder_summary_new (folder);
camel_folder_take_folder_summary (folder, folder_summary);
if (filter_all || rss_folder_get_apply_filters (self))
camel_folder_set_flags (folder, camel_folder_get_flags (folder) | CAMEL_FOLDER_FILTER_RECENT);
camel_folder_summary_load (folder_summary, NULL);
return folder;
}
const gchar *
camel_rss_folder_get_id (CamelRssFolder *self)
{
g_return_val_if_fail (CAMEL_IS_RSS_FOLDER (self), NULL);
return self->priv->id;
}

View File

@ -0,0 +1,55 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* SPDX-FileCopyrightText: (C) 2022 Red Hat (www.redhat.com)
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef CAMEL_RSS_FOLDER_H
#define CAMEL_RSS_FOLDER_H
#include <camel/camel.h>
/* Standard GObject macros */
#define CAMEL_TYPE_RSS_FOLDER \
(camel_rss_folder_get_type ())
#define CAMEL_RSS_FOLDER(obj) \
(G_TYPE_CHECK_INSTANCE_CAST \
((obj), CAMEL_TYPE_RSS_FOLDER, CamelRssFolder))
#define CAMEL_RSS_FOLDER_CLASS(cls) \
(G_TYPE_CHECK_CLASS_CAST \
((cls), CAMEL_TYPE_RSS_FOLDER, CamelRssFolderClass))
#define CAMEL_IS_RSS_FOLDER(obj) \
(G_TYPE_CHECK_INSTANCE_TYPE \
((obj), CAMEL_TYPE_RSS_FOLDER))
#define CAMEL_IS_RSS_FOLDER_CLASS(cls) \
(G_TYPE_CHECK_CLASS_TYPE \
((cls), CAMEL_TYPE_RSS_FOLDER))
#define CAMEL_RSS_FOLDER_GET_CLASS(obj) \
(G_TYPE_INSTANCE_GET_CLASS \
((obj), CAMEL_TYPE_RSS_FOLDER, CamelRssFolderClass))
G_BEGIN_DECLS
typedef struct _CamelRssFolder CamelRssFolder;
typedef struct _CamelRssFolderClass CamelRssFolderClass;
typedef struct _CamelRssFolderPrivate CamelRssFolderPrivate;
struct _CamelRssFolder {
CamelFolder parent;
CamelRssFolderPrivate *priv;
};
struct _CamelRssFolderClass {
CamelFolderClass parent;
};
GType camel_rss_folder_get_type (void);
CamelFolder * camel_rss_folder_new (CamelStore *parent,
const gchar *id,
GCancellable *cancellable,
GError **error);
const gchar * camel_rss_folder_get_id (CamelRssFolder *self);
G_END_DECLS
#endif /* CAMEL_RSS_FOLDER_H */

View File

@ -0,0 +1,94 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* SPDX-FileCopyrightText: (C) 2022 Red Hat (www.redhat.com)
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "evolution-config.h"
#include <string.h>
#include <glib/gi18n-lib.h>
#include <camel/camel.h>
#include "camel-rss-store.h"
static CamelProvider rss_provider = {
"rss",
N_("News and Blogs"),
N_("This is a provider for reading RSS news and blogs."),
"rss",
CAMEL_PROVIDER_IS_LOCAL | CAMEL_PROVIDER_IS_SOURCE | CAMEL_PROVIDER_IS_STORAGE,
CAMEL_URL_NEED_HOST,
NULL, /* conf_entries */
NULL, /* port_entries */
/* ... */
};
static void
add_hash (guint *hash,
gchar *s)
{
if (s)
*hash ^= g_str_hash(s);
}
static guint
rss_url_hash (gconstpointer key)
{
const CamelURL *u = (CamelURL *) key;
guint hash = 0;
add_hash (&hash, u->user);
add_hash (&hash, u->host);
hash ^= u->port;
return hash;
}
static gint
check_equal (gchar *s1,
gchar *s2)
{
if (s1 == NULL) {
if (s2 == NULL)
return TRUE;
else
return FALSE;
}
if (s2 == NULL)
return FALSE;
return strcmp (s1, s2) == 0;
}
static gint
rss_url_equal (gconstpointer a,
gconstpointer b)
{
const CamelURL *u1 = a, *u2 = b;
return check_equal (u1->protocol, u2->protocol)
&& check_equal (u1->user, u2->user)
&& check_equal (u1->host, u2->host)
&& u1->port == u2->port;
}
void
camel_provider_module_init (void)
{
rss_provider.object_types[CAMEL_PROVIDER_STORE] = camel_rss_store_get_type ();
rss_provider.url_hash = rss_url_hash;
rss_provider.url_equal = rss_url_equal;
rss_provider.authtypes = NULL;
rss_provider.translation_domain = GETTEXT_PACKAGE;
camel_provider_register (&rss_provider);
}

View File

@ -0,0 +1,301 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* SPDX-FileCopyrightText: (C) 2022 Red Hat (www.redhat.com)
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "evolution-config.h"
#include <glib.h>
#include "camel-rss-settings.h"
struct _CamelRssSettingsPrivate {
gboolean filter_all;
gboolean complete_articles;
gboolean feed_enclosures;
gboolean limit_feed_enclosure_size;
guint32 max_feed_enclosure_size;
};
enum {
PROP_0,
PROP_FILTER_ALL,
PROP_COMPLETE_ARTICLES,
PROP_FEED_ENCLOSURES,
PROP_LIMIT_FEED_ENCLOSURE_SIZE,
PROP_MAX_FEED_ENCLOSURE_SIZE
};
G_DEFINE_TYPE_WITH_CODE (CamelRssSettings, camel_rss_settings, CAMEL_TYPE_OFFLINE_SETTINGS,
G_ADD_PRIVATE (CamelRssSettings))
static void
rss_settings_set_property (GObject *object,
guint property_id,
const GValue *value,
GParamSpec *pspec)
{
switch (property_id) {
case PROP_FILTER_ALL:
camel_rss_settings_set_filter_all (
CAMEL_RSS_SETTINGS (object),
g_value_get_boolean (value));
return;
case PROP_COMPLETE_ARTICLES:
camel_rss_settings_set_complete_articles (
CAMEL_RSS_SETTINGS (object),
g_value_get_boolean (value));
return;
case PROP_FEED_ENCLOSURES:
camel_rss_settings_set_feed_enclosures (
CAMEL_RSS_SETTINGS (object),
g_value_get_boolean (value));
return;
case PROP_LIMIT_FEED_ENCLOSURE_SIZE:
camel_rss_settings_set_limit_feed_enclosure_size (
CAMEL_RSS_SETTINGS (object),
g_value_get_boolean (value));
return;
case PROP_MAX_FEED_ENCLOSURE_SIZE:
camel_rss_settings_set_max_feed_enclosure_size (
CAMEL_RSS_SETTINGS (object),
g_value_get_uint (value));
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
rss_settings_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
switch (property_id) {
case PROP_FILTER_ALL:
g_value_set_boolean (
value,
camel_rss_settings_get_filter_all (
CAMEL_RSS_SETTINGS (object)));
return;
case PROP_COMPLETE_ARTICLES:
g_value_set_boolean (
value,
camel_rss_settings_get_complete_articles (
CAMEL_RSS_SETTINGS (object)));
return;
case PROP_FEED_ENCLOSURES:
g_value_set_boolean (
value,
camel_rss_settings_get_feed_enclosures (
CAMEL_RSS_SETTINGS (object)));
return;
case PROP_LIMIT_FEED_ENCLOSURE_SIZE:
g_value_set_boolean (
value,
camel_rss_settings_get_limit_feed_enclosure_size (
CAMEL_RSS_SETTINGS (object)));
return;
case PROP_MAX_FEED_ENCLOSURE_SIZE:
g_value_set_uint (
value,
camel_rss_settings_get_max_feed_enclosure_size (
CAMEL_RSS_SETTINGS (object)));
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
camel_rss_settings_class_init (CamelRssSettingsClass *class)
{
GObjectClass *object_class;
object_class = G_OBJECT_CLASS (class);
object_class->set_property = rss_settings_set_property;
object_class->get_property = rss_settings_get_property;
g_object_class_install_property (
object_class,
PROP_FILTER_ALL,
g_param_spec_boolean (
"filter-all",
"Filter All",
"Whether to apply filters in all folders",
FALSE,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_EXPLICIT_NOTIFY |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property (
object_class,
PROP_COMPLETE_ARTICLES,
g_param_spec_boolean (
"complete-articles",
"Complete Articles",
"Whether to download complete articles",
FALSE,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_EXPLICIT_NOTIFY |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property (
object_class,
PROP_FEED_ENCLOSURES,
g_param_spec_boolean (
"feed-enclosures",
"Feed Enclosures",
"Whether to download feed enclosures",
FALSE,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_EXPLICIT_NOTIFY |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property (
object_class,
PROP_LIMIT_FEED_ENCLOSURE_SIZE,
g_param_spec_boolean (
"limit-feed-enclosure-size",
"Limit Feed Enclosure Size",
"Whether to limit feed enclosure size",
FALSE,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_EXPLICIT_NOTIFY |
G_PARAM_STATIC_STRINGS));
g_object_class_install_property (
object_class,
PROP_MAX_FEED_ENCLOSURE_SIZE,
g_param_spec_uint (
"max-feed-enclosure-size",
"Max Feed Enclosure Size",
"Max size, in kB, of feed enclosure to download",
0, G_MAXUINT32, 0,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT |
G_PARAM_EXPLICIT_NOTIFY |
G_PARAM_STATIC_STRINGS));
}
static void
camel_rss_settings_init (CamelRssSettings *settings)
{
settings->priv = camel_rss_settings_get_instance_private (settings);
}
gboolean
camel_rss_settings_get_filter_all (CamelRssSettings *settings)
{
g_return_val_if_fail (CAMEL_IS_RSS_SETTINGS (settings), FALSE);
return settings->priv->filter_all;
}
void
camel_rss_settings_set_filter_all (CamelRssSettings *settings,
gboolean filter_all)
{
g_return_if_fail (CAMEL_IS_RSS_SETTINGS (settings));
if ((!settings->priv->filter_all) == (!filter_all))
return;
settings->priv->filter_all = filter_all;
g_object_notify (G_OBJECT (settings), "filter-all");
}
gboolean
camel_rss_settings_get_complete_articles (CamelRssSettings *settings)
{
g_return_val_if_fail (CAMEL_IS_RSS_SETTINGS (settings), FALSE);
return settings->priv->complete_articles;
}
void
camel_rss_settings_set_complete_articles (CamelRssSettings *settings,
gboolean value)
{
g_return_if_fail (CAMEL_IS_RSS_SETTINGS (settings));
if ((!settings->priv->complete_articles) == (!value))
return;
settings->priv->complete_articles = value;
g_object_notify (G_OBJECT (settings), "complete-articles");
}
gboolean
camel_rss_settings_get_feed_enclosures (CamelRssSettings *settings)
{
g_return_val_if_fail (CAMEL_IS_RSS_SETTINGS (settings), FALSE);
return settings->priv->feed_enclosures;
}
void
camel_rss_settings_set_feed_enclosures (CamelRssSettings *settings,
gboolean value)
{
g_return_if_fail (CAMEL_IS_RSS_SETTINGS (settings));
if ((!settings->priv->feed_enclosures) == (!value))
return;
settings->priv->feed_enclosures = value;
g_object_notify (G_OBJECT (settings), "feed-enclosures");
}
gboolean
camel_rss_settings_get_limit_feed_enclosure_size (CamelRssSettings *settings)
{
g_return_val_if_fail (CAMEL_IS_RSS_SETTINGS (settings), FALSE);
return settings->priv->limit_feed_enclosure_size;
}
void
camel_rss_settings_set_limit_feed_enclosure_size (CamelRssSettings *settings,
gboolean value)
{
g_return_if_fail (CAMEL_IS_RSS_SETTINGS (settings));
if ((!settings->priv->limit_feed_enclosure_size) == (!value))
return;
settings->priv->limit_feed_enclosure_size = value;
g_object_notify (G_OBJECT (settings), "limit-feed-enclosure-size");
}
guint32
camel_rss_settings_get_max_feed_enclosure_size (CamelRssSettings *settings)
{
g_return_val_if_fail (CAMEL_IS_RSS_SETTINGS (settings), 0);
return settings->priv->max_feed_enclosure_size;
}
void
camel_rss_settings_set_max_feed_enclosure_size (CamelRssSettings *settings,
guint32 value)
{
g_return_if_fail (CAMEL_IS_RSS_SETTINGS (settings));
if (settings->priv->max_feed_enclosure_size == value)
return;
settings->priv->max_feed_enclosure_size = value;
g_object_notify (G_OBJECT (settings), "max-feed-enclosure-size");
}

View File

@ -0,0 +1,76 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* SPDX-FileCopyrightText: (C) 2022 Red Hat (www.redhat.com)
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef CAMEL_RSS_SETTINGS_H
#define CAMEL_RSS_SETTINGS_H
#include <camel/camel.h>
/* Standard GObject macros */
#define CAMEL_TYPE_RSS_SETTINGS \
(camel_rss_settings_get_type ())
#define CAMEL_RSS_SETTINGS(obj) \
(G_TYPE_CHECK_INSTANCE_CAST \
((obj), CAMEL_TYPE_RSS_SETTINGS, CamelRssSettings))
#define CAMEL_RSS_SETTINGS_CLASS(cls) \
(G_TYPE_CHECK_CLASS_CAST \
((cls), CAMEL_TYPE_RSS_SETTINGS, CamelRssSettingsClass))
#define CAMEL_IS_RSS_SETTINGS(obj) \
(G_TYPE_CHECK_INSTANCE_TYPE \
((obj), CAMEL_TYPE_RSS_SETTINGS))
#define CAMEL_IS_RSS_SETTINGS_CLASS(cls) \
(G_TYPE_CHECK_CLASS_TYPE \
((cls), CAMEL_TYPE_RSS_SETTINGS))
#define CAMEL_RSS_SETTINGS_GET_CLASS(obj) \
(G_TYPE_INSTANCE_GET_CLASS \
((obj), CAMEL_TYPE_RSS_SETTINGS, CamelRssSettingsClass))
G_BEGIN_DECLS
typedef struct _CamelRssSettings CamelRssSettings;
typedef struct _CamelRssSettingsClass CamelRssSettingsClass;
typedef struct _CamelRssSettingsPrivate CamelRssSettingsPrivate;
struct _CamelRssSettings {
CamelOfflineSettings parent;
CamelRssSettingsPrivate *priv;
};
struct _CamelRssSettingsClass {
CamelOfflineSettingsClass parent_class;
};
GType camel_rss_settings_get_type
(void) G_GNUC_CONST;
gboolean camel_rss_settings_get_filter_all
(CamelRssSettings *settings);
void camel_rss_settings_set_filter_all
(CamelRssSettings *settings,
gboolean filter_all);
gboolean camel_rss_settings_get_complete_articles
(CamelRssSettings *settings);
void camel_rss_settings_set_complete_articles
(CamelRssSettings *settings,
gboolean value);
gboolean camel_rss_settings_get_feed_enclosures
(CamelRssSettings *settings);
void camel_rss_settings_set_feed_enclosures
(CamelRssSettings *settings,
gboolean value);
gboolean camel_rss_settings_get_limit_feed_enclosure_size
(CamelRssSettings *settings);
void camel_rss_settings_set_limit_feed_enclosure_size
(CamelRssSettings *settings,
gboolean value);
guint32 camel_rss_settings_get_max_feed_enclosure_size
(CamelRssSettings *settings);
void camel_rss_settings_set_max_feed_enclosure_size
(CamelRssSettings *settings,
guint32 value);
G_END_DECLS
#endif /* CAMEL_RSS_SETTINGS_H */

View File

@ -0,0 +1,397 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* SPDX-FileCopyrightText: (C) 2022 Red Hat (www.redhat.com)
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "evolution-config.h"
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <glib/gstdio.h>
#include <glib/gi18n-lib.h>
#include <libedataserver/libedataserver.h>
#include "camel-rss-folder.h"
#include "camel-rss-settings.h"
#include "camel-rss-store-summary.h"
#include "camel-rss-store.h"
struct _CamelRssStorePrivate {
CamelDataCache *cache;
CamelRssStoreSummary *summary;
};
enum {
PROP_0,
PROP_SUMMARY
};
static GInitableIface *parent_initable_interface;
/* Forward Declarations */
static void camel_rss_store_initable_init (GInitableIface *iface);
G_DEFINE_TYPE_WITH_CODE (CamelRssStore, camel_rss_store, CAMEL_TYPE_STORE,
G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, camel_rss_store_initable_init)
G_ADD_PRIVATE (CamelRssStore))
static gchar *
rss_store_get_name (CamelService *service,
gboolean brief)
{
return g_strdup (_("News and Blogs"));
}
static gboolean
rss_store_can_refresh_folder (CamelStore *store,
CamelFolderInfo *info,
GError **error)
{
/* Any RSS folder can be refreshed */
return TRUE;
}
static CamelFolder *
rss_store_get_folder_sync (CamelStore *store,
const gchar *folder_name,
CamelStoreGetFolderFlags flags,
GCancellable *cancellable,
GError **error)
{
CamelRssStore *self = CAMEL_RSS_STORE (store);
CamelFolder *folder = NULL;
camel_rss_store_summary_lock (self->priv->summary);
/* The 'folder_name' is the folder ID */
if (camel_rss_store_summary_contains (self->priv->summary, folder_name)) {
folder = camel_rss_folder_new (store, folder_name, cancellable, error);
} else {
g_set_error (error, CAMEL_STORE_ERROR, CAMEL_STORE_ERROR_NO_FOLDER,
_("Folder '%s' not found"), folder_name);
}
camel_rss_store_summary_unlock (self->priv->summary);
return folder;
}
static CamelFolderInfo *
rss_store_get_folder_info_sync (CamelStore *store,
const gchar *top,
CamelStoreGetFolderInfoFlags flags,
GCancellable *cancellable,
GError **error)
{
CamelRssStore *self = CAMEL_RSS_STORE (store);
CamelFolderInfo *fi = NULL, *first = NULL, *last = NULL;
if (!top || !*top) {
GSList *ids, *link;
ids = camel_rss_store_summary_dup_feeds (self->priv->summary);
for (link = ids; link; link = g_slist_next (link)) {
const gchar *id = link->data;
fi = camel_rss_store_summary_dup_folder_info (self->priv->summary, id);
if (fi) {
if (last) {
last->next = fi;
last = fi;
} else {
first = fi;
last = first;
}
}
}
g_slist_free_full (ids, g_free);
} else {
first = camel_rss_store_summary_dup_folder_info (self->priv->summary, top);
if (!first)
first = camel_rss_store_summary_dup_folder_info_for_display_name (self->priv->summary, top);
if (!first) {
g_set_error (error, CAMEL_STORE_ERROR, CAMEL_STORE_ERROR_NO_FOLDER,
_("Folder '%s' not found"), top);
}
}
return first;
}
static CamelFolderInfo *
rss_store_create_folder_sync (CamelStore *store,
const gchar *parent_name,
const gchar *folder_name,
GCancellable *cancellable,
GError **error)
{
g_set_error (error, CAMEL_STORE_ERROR, CAMEL_STORE_ERROR_INVALID,
_("Cannot create a folder in a News and Blogs store."));
return NULL;
}
static gboolean
rss_store_rename_folder_sync (CamelStore *store,
const gchar *old_name,
const gchar *new_name_in,
GCancellable *cancellable,
GError **error)
{
CamelRssStore *self = CAMEL_RSS_STORE (store);
gboolean success = FALSE;
camel_rss_store_summary_lock (self->priv->summary);
if (camel_rss_store_summary_contains (self->priv->summary, old_name)) {
const gchar *display_name;
success = TRUE;
display_name = camel_rss_store_summary_get_display_name (self->priv->summary, old_name);
if (g_strcmp0 (display_name, new_name_in) != 0) {
camel_rss_store_summary_set_display_name (self->priv->summary, old_name, new_name_in);
success = camel_rss_store_summary_save (self->priv->summary, error);
if (success) {
CamelFolderInfo *fi;
fi = camel_rss_store_summary_dup_folder_info (self->priv->summary, old_name);
camel_store_folder_renamed (store, old_name, fi);
camel_folder_info_free (fi);
}
}
} else {
g_set_error (error, CAMEL_STORE_ERROR, CAMEL_STORE_ERROR_NO_FOLDER,
_("Folder '%s' not found"), old_name);
}
camel_rss_store_summary_unlock (self->priv->summary);
return success;
}
static gboolean
rss_store_delete_folder_sync (CamelStore *store,
const gchar *folder_name,
GCancellable *cancellable,
GError **error)
{
CamelRssStore *self = CAMEL_RSS_STORE (store);
CamelFolderInfo *fi;
gboolean success = FALSE;
camel_rss_store_summary_lock (self->priv->summary);
/* The 'folder_name' is the folder ID */
fi = camel_rss_store_summary_dup_folder_info (self->priv->summary, folder_name);
if (camel_rss_store_summary_remove (self->priv->summary, folder_name)) {
GFile *file;
gchar *cmeta_filename;
GError *local_error = NULL;
file = g_file_new_build_filename (camel_data_cache_get_path (self->priv->cache), folder_name, NULL);
/* Ignore errors */
if (!e_file_recursive_delete_sync (file, cancellable, &local_error)) {
if (camel_debug ("rss") &&
!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND) &&
!g_error_matches (local_error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
g_printerr ("%s: Failed to delete cache directory '%s': %s", G_STRFUNC, g_file_peek_path (file), local_error ? local_error->message : "Unknown error");
g_clear_error (&local_error);
}
g_clear_object (&file);
cmeta_filename = g_strdup_printf ("%s%c%s.cmeta", camel_data_cache_get_path (self->priv->cache), G_DIR_SEPARATOR, folder_name);
if (g_unlink (cmeta_filename)) {
gint errn = errno;
if (errn != ENOENT && camel_debug ("rss"))
g_printerr ("%s: Failed to delete '%s': %s", G_STRFUNC, cmeta_filename, g_strerror (errn));
}
g_free (cmeta_filename);
camel_store_folder_deleted (store, fi);
success = camel_rss_store_summary_save (self->priv->summary, error);
} else {
g_set_error (error, CAMEL_STORE_ERROR, CAMEL_STORE_ERROR_NO_FOLDER,
_("Folder '%s' not found"), folder_name);
}
camel_rss_store_summary_unlock (self->priv->summary);
if (fi)
camel_folder_info_free (fi);
return success;
}
static void
rss_store_get_property (GObject *object,
guint property_id,
GValue *value,
GParamSpec *pspec)
{
switch (property_id) {
case PROP_SUMMARY:
g_value_set_object (value,
camel_rss_store_get_summary (
CAMEL_RSS_STORE (object)));
return;
}
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
}
static void
rss_store_dispose (GObject *object)
{
CamelRssStore *self = CAMEL_RSS_STORE (object);
if (self->priv->summary) {
GError *local_error = NULL;
if (!camel_rss_store_summary_save (self->priv->summary, &local_error)) {
g_warning ("%s: Failed to save RSS store summary: %s", G_STRFUNC, local_error ? local_error->message : "Unknown error");
g_clear_error (&local_error);
}
}
g_clear_object (&self->priv->cache);
g_clear_object (&self->priv->summary);
/* Chain up to parent's method. */
G_OBJECT_CLASS (camel_rss_store_parent_class)->dispose (object);
}
static void
camel_rss_store_class_init (CamelRssStoreClass *klass)
{
GObjectClass *object_class;
CamelServiceClass *service_class;
CamelStoreClass *store_class;
object_class = G_OBJECT_CLASS (klass);
object_class->get_property = rss_store_get_property;
object_class->dispose = rss_store_dispose;
service_class = CAMEL_SERVICE_CLASS (klass);
service_class->settings_type = CAMEL_TYPE_RSS_SETTINGS;
service_class->get_name = rss_store_get_name;
store_class = CAMEL_STORE_CLASS (klass);
store_class->can_refresh_folder = rss_store_can_refresh_folder;
store_class->get_folder_sync = rss_store_get_folder_sync;
store_class->get_folder_info_sync = rss_store_get_folder_info_sync;
store_class->create_folder_sync = rss_store_create_folder_sync;
store_class->delete_folder_sync = rss_store_delete_folder_sync;
store_class->rename_folder_sync = rss_store_rename_folder_sync;
g_object_class_install_property (
object_class,
PROP_SUMMARY,
g_param_spec_object (
"summary", NULL, NULL,
CAMEL_TYPE_RSS_STORE_SUMMARY,
G_PARAM_READABLE |
G_PARAM_STATIC_STRINGS));
}
static gboolean
rss_store_initable_init (GInitable *initable,
GCancellable *cancellable,
GError **error)
{
CamelDataCache *rss_cache;
CamelRssStore *self;
CamelStore *store;
CamelService *service;
const gchar *user_data_dir;
gchar *filename;
self = CAMEL_RSS_STORE (initable);
store = CAMEL_STORE (initable);
service = CAMEL_SERVICE (initable);
camel_store_set_flags (store, camel_store_get_flags (store) | CAMEL_STORE_VTRASH | CAMEL_STORE_VJUNK | CAMEL_STORE_IS_BUILTIN);
/* Chain up to parent method. */
if (!parent_initable_interface->init (initable, cancellable, error))
return FALSE;
service = CAMEL_SERVICE (initable);
user_data_dir = camel_service_get_user_data_dir (service);
if (g_mkdir_with_parents (user_data_dir, S_IRWXU) == -1) {
g_set_error_literal (
error, G_FILE_ERROR,
g_file_error_from_errno (errno),
g_strerror (errno));
return FALSE;
}
filename = g_build_filename (user_data_dir, "rss.ini", NULL);
self->priv->summary = camel_rss_store_summary_new (filename);
g_free (filename);
if (!camel_rss_store_summary_load (self->priv->summary, error))
return FALSE;
/* setup store-wide cache */
rss_cache = camel_data_cache_new (user_data_dir, error);
if (rss_cache == NULL)
return FALSE;
/* Do not expire the cache */
camel_data_cache_set_expire_enabled (rss_cache, FALSE);
self->priv->cache = rss_cache; /* takes ownership */
return TRUE;
}
static void
camel_rss_store_initable_init (GInitableIface *iface)
{
parent_initable_interface = g_type_interface_peek_parent (iface);
iface->init = rss_store_initable_init;
}
static void
camel_rss_store_init (CamelRssStore *self)
{
self->priv = camel_rss_store_get_instance_private (self);
camel_store_set_flags (CAMEL_STORE (self), 0);
}
CamelDataCache *
camel_rss_store_get_cache (CamelRssStore *self)
{
g_return_val_if_fail (CAMEL_IS_RSS_STORE (self), NULL);
return self->priv->cache;
}
CamelRssStoreSummary *
camel_rss_store_get_summary (CamelRssStore *self)
{
g_return_val_if_fail (CAMEL_IS_RSS_STORE (self), NULL);
return self->priv->summary;
}

View File

@ -0,0 +1,55 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* SPDX-FileCopyrightText: (C) 2022 Red Hat (www.redhat.com)
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef CAMEL_RSS_STORE_H
#define CAMEL_RSS_STORE_H
#include <camel/camel.h>
#include "camel-rss-store-summary.h"
/* Standard GObject macros */
#define CAMEL_TYPE_RSS_STORE \
(camel_rss_store_get_type ())
#define CAMEL_RSS_STORE(obj) \
(G_TYPE_CHECK_INSTANCE_CAST \
((obj), CAMEL_TYPE_RSS_STORE, CamelRssStore))
#define CAMEL_RSS_STORE_CLASS(cls) \
(G_TYPE_CHECK_CLASS_CAST \
((cls), CAMEL_TYPE_RSS_STORE, CamelRssStoreClass))
#define CAMEL_IS_RSS_STORE(obj) \
(G_TYPE_CHECK_INSTANCE_TYPE \
((obj), CAMEL_TYPE_RSS_STORE))
#define CAMEL_IS_RSS_STORE_CLASS(cls) \
(G_TYPE_CHECK_CLASS_TYPE \
((cls), CAMEL_TYPE_RSS_STORE))
#define CAMEL_RSS_STORE_GET_CLASS(obj) \
(G_TYPE_INSTANCE_GET_CLASS \
((obj), CAMEL_TYPE_RSS_STORE, CamelRssStoreClass))
G_BEGIN_DECLS
typedef struct _CamelRssStore CamelRssStore;
typedef struct _CamelRssStoreClass CamelRssStoreClass;
typedef struct _CamelRssStorePrivate CamelRssStorePrivate;
struct _CamelRssStore {
CamelStore parent;
CamelRssStorePrivate *priv;
};
struct _CamelRssStoreClass {
CamelStoreClass parent_class;
};
GType camel_rss_store_get_type (void);
CamelDataCache *camel_rss_store_get_cache (CamelRssStore *self);
CamelRssStoreSummary *
camel_rss_store_get_summary (CamelRssStore *self);
G_END_DECLS
#endif /* CAMEL_RSS_STORE_H */

View File

@ -0,0 +1 @@
rss

View File

@ -0,0 +1,656 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* SPDX-FileCopyrightText: (C) 2022 Red Hat (www.redhat.com)
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "evolution-config.h"
#include <glib.h>
#include <glib/gi18n-lib.h>
#include <camel/camel.h>
#include <libedataserver/libedataserver.h>
#include "e-rss-parser.h"
ERssEnclosure *
e_rss_enclosure_new (void)
{
return g_slice_new0 (ERssEnclosure);
}
void
e_rss_enclosure_free (gpointer ptr)
{
ERssEnclosure *enclosure = ptr;
if (enclosure) {
g_clear_pointer (&enclosure->data, g_bytes_unref);
g_free (enclosure->title);
g_free (enclosure->href);
g_free (enclosure->content_type);
g_slice_free (ERssEnclosure, enclosure);
}
}
ERssFeed *
e_rss_feed_new (void)
{
return g_slice_new0 (ERssFeed);
}
void
e_rss_feed_free (gpointer ptr)
{
ERssFeed *feed = ptr;
if (feed) {
g_free (feed->id);
g_free (feed->link);
g_free (feed->author);
g_free (feed->title);
g_free (feed->body);
g_slist_free_full (feed->enclosures, e_rss_enclosure_free);
g_slice_free (ERssFeed, feed);
}
}
static void
e_rss_read_feed_person (xmlNodePtr author,
xmlChar **out_name,
xmlChar **out_email)
{
xmlNodePtr node;
for (node = author->children; node && (!*out_name || !*out_email); node = node->next) {
if (g_strcmp0 ((const gchar *) node->name, "name") == 0) {
g_clear_pointer (out_name, xmlFree);
*out_name = xmlNodeGetContent (node);
} else if (g_strcmp0 ((const gchar *) node->name, "email") == 0) {
g_clear_pointer (out_email, xmlFree);
*out_email = xmlNodeGetContent (node);
} else if (g_strcmp0 ((const gchar *) node->name, "uri") == 0 &&
(!*out_email || !**out_email)) {
g_clear_pointer (out_email, xmlFree);
*out_email = xmlNodeGetContent (node);
}
}
if (!*out_name && !*out_email) {
*out_name = xmlNodeGetContent (author);
if (*out_name && !**out_name)
g_clear_pointer (&out_name, xmlFree);
}
}
static gchar *
e_rss_parser_encode_address (xmlChar *name,
xmlChar *email)
{
gchar *address;
if (!name && !email)
return NULL;
address = camel_internet_address_format_address ((const gchar *) name,
email ? (const gchar *) email : "");
if (address && (!email || !*email) && g_str_has_suffix (address, " <>")) {
/* avoid empty email in the string */
address[strlen (address) - 3] = '\0';
}
return address;
}
static ERssEnclosure *
e_rss_read_enclosure (xmlNodePtr node)
{
#define dup_attr(des, x) { \
xmlChar *attr = xmlGetProp (node, (const xmlChar *) x); \
if (attr && *attr) \
des = g_strdup ((const gchar *) attr); \
else \
des = NULL; \
g_clear_pointer (&attr, xmlFree); \
}
ERssEnclosure *enclosure;
xmlChar *length;
gchar *href;
dup_attr (href, "href");
if (!href)
dup_attr (href, "url");
if (!href || !*href) {
g_free (href);
return NULL;
}
enclosure = e_rss_enclosure_new ();
enclosure->href = href;
dup_attr (enclosure->title, "title");
dup_attr (enclosure->content_type, "type");
#undef dup_attr
length = xmlGetProp (node, (const xmlChar *) "length");
if (length && *length)
enclosure->size = g_ascii_strtoull ((const gchar *) length, NULL, 10);
g_clear_pointer (&length, xmlFree);
return enclosure;
}
typedef struct _FeedDefaults {
GUri *base_uri; /* 'base' as a GUri */
xmlChar *base;
xmlChar *author_name;
xmlChar *author_email;
gint64 publish_date;
xmlChar *link;
xmlChar *alt_link;
xmlChar *title;
xmlChar *icon;
} FeedDefaults;
static void
e_rss_ensure_uri_absolute (GUri *base_uri,
gchar **inout_uri)
{
GUri *abs_uri;
const gchar *uri;
if (!base_uri || !inout_uri)
return;
uri = *inout_uri;
if (!uri || *uri != '/')
return;
abs_uri = g_uri_parse_relative (base_uri, uri,
G_URI_FLAGS_PARSE_RELAXED |
G_URI_FLAGS_HAS_PASSWORD |
G_URI_FLAGS_ENCODED_PATH |
G_URI_FLAGS_ENCODED_QUERY |
G_URI_FLAGS_ENCODED_FRAGMENT |
G_URI_FLAGS_SCHEME_NORMALIZE, NULL);
if (abs_uri) {
g_free (*inout_uri);
*inout_uri = g_uri_to_string_partial (abs_uri, G_URI_HIDE_PASSWORD);
g_uri_unref (abs_uri);
}
}
static void
e_rss_read_item (xmlNodePtr item,
const FeedDefaults *defaults,
GSList **out_feeds)
{
ERssFeed *feed = e_rss_feed_new ();
xmlNodePtr node;
for (node = item->children; node; node = node->next) {
xmlChar *value = NULL;
if (g_strcmp0 ((const gchar *) node->name, "title") == 0) {
value = xmlNodeGetContent (node);
g_clear_pointer (&feed->title, g_free);
feed->title = g_strdup ((const gchar *) value);
} else if (g_strcmp0 ((const gchar *) node->name, "link") == 0) {
xmlChar *rel = xmlGetProp (node, (const xmlChar *) "rel");
if (!rel ||
g_strcmp0 ((const gchar *) rel, "self") == 0 ||
g_strcmp0 ((const gchar *) rel, "alternate") == 0) {
value = xmlGetProp (node, (const xmlChar *) "href");
if (!value)
value = xmlNodeGetContent (node);
g_clear_pointer (&feed->link, g_free);
feed->link = g_strdup ((const gchar *) value);
/* Use full URI-s, not relative */
if (feed->link && *feed->link == '/' && defaults->base_uri)
e_rss_ensure_uri_absolute (defaults->base_uri, &feed->link);
} else if (g_strcmp0 ((const gchar *) rel, "enclosure") == 0) {
ERssEnclosure *enclosure = e_rss_read_enclosure (node);
if (enclosure)
feed->enclosures = g_slist_prepend (feed->enclosures, enclosure);
}
g_clear_pointer (&rel, xmlFree);
} else if (g_strcmp0 ((const gchar *) node->name, "id") == 0 ||
g_strcmp0 ((const gchar *) node->name, "guid") == 0) {
value = xmlNodeGetContent (node);
g_clear_pointer (&feed->id, g_free);
feed->id = g_strdup ((const gchar *) value);
} else if (g_strcmp0 ((const gchar *) node->name, "content") == 0) {
value = xmlNodeGetContent (node);
g_clear_pointer (&feed->body, g_free);
feed->body = g_strdup ((const gchar *) value);
} else if (g_strcmp0 ((const gchar *) node->name, "description") == 0 ||
g_strcmp0 ((const gchar *) node->name, "summary") == 0) {
if (!feed->body || !*feed->body) {
value = xmlNodeGetContent (node);
g_clear_pointer (&feed->body, g_free);
feed->body = g_strdup ((const gchar *) value);
}
} else if (g_strcmp0 ((const gchar *) node->name, "enclosure") == 0) {
ERssEnclosure *enclosure = e_rss_read_enclosure (node);
if (enclosure)
feed->enclosures = g_slist_prepend (feed->enclosures, enclosure);
} else if (g_strcmp0 ((const gchar *) node->name, "author") == 0) {
xmlChar *name = NULL, *email = NULL;
e_rss_read_feed_person (node, &name, &email);
if (name || email) {
g_clear_pointer (&feed->author, g_free);
feed->author = e_rss_parser_encode_address (name, email);
g_clear_pointer (&name, xmlFree);
g_clear_pointer (&email, xmlFree);
}
} else if (g_strcmp0 ((const gchar *) node->name, "pubDate") == 0) {
value = xmlNodeGetContent (node);
if (value && *value)
feed->last_modified = camel_header_decode_date ((const gchar *) value, NULL);
} else if (g_strcmp0 ((const gchar *) node->name, "updated") == 0) {
value = xmlNodeGetContent (node);
if (value && *value) {
GDateTime *dt;
dt = g_date_time_new_from_iso8601 ((const gchar *) value, NULL);
if (dt)
feed->last_modified = g_date_time_to_unix (dt);
g_clear_pointer (&dt, g_date_time_unref);
}
}
g_clear_pointer (&value, xmlFree);
}
if (feed->link && feed->title) {
if (!feed->author) {
if (defaults->author_name || defaults->author_email) {
feed->author = e_rss_parser_encode_address (defaults->author_name, defaults->author_email);
} else {
feed->author = g_strdup (_("Unknown author"));
}
}
if (!feed->last_modified)
feed->last_modified = defaults->publish_date;
feed->enclosures = g_slist_reverse (feed->enclosures);
*out_feeds = g_slist_prepend (*out_feeds, feed);
} else {
e_rss_feed_free (feed);
}
}
static void
e_rss_read_defaults_rdf (xmlNodePtr root,
FeedDefaults *defaults)
{
xmlNodePtr node;
defaults->base = xmlGetProp (root, (const xmlChar *) "base");
for (node = root->children; node; node = node->next) {
if (g_strcmp0 ((const gchar *) node->name, "channel") == 0) {
xmlNodePtr subnode;
gboolean has_author = FALSE, has_link = FALSE, has_title = FALSE, has_image = FALSE;
for (subnode = node->children; subnode && (!has_author || !has_link || !has_title || !has_image); subnode = subnode->next) {
if (!has_author && g_strcmp0 ((const gchar *) subnode->name, "creator") == 0) {
g_clear_pointer (&defaults->author_name, xmlFree);
defaults->author_name = xmlNodeGetContent (subnode);
has_author = TRUE;
} else if (!has_author && g_strcmp0 ((const gchar *) subnode->name, "publisher") == 0) {
g_clear_pointer (&defaults->author_name, xmlFree);
defaults->author_name = xmlNodeGetContent (subnode);
/* do not set has_author here, creator is more suitable */
}
if (!has_link && g_strcmp0 ((const gchar *) subnode->name, "link") == 0) {
defaults->link = xmlNodeGetContent (subnode);
has_link = TRUE;
}
if (!has_title && g_strcmp0 ((const gchar *) subnode->name, "title") == 0) {
defaults->title = xmlNodeGetContent (subnode);
has_title = TRUE;
}
if (!has_image && g_strcmp0 ((const gchar *) subnode->name, "image") == 0) {
defaults->icon = xmlGetProp (subnode, (const xmlChar *) "resource");
has_image = TRUE;
}
}
break;
}
}
}
static void
e_rss_read_rdf (xmlNodePtr node,
const FeedDefaults *defaults,
GSList **out_feeds)
{
if (g_strcmp0 ((const gchar *) node->name, "item") == 0) {
e_rss_read_item (node, defaults, out_feeds);
}
}
static void
e_rss_read_defaults_rss (xmlNodePtr root,
FeedDefaults *defaults)
{
xmlNodePtr channel_node;
defaults->base = xmlGetProp (root, (const xmlChar *) "base");
for (channel_node = root->children; channel_node; channel_node = channel_node->next) {
if (g_strcmp0 ((const gchar *) channel_node->name, "channel") == 0) {
xmlNodePtr node;
gboolean has_pubdate = FALSE, has_link = FALSE, has_title = FALSE, has_image = FALSE;
for (node = channel_node->children; node && (!has_pubdate || !has_link || !has_title || !has_image); node = node->next) {
if (!has_pubdate && g_strcmp0 ((const gchar *) node->name, "pubDate") == 0) {
xmlChar *value = xmlNodeGetContent (node);
if (value && *value)
defaults->publish_date = camel_header_decode_date ((const gchar *) value, NULL);
g_clear_pointer (&value, xmlFree);
has_pubdate = TRUE;
}
if (!has_link && g_strcmp0 ((const gchar *) node->name, "link") == 0) {
xmlChar *value = xmlNodeGetContent (node);
if (value && *value) {
defaults->link = value;
has_link = TRUE;
} else {
g_clear_pointer (&value, xmlFree);
}
}
if (!has_title && g_strcmp0 ((const gchar *) node->name, "title") == 0) {
xmlChar *value = xmlNodeGetContent (node);
if (value && *value)
defaults->title = value;
else
g_clear_pointer (&value, xmlFree);
has_title = TRUE;
}
if (!has_image && g_strcmp0 ((const gchar *) node->name, "image") == 0) {
xmlNodePtr image_node;
for (image_node = node->children; image_node; image_node = image_node->next) {
if (g_strcmp0 ((const gchar *) image_node->name, "url") == 0) {
xmlChar *value = xmlNodeGetContent (image_node);
if (value && *value)
defaults->icon = value;
else
g_clear_pointer (&value, xmlFree);
break;
}
}
has_image = TRUE;
}
}
/* read only the first channel */
break;
}
}
}
static void
e_rss_read_rss (xmlNodePtr node,
const FeedDefaults *defaults,
GSList **out_feeds)
{
if (g_strcmp0 ((const gchar *) node->name, "channel") == 0) {
xmlNodePtr subnode;
for (subnode = node->children; subnode; subnode = subnode->next) {
if (g_strcmp0 ((const gchar *) subnode->name, "item") == 0) {
e_rss_read_item (subnode, defaults, out_feeds);
}
}
}
}
static void
e_rss_read_defaults_feed (xmlNodePtr root,
FeedDefaults *defaults)
{
xmlNodePtr node;
gboolean has_author = FALSE, has_published = FALSE, has_link = FALSE, has_alt_link = FALSE, has_title = FALSE, has_icon = FALSE;
defaults->base = xmlGetProp (root, (const xmlChar *) "base");
if (!defaults->base) {
for (node = root->children; node && !defaults->base; node = node->next) {
if (g_strcmp0 ((const gchar *) node->name, "link") == 0)
defaults->base = xmlGetProp (root, (const xmlChar *) "rel");
else if (g_strcmp0 ((const gchar *) node->name, "alternate") == 0)
defaults->base = xmlGetProp (root, (const xmlChar *) "href");
}
}
for (node = root->children; node && (!has_author || !has_published || !has_link || !has_alt_link || !has_title || !has_icon); node = node->next) {
if (!has_author && g_strcmp0 ((const gchar *) node->name, "author") == 0) {
e_rss_read_feed_person (node, &defaults->author_name, &defaults->author_email);
has_author = TRUE;
}
if (!has_published && (
g_strcmp0 ((const gchar *) node->name, "published") == 0 ||
g_strcmp0 ((const gchar *) node->name, "updated") == 0)) {
xmlChar *value = xmlNodeGetContent (node);
if (value && *value) {
GDateTime *dt;
dt = g_date_time_new_from_iso8601 ((const gchar *) value, NULL);
if (dt) {
defaults->publish_date = g_date_time_to_unix (dt);
has_published = TRUE;
}
g_clear_pointer (&dt, g_date_time_unref);
}
g_clear_pointer (&value, xmlFree);
}
if ((!has_link || !has_alt_link) && g_strcmp0 ((const gchar *) node->name, "link") == 0) {
xmlChar *rel, *href;
rel = xmlGetProp (node, (const xmlChar *) "rel");
href = xmlGetProp (node, (const xmlChar *) "href");
if (!has_link && href && *href && g_strcmp0 ((const gchar *) rel, "self") == 0) {
defaults->link = href;
href = NULL;
has_link = TRUE;
}
if (!has_alt_link && href && *href && g_strcmp0 ((const gchar *) rel, "alternate") == 0) {
defaults->alt_link = href;
href = NULL;
has_alt_link = TRUE;
}
g_clear_pointer (&rel, xmlFree);
g_clear_pointer (&href, xmlFree);
}
if (!has_title && g_strcmp0 ((const gchar *) node->name, "title") == 0) {
xmlChar *value = xmlNodeGetContent (node);
if (value && *value)
defaults->title = value;
else
g_clear_pointer (&value, xmlFree);
has_title = TRUE;
}
if (!has_icon && (
g_strcmp0 ((const gchar *) node->name, "icon") == 0 ||
g_strcmp0 ((const gchar *) node->name, "logo") == 0)) {
xmlChar *value = xmlNodeGetContent (node);
if (value && *value) {
g_clear_pointer (&defaults->icon, xmlFree);
defaults->icon = value;
} else {
g_clear_pointer (&value, xmlFree);
}
/* Prefer "icon", but if not available, then use "logo" */
has_icon = g_strcmp0 ((const gchar *) node->name, "icon") == 0;
}
}
}
static void
e_rss_read_feed (xmlNodePtr node,
const FeedDefaults *defaults,
GSList **out_feeds)
{
if (g_strcmp0 ((const gchar *) node->name, "entry") == 0) {
e_rss_read_item (node, defaults, out_feeds);
}
}
gboolean
e_rss_parser_parse (const gchar *xml,
gsize xml_len,
gchar **out_link,
gchar **out_alt_link,
gchar **out_title,
gchar **out_icon,
GSList **out_feeds) /* ERssFeed * */
{
xmlDoc *doc;
xmlNodePtr root;
g_return_val_if_fail (xml != NULL, FALSE);
if (out_feeds)
*out_feeds = NULL;
doc = e_xml_parse_data (xml, xml_len);
if (!doc)
return FALSE;
root = xmlDocGetRootElement (doc);
if (root) {
FeedDefaults defaults = { 0, };
void (*read_func) (xmlNodePtr node,
const FeedDefaults *defaults,
GSList **out_feeds) = NULL;
if (g_strcmp0 ((const gchar *) root->name, "rdf") == 0) {
/* RSS 1.0 - https://web.resource.org/rss/1.0/ */
e_rss_read_defaults_rdf (root, &defaults);
read_func = e_rss_read_rdf;
} else if (g_strcmp0 ((const gchar *) root->name, "rss") == 0) {
/* RSS 2.0 - https://www.rssboard.org/rss-specification */
e_rss_read_defaults_rss (root, &defaults);
read_func = e_rss_read_rss;
} else if (g_strcmp0 ((const gchar *) root->name, "feed") == 0) {
/* Atom - https://validator.w3.org/feed/docs/atom.html */
e_rss_read_defaults_feed (root, &defaults);
read_func = e_rss_read_feed;
}
if (defaults.base || defaults.link || defaults.alt_link) {
const gchar *base;
base = (const gchar *) defaults.base;
if (!base || *base == '/')
base = (const gchar *) defaults.link;
if (!base || *base == '/')
base = (const gchar *) defaults.alt_link;
if (base && *base != '/') {
defaults.base_uri = g_uri_parse (base,
G_URI_FLAGS_PARSE_RELAXED |
G_URI_FLAGS_HAS_PASSWORD |
G_URI_FLAGS_ENCODED_PATH |
G_URI_FLAGS_ENCODED_QUERY |
G_URI_FLAGS_ENCODED_FRAGMENT |
G_URI_FLAGS_SCHEME_NORMALIZE, NULL);
}
}
if (read_func && out_feeds) {
xmlNodePtr node;
for (node = root->children; node; node = node->next) {
read_func (node, &defaults, out_feeds);
}
}
if (out_link) {
*out_link = g_strdup ((const gchar *) defaults.link);
e_rss_ensure_uri_absolute (defaults.base_uri, out_link);
}
if (out_alt_link) {
*out_alt_link = g_strdup ((const gchar *) defaults.alt_link);
e_rss_ensure_uri_absolute (defaults.base_uri, out_alt_link);
}
if (out_title)
*out_title = g_strdup ((const gchar *) defaults.title);
if (out_icon) {
*out_icon = g_strdup ((const gchar *) defaults.icon);
e_rss_ensure_uri_absolute (defaults.base_uri, out_icon);
}
g_clear_pointer (&defaults.base_uri, g_uri_unref);
g_clear_pointer (&defaults.base, xmlFree);
g_clear_pointer (&defaults.author_name, xmlFree);
g_clear_pointer (&defaults.author_email, xmlFree);
g_clear_pointer (&defaults.link, xmlFree);
g_clear_pointer (&defaults.alt_link, xmlFree);
g_clear_pointer (&defaults.title, xmlFree);
g_clear_pointer (&defaults.icon, xmlFree);
if (out_feeds)
*out_feeds = g_slist_reverse (*out_feeds);
}
xmlFreeDoc (doc);
return TRUE;
}

View File

@ -0,0 +1,48 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* SPDX-FileCopyrightText: (C) 2022 Red Hat (www.redhat.com)
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef E_RSS_PARSER_H
#define E_RSS_PARSER_H
#include <glib.h>
G_BEGIN_DECLS
typedef struct _ERssEnclosure {
gchar *title;
gchar *href;
gchar *content_type;
guint64 size;
GBytes *data;
} ERssEnclosure;
typedef struct _ERssFeed {
gchar *id;
gchar *link;
gchar *author;
gchar *title;
gchar *body;
gint64 last_modified;
GSList *enclosures; /* ERssEnclosure * */
} ERssFeed;
ERssEnclosure * e_rss_enclosure_new (void);
void e_rss_enclosure_free (gpointer ptr);
ERssFeed * e_rss_feed_new (void);
void e_rss_feed_free (gpointer ptr);
gboolean e_rss_parser_parse (const gchar *xml,
gsize xml_len,
gchar **out_link,
gchar **out_alt_link,
gchar **out_title,
gchar **out_icon,
GSList **out_feeds); /* ERssFeed * */
G_END_DECLS
#endif /* E_RSS_PARSER_H */

View File

@ -0,0 +1,32 @@
set(extra_deps
evolution-mail
evolution-shell
)
set(sources
e-rss-preferences.c
e-rss-preferences.h
e-rss-folder-tree-model-extension.c
e-rss-shell-extension.c
e-rss-shell-view-extension.c
module-rss.c
module-rss.h
../camel-rss-store-summary.c
../camel-rss-store-summary.h
../e-rss-parser.c
../e-rss-parser.h
)
set(extra_defines)
set(extra_cflags)
set(extra_incdirs
${CMAKE_CURRENT_SOURCE_DIR}/..
)
set(extra_ldflags)
add_evolution_module(module-rss
sources
extra_deps
extra_defines
extra_cflags
extra_incdirs
extra_ldflags
)

View File

@ -0,0 +1,217 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* SPDX-FileCopyrightText: (C) 2022 Red Hat (www.redhat.com)
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "evolution-config.h"
#include <glib-object.h>
#include "mail/em-folder-tree-model.h"
#include "../camel-rss-store-summary.h"
#include "module-rss.h"
#define E_TYPE_RSS_FOLDER_TREE_MODEL_EXTENSION (e_rss_folder_tree_model_extension_get_type ())
GType e_rss_folder_tree_model_extension_get_type (void);
typedef struct _ERssFolderTreeModelExtension {
EExtension parent;
gboolean listens_feed_changed;
} ERssFolderTreeModelExtension;
typedef struct _ERssFolderTreeModelExtensionClass {
EExtensionClass parent_class;
} ERssFolderTreeModelExtensionClass;
G_DEFINE_DYNAMIC_TYPE (ERssFolderTreeModelExtension, e_rss_folder_tree_model_extension, E_TYPE_EXTENSION)
static void
e_rss_update_custom_icon (CamelRssStoreSummary *store_summary,
const gchar *full_name,
EMFolderTreeModel *model,
GtkTreeIter *iter)
{
const gchar *icon_filename;
icon_filename = camel_rss_store_summary_get_icon_filename (store_summary, full_name);
if (icon_filename && g_file_test (icon_filename, G_FILE_TEST_IS_REGULAR | G_FILE_TEST_EXISTS))
icon_filename = full_name;
else
icon_filename = "rss";
gtk_tree_store_set (GTK_TREE_STORE (model), iter,
COL_STRING_ICON_NAME, icon_filename,
-1);
}
static void
e_rss_folder_custom_icon_feed_changed_cb (CamelRssStoreSummary *store_summary,
const gchar *feed_id,
EMFolderTreeModel *model)
{
EMailSession *session;
CamelService *service;
if (!feed_id || !camel_rss_store_summary_contains (store_summary, feed_id))
return;
session = em_folder_tree_model_get_session (model);
if (!session)
return;
service = camel_session_ref_service (CAMEL_SESSION (session), "rss");
if (service) {
GtkTreeRowReference *row;
row = em_folder_tree_model_get_row_reference (model, CAMEL_STORE (service), feed_id);
if (row) {
GtkTreePath *path;
GtkTreeIter iter;
path = gtk_tree_row_reference_get_path (row);
gtk_tree_model_get_iter (GTK_TREE_MODEL (model), &iter, path);
gtk_tree_path_free (path);
e_rss_update_custom_icon (store_summary, feed_id, model, &iter);
}
}
g_clear_object (&service);
}
static void
e_rss_folder_custom_icon_cb (EMFolderTreeModel *model,
GtkTreeIter *iter,
CamelStore *store,
const gchar *full_name,
ERssFolderTreeModelExtension *extension)
{
CamelRssStoreSummary *store_summary = NULL;
const gchar *uid = camel_service_get_uid (CAMEL_SERVICE (store));
g_return_if_fail (extension != NULL);
if (g_strcmp0 (uid, "rss") != 0 || !full_name)
return;
if (g_strcmp0 (full_name, CAMEL_VJUNK_NAME) == 0 ||
g_strcmp0 (full_name, CAMEL_VTRASH_NAME) == 0)
return;
g_object_get (store, "summary", &store_summary, NULL);
if (!store_summary)
return;
if (!extension->listens_feed_changed) {
extension->listens_feed_changed = TRUE;
g_signal_connect_object (store_summary, "feed-changed",
G_CALLBACK (e_rss_folder_custom_icon_feed_changed_cb), model, 0);
}
e_rss_update_custom_icon (store_summary, full_name, model, iter);
g_clear_object (&store_summary);
}
static gint
e_rss_compare_folders_cb (EMFolderTreeModel *model,
const gchar *store_uid,
GtkTreeIter *iter1,
GtkTreeIter *iter2)
{
gint rv = -2;
/* Junk/Trash as the last, to not mix them with feed folders */
if (g_strcmp0 (store_uid, "rss") == 0) {
gboolean a_is_vfolder, b_is_vfolder;
guint32 flags_a, flags_b;
gtk_tree_model_get (
GTK_TREE_MODEL (model), iter1,
COL_UINT_FLAGS, &flags_a,
-1);
gtk_tree_model_get (
GTK_TREE_MODEL (model), iter2,
COL_UINT_FLAGS, &flags_b,
-1);
a_is_vfolder = (flags_a & CAMEL_FOLDER_TYPE_MASK) == CAMEL_FOLDER_TYPE_JUNK ||
(flags_a & CAMEL_FOLDER_TYPE_MASK) == CAMEL_FOLDER_TYPE_TRASH;
b_is_vfolder = (flags_b & CAMEL_FOLDER_TYPE_MASK) == CAMEL_FOLDER_TYPE_JUNK ||
(flags_b & CAMEL_FOLDER_TYPE_MASK) == CAMEL_FOLDER_TYPE_TRASH;
if ((a_is_vfolder || b_is_vfolder) && (!a_is_vfolder || !b_is_vfolder)) {
if (a_is_vfolder)
rv = 1;
else
rv = -1;
}
}
return rv;
}
static void
e_rss_folder_tree_model_extension_constructed (GObject *object)
{
static gboolean icon_dir_registered = FALSE;
/* Chain up to parent's method */
G_OBJECT_CLASS (e_rss_folder_tree_model_extension_parent_class)->constructed (object);
g_signal_connect_object (e_extension_get_extensible (E_EXTENSION (object)), "folder-custom-icon",
G_CALLBACK (e_rss_folder_custom_icon_cb), object, 0);
g_signal_connect_object (e_extension_get_extensible (E_EXTENSION (object)), "compare-folders",
G_CALLBACK (e_rss_compare_folders_cb), NULL, 0);
if (!icon_dir_registered) {
gchar *icon_dir;
icon_dir_registered = TRUE;
icon_dir = g_build_filename (e_get_user_data_dir (), "mail", "rss", NULL);
gtk_icon_theme_append_search_path (gtk_icon_theme_get_default (), icon_dir);
g_free (icon_dir);
}
}
static void
e_rss_folder_tree_model_extension_class_init (ERssFolderTreeModelExtensionClass *klass)
{
GObjectClass *object_class;
EExtensionClass *extension_class;
object_class = G_OBJECT_CLASS (klass);
object_class->constructed = e_rss_folder_tree_model_extension_constructed;
extension_class = E_EXTENSION_CLASS (klass);
extension_class->extensible_type = EM_TYPE_FOLDER_TREE_MODEL;
}
static void
e_rss_folder_tree_model_extension_class_finalize (ERssFolderTreeModelExtensionClass *klass)
{
}
static void
e_rss_folder_tree_model_extension_init (ERssFolderTreeModelExtension *extension)
{
}
void
e_rss_folder_tree_model_extension_type_register (GTypeModule *type_module)
{
e_rss_folder_tree_model_extension_register_type (type_module);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,20 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* SPDX-FileCopyrightText: (C) 2022 Red Hat (www.redhat.com)
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef E_RSS_PREFERENCES_H
#define E_RSS_PREFERENCES_H
#include <glib.h>
#include <shell/e-shell.h>
G_BEGIN_DECLS
void e_rss_preferences_init (EShell *shell);
G_END_DECLS
#endif /* E_RSS_PREFERENCES_H */

View File

@ -0,0 +1,132 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* SPDX-FileCopyrightText: (C) 2022 Red Hat (www.redhat.com)
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "evolution-config.h"
#include <glib-object.h>
#include <glib/gi18n-lib.h>
#include "shell/e-shell.h"
#include "e-rss-preferences.h"
#include "module-rss.h"
#define E_TYPE_RSS_SHELL_EXTENSION (e_rss_shell_extension_get_type ())
GType e_rss_shell_extension_get_type (void);
typedef struct _ERssShellExtension {
EExtension parent;
} ERssShellExtension;
typedef struct _ERssShellExtensionClass {
EExtensionClass parent_class;
} ERssShellExtensionClass;
G_DEFINE_DYNAMIC_TYPE (ERssShellExtension, e_rss_shell_extension, E_TYPE_EXTENSION)
static void
e_rss_ensure_esource (EShell *shell)
{
ESourceRegistry *registry;
ESource *rss_source;
registry = e_shell_get_registry (shell);
rss_source = e_source_registry_ref_source (registry, "rss");
if (!rss_source) {
GError *error = NULL;
rss_source = e_source_new_with_uid ("rss", NULL, &error);
if (rss_source) {
ESourceMailAccount *mail_account;
mail_account = e_source_get_extension (rss_source, E_SOURCE_EXTENSION_MAIL_ACCOUNT);
e_source_mail_account_set_builtin (mail_account, TRUE);
e_source_backend_set_backend_name (E_SOURCE_BACKEND (mail_account), "rss");
} else {
g_warning ("Failed to create RSS source: %s", error ? error->message : "Unknown error");
}
g_clear_error (&error);
}
if (rss_source) {
GError *error = NULL;
e_source_set_display_name (rss_source, _("News and Blogs"));
if (!e_source_registry_commit_source_sync (registry, rss_source, NULL, &error))
g_warning ("Failed to commit RSS source: %s", error ? error->message : "Unknown error");
g_clear_error (&error);
}
g_clear_object (&rss_source);
}
static gboolean
init_preferences_idle_cb (gpointer user_data)
{
EShell *shell = g_weak_ref_get (user_data);
if (shell)
e_rss_preferences_init (shell);
g_clear_object (&shell);
return G_SOURCE_REMOVE;
}
static void
e_rss_shell_ready_to_start_cb (EShell *shell)
{
e_rss_ensure_esource (shell);
g_idle_add_full (G_PRIORITY_LOW, init_preferences_idle_cb,
e_weak_ref_new (shell), (GDestroyNotify) e_weak_ref_free);
}
static void
e_rss_shell_extension_constructed (GObject *object)
{
/* Chain up to parent's method */
G_OBJECT_CLASS (e_rss_shell_extension_parent_class)->constructed (object);
g_signal_connect_object (e_extension_get_extensible (E_EXTENSION (object)), "event::ready-to-start",
G_CALLBACK (e_rss_shell_ready_to_start_cb), NULL, 0);
}
static void
e_rss_shell_extension_class_init (ERssShellExtensionClass *klass)
{
GObjectClass *object_class;
EExtensionClass *extension_class;
object_class = G_OBJECT_CLASS (klass);
object_class->constructed = e_rss_shell_extension_constructed;
extension_class = E_EXTENSION_CLASS (klass);
extension_class->extensible_type = E_TYPE_SHELL;
}
static void
e_rss_shell_extension_class_finalize (ERssShellExtensionClass *klass)
{
}
static void
e_rss_shell_extension_init (ERssShellExtension *extension)
{
}
void
e_rss_shell_extension_type_register (GTypeModule *type_module)
{
e_rss_shell_extension_register_type (type_module);
}

View File

@ -0,0 +1,277 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* SPDX-FileCopyrightText: (C) 2022 Red Hat (www.redhat.com)
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "evolution-config.h"
#include <glib-object.h>
#include <glib/gi18n-lib.h>
#include "mail/e-mail-reader-utils.h"
#include "mail/em-folder-tree.h"
#include "shell/e-shell-content.h"
#include "shell/e-shell-view.h"
#include "shell/e-shell-window.h"
#include "../camel-rss-store-summary.h"
#include "module-rss.h"
static const gchar *mail_ui_def =
"<popup name=\"mail-folder-popup\">\n"
" <placeholder name=\"mail-folder-popup-actions\">\n"
" <menuitem action=\"e-rss-mail-folder-reload-action\"/>\n"
" </placeholder>\n"
"</popup>\n";
#define E_TYPE_RSS_SHELL_VIEW_EXTENSION (e_rss_shell_view_extension_get_type ())
GType e_rss_shell_view_extension_get_type (void);
typedef struct _ERssShellViewExtension {
EExtension parent;
guint current_ui_id;
gboolean actions_added;
} ERssShellViewExtension;
typedef struct _ERssShellViewExtensionClass {
EExtensionClass parent_class;
} ERssShellViewExtensionClass;
G_DEFINE_DYNAMIC_TYPE (ERssShellViewExtension, e_rss_shell_view_extension, E_TYPE_EXTENSION)
static gboolean
e_rss_check_rss_folder_selected (EShellView *shell_view,
CamelStore **pstore,
gchar **pfolder_path)
{
EShellSidebar *shell_sidebar;
EMFolderTree *folder_tree;
gchar *selected_path = NULL;
CamelStore *selected_store = NULL;
gboolean is_rss_folder = FALSE;
shell_sidebar = e_shell_view_get_shell_sidebar (shell_view);
g_object_get (shell_sidebar, "folder-tree", &folder_tree, NULL);
if (em_folder_tree_get_selected (folder_tree, &selected_store, &selected_path)) {
if (selected_store) {
is_rss_folder = g_strcmp0 (camel_service_get_uid (CAMEL_SERVICE (selected_store)), "rss") == 0 &&
g_strcmp0 (selected_path, CAMEL_VJUNK_NAME) != 0 &&
g_strcmp0 (selected_path, CAMEL_VTRASH_NAME) != 0;
if (is_rss_folder) {
if (pstore)
*pstore = g_object_ref (selected_store);
if (pfolder_path)
*pfolder_path = selected_path;
else
g_free (selected_path);
selected_path = NULL;
}
g_object_unref (selected_store);
}
g_free (selected_path);
}
g_object_unref (folder_tree);
return is_rss_folder;
}
static void
e_rss_mail_folder_reload_got_folder_cb (GObject *source_object,
GAsyncResult *result,
gpointer user_data)
{
EShellView *shell_view = user_data;
CamelFolder *folder;
GError *error = NULL;
folder = camel_store_get_folder_finish (CAMEL_STORE (source_object), result, &error);
if (folder) {
EShellContent *shell_content;
shell_content = e_shell_view_get_shell_content (shell_view);
e_mail_reader_refresh_folder (E_MAIL_READER (shell_content), folder);
g_object_unref (folder);
} else {
g_warning ("%s: Failed to get folder: %s", G_STRFUNC, error ? error->message : "Unknown error");
}
}
static void
action_rss_mail_folder_reload_cb (GtkAction *action,
EShellView *shell_view)
{
CamelStore *store = NULL;
CamelRssStoreSummary *store_summary = NULL;
gchar *folder_path = NULL;
g_return_if_fail (E_IS_SHELL_VIEW (shell_view));
if (!e_rss_check_rss_folder_selected (shell_view, &store, &folder_path))
return;
g_object_get (store, "summary", &store_summary, NULL);
camel_rss_store_summary_set_last_updated (store_summary, folder_path, 0);
camel_store_get_folder (store, folder_path, CAMEL_STORE_FOLDER_NONE, G_PRIORITY_DEFAULT, NULL,
e_rss_mail_folder_reload_got_folder_cb, shell_view);
g_clear_object (&store_summary);
g_clear_object (&store);
g_free (folder_path);
}
static void
e_rss_shell_view_update_actions_cb (EShellView *shell_view,
GtkActionEntry *entries)
{
CamelStore *store = NULL;
EShellWindow *shell_window;
GtkActionGroup *action_group;
GtkAction *action;
GtkUIManager *ui_manager;
gboolean is_rss_folder = FALSE;
is_rss_folder = e_rss_check_rss_folder_selected (shell_view, &store, NULL);
shell_window = e_shell_view_get_shell_window (shell_view);
ui_manager = e_shell_window_get_ui_manager (shell_window);
action_group = e_lookup_action_group (ui_manager, "mail");
action = gtk_action_group_get_action (action_group, "e-rss-mail-folder-reload-action");
if (action) {
gtk_action_set_visible (action, is_rss_folder);
if (store) {
CamelSession *session;
session = camel_service_ref_session (CAMEL_SERVICE (store));
gtk_action_set_sensitive (action, session && camel_session_get_online (session));
g_clear_object (&session);
} else {
gtk_action_set_sensitive (action, FALSE);
}
}
g_clear_object (&store);
}
static void
e_rss_shell_view_toggled_cb (EShellView *shell_view,
ERssShellViewExtension *extension)
{
EShellViewClass *shell_view_class;
EShellWindow *shell_window;
GtkUIManager *ui_manager;
gboolean is_active, need_update;
GError *error = NULL;
g_return_if_fail (E_IS_SHELL_VIEW (shell_view));
g_return_if_fail (extension != NULL);
shell_view_class = E_SHELL_VIEW_GET_CLASS (shell_view);
g_return_if_fail (shell_view_class != NULL);
shell_window = e_shell_view_get_shell_window (shell_view);
ui_manager = e_shell_window_get_ui_manager (shell_window);
need_update = extension->current_ui_id != 0;
if (extension->current_ui_id) {
gtk_ui_manager_remove_ui (ui_manager, extension->current_ui_id);
extension->current_ui_id = 0;
}
is_active = e_shell_view_is_active (shell_view);
if (!is_active || g_strcmp0 (shell_view_class->ui_manager_id, "org.gnome.evolution.mail") != 0) {
if (need_update)
gtk_ui_manager_ensure_update (ui_manager);
return;
}
if (!extension->actions_added) {
GtkActionEntry mail_folder_context_entries[] = {
{ "e-rss-mail-folder-reload-action",
NULL,
N_("Re_load feed articles"),
NULL,
N_("Reload all feed articles from the server, updating existing and adding any missing"),
G_CALLBACK (action_rss_mail_folder_reload_cb) }
};
GtkActionGroup *action_group;
action_group = e_shell_window_get_action_group (shell_window, "mail");
e_action_group_add_actions_localized (
action_group, GETTEXT_PACKAGE,
mail_folder_context_entries, G_N_ELEMENTS (mail_folder_context_entries), shell_view);
g_signal_connect (shell_view, "update-actions",
G_CALLBACK (e_rss_shell_view_update_actions_cb), NULL);
extension->actions_added = TRUE;
}
extension->current_ui_id = gtk_ui_manager_add_ui_from_string (ui_manager, mail_ui_def, -1, &error);
if (error) {
g_warning ("%s: Failed to add ui definition: %s", G_STRFUNC, error->message);
g_error_free (error);
}
gtk_ui_manager_ensure_update (ui_manager);
}
static void
e_rss_shell_view_extension_constructed (GObject *object)
{
/* Chain up to parent's method */
G_OBJECT_CLASS (e_rss_shell_view_extension_parent_class)->constructed (object);
g_signal_connect_object (e_extension_get_extensible (E_EXTENSION (object)), "toggled",
G_CALLBACK (e_rss_shell_view_toggled_cb), object, 0);
}
static void
e_rss_shell_view_extension_class_init (ERssShellViewExtensionClass *klass)
{
GObjectClass *object_class;
EExtensionClass *extension_class;
object_class = G_OBJECT_CLASS (klass);
object_class->constructed = e_rss_shell_view_extension_constructed;
extension_class = E_EXTENSION_CLASS (klass);
extension_class->extensible_type = E_TYPE_SHELL_VIEW;
}
static void
e_rss_shell_view_extension_class_finalize (ERssShellViewExtensionClass *klass)
{
}
static void
e_rss_shell_view_extension_init (ERssShellViewExtension *extension)
{
}
void
e_rss_shell_view_extension_type_register (GTypeModule *type_module)
{
e_rss_shell_view_extension_register_type (type_module);
}

View File

@ -0,0 +1,28 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* SPDX-FileCopyrightText: (C) 2022 Red Hat (www.redhat.com)
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "evolution-config.h"
#include <glib-object.h>
#include <gmodule.h>
#include "module-rss.h"
void e_module_load (GTypeModule *type_module);
void e_module_unload (GTypeModule *type_module);
G_MODULE_EXPORT void
e_module_load (GTypeModule *type_module)
{
e_rss_shell_extension_type_register (type_module);
e_rss_shell_view_extension_type_register (type_module);
e_rss_folder_tree_model_extension_type_register (type_module);
}
G_MODULE_EXPORT void
e_module_unload (GTypeModule *type_module)
{
}

View File

@ -0,0 +1,21 @@
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* SPDX-FileCopyrightText: (C) 2022 Red Hat (www.redhat.com)
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#ifndef MODULE_RSS_H
#define MODULE_RSS_H
#include <glib-object.h>
G_BEGIN_DECLS
void e_rss_shell_extension_type_register (GTypeModule *type_module);
void e_rss_shell_view_extension_type_register(GTypeModule *type_module);
void e_rss_folder_tree_model_extension_type_register
(GTypeModule *type_module);
G_END_DECLS
#endif /* MODULE_RSS_H */