Implement News & Blogs (RSS) reader
This commit is contained in:
@ -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.
|
||||
|
98
data/icons/hicolor_status_scalable_rss-symbolic.svg
Normal file
98
data/icons/hicolor_status_scalable_rss-symbolic.svg
Normal 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 |
110
data/icons/hicolor_status_scalable_rss.svg
Normal file
110
data/icons/hicolor_status_scalable_rss.svg
Normal 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 |
@ -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
|
||||
|
@ -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)
|
||||
|
2
src/modules/rss/CMakeLists.txt
Normal file
2
src/modules/rss/CMakeLists.txt
Normal file
@ -0,0 +1,2 @@
|
||||
add_subdirectory(camel)
|
||||
add_subdirectory(evolution)
|
852
src/modules/rss/camel-rss-store-summary.c
Normal file
852
src/modules/rss/camel-rss-store-summary.c
Normal 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);
|
||||
}
|
116
src/modules/rss/camel-rss-store-summary.h
Normal file
116
src/modules/rss/camel-rss-store-summary.h
Normal 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 */
|
50
src/modules/rss/camel/CMakeLists.txt
Normal file
50
src/modules/rss/camel/CMakeLists.txt
Normal 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}
|
||||
)
|
412
src/modules/rss/camel/camel-rss-folder-summary.c
Normal file
412
src/modules/rss/camel/camel-rss-folder-summary.c
Normal 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;
|
||||
}
|
69
src/modules/rss/camel/camel-rss-folder-summary.h
Normal file
69
src/modules/rss/camel/camel-rss-folder-summary.h
Normal 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 */
|
778
src/modules/rss/camel/camel-rss-folder.c
Normal file
778
src/modules/rss/camel/camel-rss-folder.c
Normal 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;
|
||||
}
|
55
src/modules/rss/camel/camel-rss-folder.h
Normal file
55
src/modules/rss/camel/camel-rss-folder.h
Normal 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 */
|
94
src/modules/rss/camel/camel-rss-provider.c
Normal file
94
src/modules/rss/camel/camel-rss-provider.c
Normal 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);
|
||||
}
|
301
src/modules/rss/camel/camel-rss-settings.c
Normal file
301
src/modules/rss/camel/camel-rss-settings.c
Normal 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");
|
||||
}
|
76
src/modules/rss/camel/camel-rss-settings.h
Normal file
76
src/modules/rss/camel/camel-rss-settings.h
Normal 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 */
|
397
src/modules/rss/camel/camel-rss-store.c
Normal file
397
src/modules/rss/camel/camel-rss-store.c
Normal 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;
|
||||
}
|
55
src/modules/rss/camel/camel-rss-store.h
Normal file
55
src/modules/rss/camel/camel-rss-store.h
Normal 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 */
|
1
src/modules/rss/camel/libcamelrss.urls
Normal file
1
src/modules/rss/camel/libcamelrss.urls
Normal file
@ -0,0 +1 @@
|
||||
rss
|
656
src/modules/rss/e-rss-parser.c
Normal file
656
src/modules/rss/e-rss-parser.c
Normal 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;
|
||||
}
|
48
src/modules/rss/e-rss-parser.h
Normal file
48
src/modules/rss/e-rss-parser.h
Normal 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 */
|
32
src/modules/rss/evolution/CMakeLists.txt
Normal file
32
src/modules/rss/evolution/CMakeLists.txt
Normal 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
|
||||
)
|
217
src/modules/rss/evolution/e-rss-folder-tree-model-extension.c
Normal file
217
src/modules/rss/evolution/e-rss-folder-tree-model-extension.c
Normal 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);
|
||||
}
|
1437
src/modules/rss/evolution/e-rss-preferences.c
Normal file
1437
src/modules/rss/evolution/e-rss-preferences.c
Normal file
File diff suppressed because it is too large
Load Diff
20
src/modules/rss/evolution/e-rss-preferences.h
Normal file
20
src/modules/rss/evolution/e-rss-preferences.h
Normal 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 */
|
132
src/modules/rss/evolution/e-rss-shell-extension.c
Normal file
132
src/modules/rss/evolution/e-rss-shell-extension.c
Normal 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);
|
||||
}
|
277
src/modules/rss/evolution/e-rss-shell-view-extension.c
Normal file
277
src/modules/rss/evolution/e-rss-shell-view-extension.c
Normal 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);
|
||||
}
|
28
src/modules/rss/evolution/module-rss.c
Normal file
28
src/modules/rss/evolution/module-rss.c
Normal 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)
|
||||
{
|
||||
}
|
21
src/modules/rss/evolution/module-rss.h
Normal file
21
src/modules/rss/evolution/module-rss.h
Normal 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 */
|
Reference in New Issue
Block a user