1 /**
2 * This file is a part of the Cairo-Dock project
3 *
4 * Copyright : (C) see the 'copyright' file.
5 * E-mail    : see the 'copyright' file.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 3
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18 */
19 
20 #include <glib.h>
21 #include <glib/gprintf.h>
22 
23 #include "applet-struct.h"
24 #include "applet-draw.h"
25 #include "applet-rss.h"
26 
27 const gchar *cExtendedAscii[256-32] = {" ", "!", "\"", "#", "$", "%", "&", "’", "(", ")", "*", "+", ",", "-", ".", "/", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", ";", "<", "=", ">", "?", "@", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "[", "\"", "]", "^", "_", "`", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "{", "|", "}", "~", " ", "€", "�", "‚", "ƒ", "„", "…", "†", "‡", "ˆ", "‰", "Š", "<", "Œ", "�", "Ž", "�", "�", "‘", "’", "“", "”", "•", "–", "—", "˜", "™", "š", "›", "œ", "�", "ž", "Ÿ", " ", "¡", "¢", "£", "¤", "¥", "¦", "§", "¨", "©", "ª", "«", "¬", " ", "®", "¯", "°", "±", "²", "³", "´", "µ", "¶", "·", "¸", "¹", "º", "»", "¼", "½", "¾", "¿", "À", "Á", "Â", "Ã", "Ä", "Å", "Æ", "Ç", "È", "É", "Ê", "Ë", "Ì", "Í", "Î", "Ï", "Ð", "Ñ", "Ò", "Ó", "Ô", "Õ", "Ö", "×", "Ø", "Ù", "Ú", "Û", "Ü", "Ý", "Þ", "ß", "à", "á", "â", "ã", "ä", "å", "æ", "ç", "è", "é", "ê", "ë", "ì", "í", "î", "ï", "ð", "ñ", "ò", "ó", "ô", "õ", "ö", "÷", "ø", "ù", "ú", "û", "ü", "ý", "þ", "ÿ"};
28 
29 /* Insere des retours chariots dans une chaine de caracteres de facon a la faire tenir dans un rectangle donne.
30  */
cd_rssreader_cut_line(gchar * cLine,PangoLayout * pLayout,int iMaxWidth)31 void cd_rssreader_cut_line (gchar *cLine, PangoLayout *pLayout, int iMaxWidth)
32 {
33 	cd_debug ("%s (%s)", __func__, cLine);
34 	// on convertit les caracteres internet.
35 	gchar *str=cLine, *amp;
36 	do
37 	{
38 		amp = strchr (str, '&');
39 		if (!amp)
40 			break;
41 		if (amp[1] == '#' && g_ascii_isdigit (amp[2]) && g_ascii_isdigit (amp[3]) && g_ascii_isdigit (amp[4]) && amp[5] == ';')  // &#039;
42 		{
43 			int i = atoi (amp+2) - 32;
44 			cd_debug ("%d", i);
45 
46 			if (i >= 0 && i < 256 - 32)
47 			{
48 				cd_debug ("%d -> %s", i, cExtendedAscii[i]);
49 				strcpy (amp, cExtendedAscii[i]);
50 				strcpy (amp+strlen (cExtendedAscii[i]), amp+6);
51 			}
52 		}
53 		str = amp + 1;
54 	} while (1);
55 
56 	// on insere des retours chariot pour tenir dans la largeur donnee.
57 	PangoRectangle ink, log;
58 	gchar *sp, *last_sp=NULL;
59 	double w;
60 	int iNbLines = 0;
61 
62 	str = cLine;
63 	while (*str == ' ')  // on saute les espaces en debut de ligne.
64 		str ++;
65 
66 	sp = str;
67 	do
68 	{
69 		sp = strchr (sp+1, ' ');  // on trouve l'espace suivant.
70 		if (!sp)  // plus d'espace, on quitte.
71 			break ;
72 
73 		*sp = '\0';  // on coupe a cet espace.
74 		pango_layout_set_text (pLayout, str, -1);  // on regarde la taille de str a sp.
75 		pango_layout_get_pixel_extents (pLayout, &ink, &log);
76 		//g_print ("%s => w:%d/%d, x:%d/%d\n", str, log.width, ink.width, log.x, ink.x);
77 		w = log.width + log.x;
78 
79 		if (w > iMaxWidth)  // on deborde.
80 		{
81 			if (last_sp != NULL)  // on coupe au dernier espace connu.
82 			{
83 				*sp = ' ';  // on remet l'espace.
84 				*last_sp = '\n';  // on coupe.
85 				iNbLines ++;
86 				str = last_sp + 1;  // on place le debut de ligne apres la coupure.
87 			}
88 			else  // aucun espace, c'est un mot entier.
89 			{
90 				*sp = '\n';  // on coupe apres le mot.
91 				iNbLines ++;
92 				str = sp + 1;  // on place le debut de ligne apres la coupure.
93 			}
94 
95 			while (*str == ' ')  // on saute les espaces en debut de ligne.
96 				str ++;
97 			sp = str;
98 			last_sp = NULL;
99 		}
100 		else  // ca rentre.
101 		{
102 			*sp = ' ';  // on remet l'espace.
103 			last_sp = sp;  // on memorise la derniere cesure qui fait tenir la ligne en largeur.
104 			sp ++;  // on se place apres.
105 			while (*sp == ' ')  // on saute tous les espaces.
106 				sp ++;
107 		}
108 	} while (sp);
109 
110 	// dernier mot.
111 	pango_layout_set_text (pLayout, str, -1);  // on regarde la taille de str a sp.
112 	pango_layout_get_pixel_extents (pLayout, &ink, &log);
113 	w = log.width + log.x;
114 	if (w > iMaxWidth)  // on deborde.
115 	{
116 		if (last_sp != NULL)  // on coupe au dernier espace connu.
117 			*last_sp = '\n';
118 	}
119 }
120 
121 
_free_item(CDRssItem * pItem)122 static void _free_item (CDRssItem *pItem)
123 {
124 	if (pItem == NULL)
125 		return;
126 
127 	g_free (pItem->cTitle);
128 	g_free (pItem->cDescription);
129 	g_free (pItem->cLink);
130 	g_free (pItem->cDate);
131 	g_free (pItem);
132 }
cd_rssreader_free_item_list(GldiModuleInstance * myApplet)133 void cd_rssreader_free_item_list (GldiModuleInstance *myApplet)
134 {
135 	if (myData.pItemList == NULL)
136 		return;
137 	CDRssItem *pItem;
138 	GList *it;
139 	for (it = myData.pItemList; it != NULL; it = it->next)
140 	{
141 		pItem = it->data;
142 		_free_item (pItem);
143 	}
144 	g_list_free (myData.pItemList);
145 	myData.pItemList = NULL;
146 	myData.iFirstDisplayedItem = 0;
147 
148 	gldi_object_unref (GLDI_OBJECT(myData.pDialog));  // un peu bourrin mais bon ... on pourrait le reafficher s'il etait present ...
149 	myData.pDialog = NULL;
150 }
151 
152 
_get_feeds(CDSharedMemory * pSharedMemory)153 static void _get_feeds (CDSharedMemory *pSharedMemory)
154 {
155 	if (pSharedMemory->cUrl == NULL)
156 		return ;
157 
158 	gchar *cUrlWithLoginPwd = NULL;
159 	if (pSharedMemory->cUrlLogin && pSharedMemory->cUrlPassword
160 	&& *pSharedMemory->cUrlLogin != '\0' && *pSharedMemory->cUrlPassword != '\0')
161 	{
162 		// An URL is composed of that: "protocol://login:password@server/path"
163 		// so look for the "://" string and insert "login:password@" at that place
164 		gchar *location = g_strstr_len(pSharedMemory->cUrl, 10, "://");
165 		if( location )
166 		{
167 			gsize length_first_part = location - pSharedMemory->cUrl + 3;
168 			if( length_first_part > 0 )
169 			{
170 				gchar *first_part = g_strndup(pSharedMemory->cUrl, length_first_part);
171 				cUrlWithLoginPwd = g_strdup_printf("%s%s:%s@%s", first_part, pSharedMemory->cUrlLogin, pSharedMemory->cUrlPassword, location+3);
172 				g_free(first_part);
173 			}
174 		}
175 	}
176 
177 	pSharedMemory->cTaskBridge = cairo_dock_get_url_data (cUrlWithLoginPwd ? cUrlWithLoginPwd : pSharedMemory->cUrl, NULL);
178 	g_free (cUrlWithLoginPwd);
179 }
180 
_parse_rss_item(xmlNodePtr node,CDRssItem * pItem,GList * pItemList)181 static GList * _parse_rss_item (xmlNodePtr node, CDRssItem *pItem, GList *pItemList)
182 {
183 	xmlChar *content;
184 	xmlNodePtr item;
185 	for (item = node->children; item != NULL; item = item->next)
186 	{
187 		cd_debug ("  + item: %s", item->name);
188 		if (xmlStrcmp (item->name, BAD_CAST "item") == 0)  // c'est un nouvel item.
189 		{
190 			CDRssItem *pNewItem = g_new0 (CDRssItem, 1);
191 			pItemList = g_list_prepend (pItemList, pNewItem);
192 
193 			pItemList = _parse_rss_item (item, pNewItem, pItemList);
194 		}
195 		else if (xmlStrcmp (item->name, BAD_CAST "title") == 0)  // c'est le titre.
196 		{
197 			if (pItem->cTitle == NULL)  // cas du titre du flux force a une valeur par l'utilisateur.
198 			{
199 				content = xmlNodeGetContent (item);
200 				if (content != NULL)
201 				{
202 					// remove leading and trailing carriage returns.
203 					gchar *str = (gchar *) content;
204 					while (*str == '\n') str ++;
205 					int n = strlen(str);
206 					while (str[n-1] == '\n') str[--n] = '\0';
207 
208 					pItem->cTitle = g_strdup (str);
209 
210 					xmlFree (content);
211 				}
212 			}
213 			//g_print ("   + titre : '%s'\n", pItem->cTitle);
214 		}
215 		else if (xmlStrcmp (item->name, BAD_CAST "description") == 0)  // c'est la description.
216 		{
217 			content = xmlNodeGetContent (item);
218 			pItem->cDescription = g_strdup ((gchar *)content);
219 			xmlFree (content);
220 
221 			// on elimine les balises integrees a la description.
222 			gchar *str = pItem->cDescription, *balise, *balise2;
223 			do
224 			{
225 				balise2 = NULL;
226 				balise = strchr (str, '<');  // debut de balise ("<")
227 				if (balise)
228 				{
229 					balise2 = strchr (balise+1, '>');  // fin de balise (">")
230 					if (balise2)
231 					{
232 						strcpy (balise, balise2+1);
233 						str = balise;
234 					}
235 				}
236 			}
237 			while (balise2);
238 			str = pItem->cDescription;
239 			do
240 			{
241 				balise = g_strstr_len (str, -1, "&nbsp;");
242 				if (balise)
243 				{
244 					memset (balise, ' ', 6);
245 					str = balise+6;
246 				}
247 			}
248 			while (balise);
249 			cd_debug ("   + description : '%s'", pItem->cDescription);
250 		}
251 		else if (xmlStrcmp (item->name, BAD_CAST "link") == 0)  // c'est le lien.
252 		{
253 			content = xmlNodeGetContent (item);
254 			pItem->cLink = g_strdup ((gchar *)content);
255 			xmlFree (content);
256 			cd_debug ("   + link : '%s'", pItem->cLink);
257 		}
258 		else if (xmlStrcmp (item->name, BAD_CAST "pubDate") == 0 || xmlStrcmp (item->name, BAD_CAST "date") == 0)  // c'est la date (pubDate pour RSS, data pour RDF).
259 		{
260 			content = xmlNodeGetContent (item);
261 			pItem->cDate = g_strdup ((gchar *)content);
262 			xmlFree (content);
263 		}
264 		// pour recuperer l'image, on dirait qu'on a plusieurs cas, entre autre :
265 		// <enclosure url="http://medias.lemonde.fr/mmpub/edt/ill/2009/11/01/h_1_ill_1261356_5d02_258607.jpg" length="2514" type="image/jpeg" />  ----> bien verifier que c'est une image.
266 		// ou
267 		// <media:thumbnail url="http://www.france24.com/fr/files_fr/EN-interview-Gursel-m.jpg" />
268 	}
269 	return pItemList;
270 }
271 
_parse_atom_item(xmlNodePtr node,CDRssItem * pItem,GList * pItemList,const gchar * cBaseUrl)272 static GList * _parse_atom_item (xmlNodePtr node, CDRssItem *pItem, GList *pItemList, const gchar *cBaseUrl)
273 {
274 	xmlChar *content;
275 	xmlNodePtr item, author;
276 	for (item = node->children; item != NULL; item = item->next)
277 	{
278 		if (xmlStrcmp (item->name, BAD_CAST "entry") == 0)  // c'est un nouvel item.
279 		{
280 			CDRssItem *pNewItem = g_new0 (CDRssItem, 1);
281 			pItemList = g_list_prepend (pItemList, pNewItem);
282 
283 			pItemList = _parse_atom_item (item, pNewItem, pItemList, cBaseUrl);
284 		}
285 		else if (xmlStrcmp (item->name, BAD_CAST "title") == 0)  // c'est le titre.
286 		{
287 			if (pItem->cTitle == NULL)  // cas du titre du flux force a une valeur par l'utilisateur.
288 			{
289 				content = xmlNodeGetContent (item);
290 				if (content != NULL)
291 				{
292 					// remove leading and trailing carriage returns.
293 					gchar *str = (gchar *)content;
294 					while (*str == '\n') str ++;
295 					int n = strlen(str);
296 					while (str[n-1] == '\n') str[--n] = '\0';
297 
298 					pItem->cTitle = g_strdup (str);
299 
300 					xmlFree (content);
301 				}
302 			}
303 			cd_debug ("+ title : '%s'", pItem->cTitle);
304 		}
305 		else if (xmlStrcmp (item->name, BAD_CAST "content") == 0)  // c'est la description.
306 		{
307 			xmlAttrPtr attr = xmlHasProp (item, BAD_CAST "type");
308 			if (attr && attr->children)
309 			{
310 				cd_debug ("   description type : %s", attr->children->content);
311 				if (strncmp ((gchar *)attr->children->content, "text", 4) != 0)
312 				{
313 					continue;
314 				}
315 			}
316 			content = xmlNodeGetContent (item);
317 			pItem->cDescription = g_strdup ((gchar *)content);
318 			xmlFree (content);
319 
320 			// on elimine les balises integrees a la description.
321 			gchar *str = pItem->cDescription, *balise, *balise2;
322 			do
323 			{
324 				balise2 = NULL;
325 				balise = strchr (str, '<');  // debut de balise ("<")
326 				if (balise)
327 				{
328 					balise2 = strchr (balise+1, '>');  // fin de balise (">")
329 					if (balise2)
330 					{
331 						strcpy (balise, balise2+1);
332 						str = balise;
333 					}
334 				}
335 			}
336 			while (balise2);
337 			cd_debug ("+ description : '%s'", pItem->cDescription);
338 		}
339 		else if (xmlStrcmp (item->name, BAD_CAST "link") == 0)  // c'est le lien.
340 		{
341 			xmlAttrPtr attr = xmlHasProp (item, BAD_CAST "type");  // type="text/html" rel="alternate"
342 			if (attr && attr->children)
343 			{
344 				cd_debug ("   link type : %s", attr->children->content);
345 				if (strncmp ((gchar *)attr->children->content, "text", 4) != 0)
346 				{
347 					continue;
348 				}
349 			}
350 			attr = xmlHasProp (item, BAD_CAST "href");
351 			if (attr && attr->children)
352 			{
353 				content = xmlNodeGetContent (attr->children);
354 				if (strncmp ((gchar *)content, "http://", 7) == 0)
355 				{
356 					pItem->cLink = g_strdup ((gchar *)content);
357 				}
358 				else if (cBaseUrl != NULL)
359 				{
360 					pItem->cLink = g_strdup_printf ("%s%s", cBaseUrl, (gchar *)content);
361 				}
362 				xmlFree (content);
363 				cd_debug ("+ link : '%s'", pItem->cLink);
364 			}
365 		}
366 		else if (xmlStrcmp (item->name, BAD_CAST "updated") == 0)  // c'est la date.
367 		{
368 			content = xmlNodeGetContent (item);
369 			pItem->cDate = g_strdup ((gchar *)content);
370 			xmlFree (content);
371 			cd_debug ("+ date : '%s'", pItem->cDate);
372 		}
373 		else if (xmlStrcmp (item->name, BAD_CAST "author") == 0)  // c'est l'auteur.
374 		{
375 			for (author = item->children; author != NULL; author = author->next)
376 			{
377 				if (xmlStrcmp (author->name, BAD_CAST "name") == 0)  // c'est le nom de l'auteur.
378 				{
379 					content = xmlNodeGetContent (author);
380 					pItem->cAuthor = g_strdup ((gchar *)content);
381 					xmlFree (content);
382 					cd_debug ("+ author : '%s'", pItem->cAuthor);
383 				}
384 			}
385 		}
386 		// et pour l'image je ne sais pas.
387 	}
388 	return pItemList;
389 }
390 
_insert_error_message(GldiModuleInstance * myApplet,const gchar * cErrorMessage)391 static void _insert_error_message (GldiModuleInstance *myApplet, const gchar *cErrorMessage)
392 {
393 	cd_debug ("%s (%s, %d)", __func__, cErrorMessage, myData.bError);
394 	CDRssItem *pItem;
395 	if (myData.pItemList != NULL)  // on garde la liste courante, mais on insere un message.
396 	{
397 		if (! myData.bError)  // pas encore de message d'erreur, on en insere un.
398 		{
399 			pItem = g_new0 (CDRssItem, 1);
400 			pItem->cTitle = g_strdup (D_("Warning: couldn't retrieve data last time we tried."));
401 			myData.pItemList = g_list_insert (myData.pItemList, pItem, 1);
402 		}
403 	}
404 	else  // aucune liste : c'est la 1ere recuperation => on met le titre si possible, suivi du message d'erreur.
405 	{
406 		pItem = g_new0 (CDRssItem, 1);
407 		myData.pItemList = g_list_prepend (myData.pItemList, pItem);
408 		if (myConfig.cUserTitle != NULL && myConfig.cUrl != NULL)  // si le titre est connu on l'utilise (si aucun URL n'est defini ce n'est pas pertinent par contre).
409 		{
410 			pItem->cTitle = g_strdup (myConfig.cUserTitle);
411 			pItem = g_new0 (CDRssItem, 1);
412 			myData.pItemList = g_list_append (myData.pItemList, pItem);
413 		}
414 		pItem->cTitle = g_strdup (cErrorMessage);
415 	}
416 
417 	myData.bError = TRUE;
418 }
419 
_update_from_feeds(CDSharedMemory * pSharedMemory)420 static gboolean _update_from_feeds (CDSharedMemory *pSharedMemory)
421 {
422 	GldiModuleInstance *myApplet = pSharedMemory->pApplet;
423 	CD_APPLET_ENTER;
424 	if (! myData.bInit)  // pas encore initialise, on vire le message d'attente.
425 	{
426 		cd_rssreader_free_item_list (myApplet);
427 		myData.pItemList = NULL;
428 		myData.bInit = TRUE;
429 	}
430 
431 	// On parse le flux XML.
432 	if (pSharedMemory->cTaskBridge == NULL || *pSharedMemory->cTaskBridge == '\0')
433 	{
434 		cd_warning ("RSSresader : no data");
435 		const gchar *cErrorMessage = (myConfig.cUrl == NULL ?
436 			D_("No URL is defined.") :
437 			D_("No data (no connection?)"));
438 		_insert_error_message (myApplet, cErrorMessage);
439 		if (myDesklet)
440 		{
441 			cd_applet_update_my_icon (myApplet);
442 		}
443 		myData.bUpdateIsManual = FALSE;
444 
445 		if (myData.pTask->iPeriod > 20)
446 		{
447 			cd_message ("no data, will re-try in 20s");
448 			gldi_task_change_frequency (myData.pTask, 20);  // on re-essaiera dans 20s.
449 		}
450 
451 		CD_APPLET_LEAVE (TRUE);
452 	}
453 
454 	if (myData.pTask->iPeriod != myConfig.iRefreshTime)
455 	{
456 		cd_message ("revert to normal frequency");
457 		gldi_task_change_frequency (myData.pTask, myConfig.iRefreshTime);
458 	}
459 
460 	//g_print (" --> RSS: '%s'\n", myData.cTaskBridge);
461 	xmlDocPtr doc = xmlParseMemory (pSharedMemory->cTaskBridge, strlen (pSharedMemory->cTaskBridge));
462 	g_free (pSharedMemory->cTaskBridge);
463 	pSharedMemory->cTaskBridge = NULL;
464 
465 	if (doc == NULL)
466 	{
467 		cd_warning ("RSSresader : got invalid XML data");
468 		const gchar *cErrorMessage = D_("Invalid data (invalid RSS/Atom feed?)");
469 		_insert_error_message (myApplet, cErrorMessage);
470 		if (myDesklet)
471 		{
472 			cd_applet_update_my_icon (myApplet);
473 		}
474 		g_free (myData.PrevFirstTitle);
475 		myData.PrevFirstTitle = NULL;
476 		myData.bUpdateIsManual = FALSE;
477 		CD_APPLET_LEAVE (TRUE);
478 	}
479 
480 	xmlNodePtr rss = xmlDocGetRootElement (doc);
481 	if (rss == NULL || (xmlStrcmp (rss->name, BAD_CAST "rss") != 0 && xmlStrcmp (rss->name, BAD_CAST "feed") != 0 && xmlStrcmp (rss->name, BAD_CAST "RDF") != 0))
482 	{
483 		cd_warning ("RSSresader : got invalid XML data");
484 		///xmlCleanupParser ();
485 		xmlFreeDoc (doc);
486 
487 		const gchar *cErrorMessage = D_("Invalid data (invalid RSS/Atom feed?)");
488 		_insert_error_message (myApplet, cErrorMessage);
489 		if (myDesklet)
490 		{
491 			cd_applet_update_my_icon (myApplet);
492 		}
493 		g_free (myData.PrevFirstTitle);
494 		myData.PrevFirstTitle = NULL;
495 		myData.bUpdateIsManual = FALSE;
496 		CD_APPLET_LEAVE (TRUE);
497 	}
498 
499 	// on extrait chaque item.
500 	GList *pNewItemList = NULL;
501 	CDRssItem *pItem = g_new0 (CDRssItem, 1);  // on commence au debut de la liste (c'est le titre).
502 	pNewItemList = g_list_prepend (pNewItemList, pItem);
503 	if (myConfig.cUserTitle != NULL)
504 		pItem->cTitle = g_strdup (myConfig.cUserTitle);  // ne sera pas ecrase par les donnees du flux.
505 
506 	if (xmlStrcmp (rss->name, BAD_CAST "rss") == 0)  // RSS
507 	{
508 		xmlAttrPtr attr = xmlHasProp (rss, BAD_CAST "version");
509 		if (attr && attr->children)
510 		{
511 			cd_debug ("RSS version : %s", attr->children->content);
512 		}
513 
514 		xmlNodePtr channel;
515 		for (channel = rss->children; channel != NULL; channel = channel->next)
516 		{
517 			if (xmlStrcmp (channel->name, BAD_CAST "channel") == 0)
518 			{
519 				pNewItemList = _parse_rss_item (channel, pItem, pNewItemList);  // on parse le channel comme un item, ce qui fait que le titre du flux est considere comme un simple item.
520 				break;  // un seul channel.
521 			}
522 		}
523 	}
524 	else if (xmlStrcmp (rss->name, BAD_CAST "RDF") == 0)  // RDF
525 	{
526 		pNewItemList = _parse_rss_item (rss, pItem, pNewItemList);  // on parse le premier groupe "channel" comme un item, ce qui fait que le titre du flux est considere comme un simple item.
527 	}
528 	else  // Atom
529 	{
530 		xmlNodePtr feed = rss;
531 		gchar *cBaseUrl = NULL;  // on recupere la base de l'URL, pour le cas ou les link seraient exprimes relativement a elle.
532 		gchar *str = g_strstr_len(myConfig.cUrl, 10, "://");
533 		if (str)
534 		{
535 			str = strchr (str + 3, '/');
536 			if (str)
537 				cBaseUrl = g_strndup (myConfig.cUrl, (gpointer)str-(gpointer)myConfig.cUrl);
538 		}
539 		pNewItemList = _parse_atom_item (feed, pItem, pNewItemList, cBaseUrl);  // on parse le feed comme un item, ce qui fait que le titre du flux est considere comme un simple item.
540 		g_free (cBaseUrl);
541 	}
542 	pNewItemList = g_list_reverse (pNewItemList);
543 
544 	xmlFreeDoc (doc);  // ne pas utiliser xmlCleanupParser dans un thread !
545 
546 	// si aucune donnee, on l'affiche et on quitte.
547 	if (pNewItemList == NULL)
548 	{
549 		cd_debug ("RSS: aucune donnee");
550 
551 		const gchar *cErrorMessage = D_("No data");
552 		_insert_error_message (myApplet, cErrorMessage);
553 		if (myDesklet)
554 		{
555 			cd_applet_update_my_icon (myApplet);
556 		}
557 		g_free (myData.PrevFirstTitle);
558 		myData.PrevFirstTitle = NULL;
559 		myData.bUpdateIsManual = FALSE;
560 		CD_APPLET_LEAVE (TRUE);
561 	}
562 
563 	// si on est arrive a ce point, c'est qu'il n'y a pas eu d'erreur.
564 	// on vide l'ancienne liste d'items.
565 	cd_rssreader_free_item_list (myApplet);
566 	myData.pItemList = pNewItemList;
567 
568 	// on met a jour le titre.
569 	if (myIcon->cName == NULL)  // il faut mettre a jour le titre
570 	{
571 		if (myDock && myConfig.cUserTitle == NULL)  // en mode desklet inutile, le titre sera redessine avec le reste.
572 		{
573 			pItem = myData.pItemList->data;
574 			if (pItem != NULL && pItem->cTitle != NULL)
575 				CD_APPLET_SET_NAME_FOR_MY_ICON (pItem->cTitle);
576 		}
577 	}
578 
579 	// si aucun changement, on le signale, et si aucune erreur precedente, on quitte.
580 	pItem = (myData.pItemList && myData.pItemList->next ? myData.pItemList->next->data : NULL);
581 	gchar *cFirstTitle = (pItem ? pItem->cTitle : NULL);
582 	if (! cairo_dock_strings_differ (myData.PrevFirstTitle, cFirstTitle))
583 	{
584 		cd_debug ("RSS: aucune modif");
585 
586 		if (myData.bUpdateIsManual)  // L'update a ete manuel -> On affiche donc un dialogue meme s'il n'y a pas eu de changement
587 		{
588 			gldi_dialogs_remove_on_icon (myIcon);
589 			gldi_dialog_show_temporary_with_icon (D_("No modification"),
590 				myIcon,
591 				myContainer,
592 				2000, // Suffisant vu que la MaJ est manuelle
593 				myDock ? "same icon" : MY_APPLET_SHARE_DATA_DIR"/"MY_APPLET_ICON_FILE);
594 
595 			myData.bUpdateIsManual = FALSE;
596 		}
597 
598 		if (! myData.bError)
599 			CD_APPLET_LEAVE (TRUE);
600 	}
601 
602 	// on dessine le texte.
603 	if (myDesklet)
604 	{
605 		cd_applet_update_my_icon (myApplet);
606 	}
607 
608 	// on avertit l'utilisateur.
609 	if (myData.PrevFirstTitle != NULL && myConfig.iNotificationType != 0)
610 	{
611 		if (myConfig.iNotificationType != 1)
612 		{
613 			gldi_dialogs_remove_on_icon (myIcon);
614 			gldi_dialog_show_temporary_with_icon (D_("This RSS feed has been modified..."),
615 				myIcon,
616 				myContainer,
617 				1000*myConfig.iNotificationDuration,
618 				myDock ? "same icon" : MY_APPLET_SHARE_DATA_DIR"/"MY_APPLET_ICON_FILE);
619 		}
620 		if (myConfig.iNotificationType != 2)
621 		{
622 			CD_APPLET_DEMANDS_ATTENTION (myConfig.cNotificationAnimation, 3);  /// myConfig.iNotificationDuration ?...
623 		}
624 	}
625 
626 	g_free (myData.PrevFirstTitle);
627 	myData.PrevFirstTitle = g_strdup (cFirstTitle);
628 	myData.bUpdateIsManual = FALSE;
629 	myData.bError = FALSE;
630 	CD_APPLET_LEAVE (TRUE);
631 }
632 
633 
_free_shared_memory(CDSharedMemory * pSharedMemory)634 static void _free_shared_memory (CDSharedMemory *pSharedMemory)
635 {
636 	g_free (pSharedMemory->cUrl);
637 	g_free (pSharedMemory->cUrlLogin);
638 	g_free (pSharedMemory->cUrlPassword);
639 	g_free (pSharedMemory->cTaskBridge);
640 	g_free (pSharedMemory);
641 }
cd_rssreader_launch_task(GldiModuleInstance * myApplet)642 void cd_rssreader_launch_task (GldiModuleInstance *myApplet)
643 {
644 	if (myData.pTask != NULL)
645 	{
646 		gldi_task_discard (myData.pTask);
647 		myData.pTask = NULL;
648 	}
649 
650 	CDSharedMemory *pSharedMemory = g_new0 (CDSharedMemory, 1);
651 	pSharedMemory->cUrl = g_strdup (myConfig.cUrl);
652 	pSharedMemory->cUrlLogin = g_strdup (myConfig.cUrlLogin);
653 	pSharedMemory->cUrlPassword = g_strdup (myConfig.cUrlPassword);
654 	pSharedMemory->pApplet = myApplet;
655 
656 	myData.pTask = gldi_task_new_full (myConfig.iRefreshTime,
657 		(GldiGetDataAsyncFunc) _get_feeds,
658 		(GldiUpdateSyncFunc) _update_from_feeds,
659 		(GFreeFunc) _free_shared_memory,
660 		pSharedMemory);
661 	gldi_task_launch (myData.pTask);
662 }
663 
664 
665 
_on_dialog_destroyed(GldiModuleInstance * myApplet)666 static void _on_dialog_destroyed (GldiModuleInstance *myApplet)
667 {
668 	CD_APPLET_ENTER;
669 	myData.pDialog = NULL;
670 	CD_APPLET_LEAVE();
671 }
cd_rssreader_show_dialog(GldiModuleInstance * myApplet)672 void cd_rssreader_show_dialog (GldiModuleInstance *myApplet)
673 {
674 	if (myData.pDialog != NULL)  // on detruit le dialogue actuel.
675 	{
676 		gldi_object_unref (GLDI_OBJECT(myData.pDialog));
677 		myData.pDialog = NULL;
678 		return;
679 	}
680 	gldi_dialogs_remove_on_icon (myIcon);  // on enleve tout autre dialogue (message d'erreur).
681 
682 	if (myData.pItemList != NULL && myData.pItemList->next != NULL && (myData.pItemList->next->next != NULL || ! myData.bError))  // on construit le dialogue contenant toutes les infos.
683 	{
684 		// On construit le widget GTK qui contient les lignes avec les liens.
685 		GtkWidget *pVBox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);  // le widget qu'on va inserer dans le dialogue.
686 		GtkWidget *pScrolledWindow = gtk_scrolled_window_new (NULL, NULL);
687 		g_object_set (pScrolledWindow, "height-request", 250, NULL);
688 		gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (pScrolledWindow), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
689 		#if GTK_CHECK_VERSION (3, 8, 0)
690 		gtk_container_add (GTK_CONTAINER (pScrolledWindow), pVBox);
691 		#else
692 		gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (pScrolledWindow), pVBox);
693 		#endif
694 
695 		PangoLayout *pLayout = pango_cairo_create_layout (myDrawContext);
696 		PangoFontDescription *fd = pango_font_description_from_string ("");
697 		pango_layout_set_font_description (pLayout, fd);
698 
699 		int w = MIN (600, g_desktopGeometry.Xscreen.width / g_desktopGeometry.iNbScreens / 2);  // we don't know on which screen is place the container...
700 		gchar *cLine;
701 		GtkWidget *pLinkButton, *pAlign;
702 		CDRssItem *pItem;
703 		GList *it;
704 		for (it = myData.pItemList->next; it != NULL; it = it->next)
705 		{
706 			pItem = it->data;
707 			if (pItem->cTitle == NULL)
708 				continue;
709 
710 			cLine = g_strdup (pItem->cTitle);
711 			cd_rssreader_cut_line (cLine, pLayout, w);
712 
713 			if (pItem->cLink != NULL)
714 				pLinkButton = gtk_link_button_new_with_label (pItem->cLink, cLine);
715 			else
716 				pLinkButton = gtk_label_new (cLine);
717 			g_free (cLine);
718 
719 			pAlign = gtk_alignment_new (0., 0.5, 0., 0.);
720 			gtk_container_add (GTK_CONTAINER (pAlign), pLinkButton);
721 			gtk_box_pack_start (GTK_BOX (pVBox), pAlign, FALSE, FALSE, 0);
722 
723 			if (pItem->cDescription != NULL)
724 			{
725 				cLine = g_strdup (pItem->cDescription);
726 				cd_rssreader_cut_line (cLine, pLayout, w);
727 				pLinkButton = gtk_label_new (cLine);
728 				gtk_label_set_selectable (GTK_LABEL (pLinkButton), TRUE);
729 				g_free (cLine);
730 
731 				pAlign = gtk_alignment_new (0., 0.5, 0., 0.);
732 				gtk_alignment_set_padding (GTK_ALIGNMENT (pAlign), 0, 0, 20, 0);
733 				gtk_container_add (GTK_CONTAINER (pAlign), pLinkButton);
734 				gtk_box_pack_start (GTK_BOX (pVBox), pAlign, FALSE, FALSE, 0);
735 			}
736 
737 			if (pItem->cAuthor != NULL)
738 			{
739 				gchar *by = g_strdup_printf ("  [by %s]", pItem->cAuthor);
740 				pLinkButton = gtk_label_new (by);
741 				g_free (by);
742 
743 				pAlign = gtk_alignment_new (0., 0.5, 0., 0.);
744 				gtk_alignment_set_padding (GTK_ALIGNMENT (pAlign), 0, 0, 40, 0);
745 				gtk_container_add (GTK_CONTAINER (pAlign), pLinkButton);
746 				gtk_box_pack_start (GTK_BOX (pVBox), pAlign, FALSE, FALSE, 0);
747 			}
748 
749 			if (pItem->cDate != NULL)
750 			{
751 				pLinkButton = gtk_label_new (pItem->cDate);
752 
753 				pAlign = gtk_alignment_new (1., 0.5, 0., 0.);
754 				gtk_alignment_set_padding (GTK_ALIGNMENT (pAlign), 0, 0, 40, 0);
755 				gtk_container_add (GTK_CONTAINER (pAlign), pLinkButton);
756 				gtk_box_pack_start (GTK_BOX (pVBox), pAlign, FALSE, FALSE, 0);
757 			}
758 		}
759 		pango_font_description_free (fd);
760 
761 		pItem = myData.pItemList->data;  // le nom du flux en titre du dialogue.
762 
763 		// on affiche le dialogue.
764 		myData.pDialog = gldi_dialog_show (pItem->cTitle,
765 			myIcon, myContainer,
766 			0,
767 			myDock ? "same icon" : MY_APPLET_SHARE_DATA_DIR"/"MY_APPLET_ICON_FILE,
768 			pScrolledWindow,
769 			NULL,
770 			myApplet,
771 			(GFreeFunc)_on_dialog_destroyed);
772 		/**g_signal_connect (G_OBJECT (myData.pDialog->container.pWidget),
773 			"button-press-event",
774 			G_CALLBACK (on_button_press_dialog),
775 			myApplet);*/
776 	}
777 	else  // on affiche un message clair a l'utilisateur.
778 	{
779 		if (myConfig.cUrl == NULL)
780 			gldi_dialog_show_temporary_with_icon (D_("No URL is defined\nYou can define one by copying the URL in the clipboard,\n and selecting \"Paste the URL\" in the menu."),
781 				myIcon,
782 				myContainer,
783 				1000*myConfig.iNotificationDuration,
784 				myDock ? "same icon" : MY_APPLET_SHARE_DATA_DIR"/"MY_APPLET_ICON_FILE);
785 		else
786 			gldi_dialog_show_temporary_with_icon (D_("No data\nDid you set a valid RSS feed?\nIs your connection alive?"),
787 				myIcon,
788 				myContainer,
789 				1000*myConfig.iNotificationDuration,
790 				myDock ? "same icon" : MY_APPLET_SHARE_DATA_DIR"/"MY_APPLET_ICON_FILE);
791 	}
792 }
793