1 /*
2  * @file node.c  hierarchic feed list node handling
3  *
4  * Copyright (C) 2003-2016 Lars Windolf <lars.windolf@gmx.de>
5  * Copyright (C) 2004-2006 Nathan J. Conrad <t98502@users.sourceforge.net>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (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  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  */
21 
22 #include <string.h>
23 
24 #include "common.h"
25 #include "conf.h"
26 #include "db.h"
27 #include "debug.h"
28 #include "favicon.h"
29 #include "feedlist.h"
30 #include "itemlist.h"
31 #include "itemset.h"
32 #include "item_state.h"
33 #include "node.h"
34 #include "node_view.h"
35 #include "render.h"
36 #include "update.h"
37 #include "vfolder.h"
38 #include "fl_sources/node_source.h"
39 #include "ui/liferea_shell.h"
40 #include "ui/feed_list_node.h"
41 
42 static GHashTable *nodes = NULL;	/*<< node id -> node lookup table */
43 
44 #define NODE_ID_LEN	7
45 
46 nodePtr
node_is_used_id(const gchar * id)47 node_is_used_id (const gchar *id)
48 {
49 	if (!id || !nodes)
50 		return NULL;
51 
52 	return (nodePtr)g_hash_table_lookup (nodes, id);
53 }
54 
55 gchar *
node_new_id(void)56 node_new_id (void)
57 {
58 	gchar *id;
59 
60 	id = g_new0 (gchar, NODE_ID_LEN + 1);
61 	do {
62 		int i;
63 		for (i = 0; i < NODE_ID_LEN; i++)
64 			id[i] = (gchar)g_random_int_range ('a', 'z');
65 	} while (NULL != node_is_used_id (id));
66 
67 	return id;
68 }
69 
70 nodePtr
node_from_id(const gchar * id)71 node_from_id (const gchar *id)
72 {
73 	nodePtr node;
74 
75 	node = node_is_used_id (id);
76 	if (!node)
77 		debug1 (DEBUG_GUI, "Fatal: no node with id \"%s\" found!", id);
78 
79 	return node;
80 }
81 
82 nodePtr
node_new(nodeTypePtr type)83 node_new (nodeTypePtr type)
84 {
85 	nodePtr	node;
86 	gchar	*id;
87 
88 	g_assert (NULL != type);
89 
90 	node = g_new0 (struct node, 1);
91 	node->type = type;
92 	node->viewMode = NODE_VIEW_MODE_DEFAULT;
93 	node->sortColumn = NODE_VIEW_SORT_BY_TIME;
94 	node->sortReversed = TRUE;	/* default sorting is newest date at top */
95 	node->available = TRUE;
96 
97 	id = node_new_id ();
98 	node_set_id (node, id);
99 	g_free (id);
100 
101 	return node;
102 }
103 
104 void
node_set_data(nodePtr node,gpointer data)105 node_set_data (nodePtr node, gpointer data)
106 {
107 	g_assert (NULL == node->data);
108 	g_assert (NULL != node->type);
109 
110 	node->data = data;
111 }
112 
113 void
node_set_subscription(nodePtr node,subscriptionPtr subscription)114 node_set_subscription (nodePtr node, subscriptionPtr subscription)
115 {
116 	g_assert (NULL == node->subscription);
117 	g_assert (NULL != node->type);
118 
119 	node->subscription = subscription;
120 	subscription->node = node;
121 
122 	/* Besides the favicon age we have no persistent
123 	   update state field, so everything else goes NULL */
124 	if (node->iconFile && !strstr(node->iconFile, "default.png")) {
125 		subscription->updateState->lastFaviconPoll.tv_sec = common_get_mod_time (node->iconFile);
126 		debug2 (DEBUG_UPDATE, "Setting last favicon poll time for %s to %lu", node->id, subscription->updateState->lastFaviconPoll.tv_sec);
127 	}
128 }
129 
130 void
node_update_subscription(nodePtr node,gpointer user_data)131 node_update_subscription (nodePtr node, gpointer user_data)
132 {
133 	if (node->source->root == node) {
134 		node_source_update (node);
135 		return;
136 	}
137 
138 	if (node->subscription)
139 		subscription_update (node->subscription, GPOINTER_TO_UINT (user_data));
140 
141 	node_foreach_child_data (node, node_update_subscription, user_data);
142 }
143 
144 void
node_auto_update_subscription(nodePtr node)145 node_auto_update_subscription (nodePtr node)
146 {
147 	if (node->source->root == node) {
148 		node_source_auto_update (node);
149 		return;
150 	}
151 
152 	if (node->subscription)
153 		subscription_auto_update (node->subscription);
154 
155 	node_foreach_child (node, node_auto_update_subscription);
156 }
157 
158 void
node_reset_update_counter(nodePtr node,GTimeVal * now)159 node_reset_update_counter (nodePtr node, GTimeVal *now)
160 {
161 	subscription_reset_update_counter (node->subscription, now);
162 
163 	node_foreach_child_data (node, node_reset_update_counter, now);
164 }
165 
166 gboolean
node_is_ancestor(nodePtr node1,nodePtr node2)167 node_is_ancestor (nodePtr node1, nodePtr node2)
168 {
169 	nodePtr	tmp;
170 
171 	tmp = node2->parent;
172 	while (tmp) {
173 		if (node1 == tmp)
174 			return TRUE;
175 		tmp = tmp->parent;
176 	}
177 	return FALSE;
178 }
179 
180 void
node_free(nodePtr node)181 node_free (nodePtr node)
182 {
183 	if (node->data && NODE_TYPE (node)->free)
184 		NODE_TYPE (node)->free (node);
185 
186 	g_assert (NULL == node->children);
187 
188 	g_hash_table_remove (nodes, node->id);
189 
190 	update_job_cancel_by_owner (node);
191 
192 	if (node->subscription)
193 		subscription_free (node->subscription);
194 
195 	if (node->icon)
196 		g_object_unref (node->icon);
197 	g_free (node->iconFile);
198 	g_free (node->title);
199 	g_free (node->id);
200 	g_free (node);
201 }
202 
203 static void
node_calc_counters(nodePtr node)204 node_calc_counters (nodePtr node)
205 {
206 	/* Order is important! First update all children
207 	   so that hierarchical nodes (folders and feed
208 	   list sources) can determine their own unread
209 	   count as the sum of all childs afterwards */
210 	node_foreach_child (node, node_calc_counters);
211 
212 	NODE_TYPE (node)->update_counters (node);
213 }
214 
215 static void
node_update_parent_counters(nodePtr node)216 node_update_parent_counters (nodePtr node)
217 {
218 	guint old;
219 
220 	if (!node)
221 		return;
222 
223 	old = node->unreadCount;
224 
225 	NODE_TYPE (node)->update_counters (node);
226 
227 	if (old != node->unreadCount) {
228 		feed_list_node_update (node->id);
229 		feedlist_new_items (0);	/* add 0 new items, as 'new-items' signal updates unread items also */
230 	}
231 
232 	if (node->parent)
233 		node_update_parent_counters (node->parent);
234 }
235 
236 void
node_update_counters(nodePtr node)237 node_update_counters (nodePtr node)
238 {
239 	guint oldUnreadCount = node->unreadCount;
240 	guint oldItemCount = node->itemCount;
241 
242 	/* Update the node itself and its children */
243 	node_calc_counters (node);
244 
245 	if ((oldUnreadCount != node->unreadCount) ||
246 	    (oldItemCount != node->itemCount))
247 		feed_list_node_update (node->id);
248 
249 	/* Update the unread count of the parent nodes,
250 	   usually they just add all child unread counters */
251 	if (!IS_VFOLDER (node))
252 		node_update_parent_counters (node->parent);
253 }
254 
255 void
node_update_favicon(nodePtr node)256 node_update_favicon (nodePtr node)
257 {
258 	if (NODE_TYPE (node)->capabilities & NODE_CAPABILITY_UPDATE_FAVICON) {
259 		debug1 (DEBUG_UPDATE, "favicon of node %s needs to be updated...", node->title);
260 		subscription_update_favicon (node->subscription);
261 	}
262 
263 	/* Recursion */
264 	if (node->children)
265 		node_foreach_child (node, node_update_favicon);
266 }
267 
268 itemSetPtr
node_get_itemset(nodePtr node)269 node_get_itemset (nodePtr node)
270 {
271 	return NODE_TYPE (node)->load (node);
272 }
273 
274 void
node_mark_all_read(nodePtr node)275 node_mark_all_read (nodePtr node)
276 {
277 	if (!node)
278 		return;
279 
280 	if ((node->unreadCount > 0) || (IS_VFOLDER (node))) {
281 		itemset_mark_read (node);
282 		node->unreadCount = 0;
283 		node->needsUpdate = TRUE;
284 	}
285 
286 	if (node->children)
287 		node_foreach_child (node, node_mark_all_read);
288 }
289 
290 gchar *
node_render(nodePtr node)291 node_render(nodePtr node)
292 {
293 	return NODE_TYPE (node)->render (node);
294 }
295 
296 /* import callbacks and helper functions */
297 
298 void
node_set_parent(nodePtr node,nodePtr parent,gint position)299 node_set_parent (nodePtr node, nodePtr parent, gint position)
300 {
301 	g_assert (NULL != parent);
302 
303 	parent->children = g_slist_insert (parent->children, node, position);
304 	node->parent = parent;
305 
306 	/* new nodes may be provided by another node source, if
307 	   not they are handled by the parents node source */
308 	if (!node->source)
309 		node->source = parent->source;
310 }
311 
312 void
node_reparent(nodePtr node,nodePtr new_parent)313 node_reparent (nodePtr node, nodePtr new_parent)
314 {
315 	nodePtr old_parent;
316 
317 	g_assert (NULL != new_parent);
318 	g_assert (NULL != node);
319 
320 	debug2 (DEBUG_GUI, "Reparenting node '%s' to a parent '%s'", node_get_title(node), node_get_title(new_parent));
321 
322 	old_parent = node->parent;
323 	if (NULL != old_parent)
324 		old_parent->children = g_slist_remove (old_parent->children, node);
325 
326 	new_parent->children = g_slist_insert (new_parent->children, node, -1);
327 	node->parent = new_parent;
328 
329 	feed_list_node_remove_node (node);
330 	feed_list_node_add (node);
331 }
332 
333 void
node_remove(nodePtr node)334 node_remove (nodePtr node)
335 {
336 	/* using itemlist_remove_all_items() ensures correct unread
337 	   and item counters for all parent folders and matching
338 	   search folders */
339 	itemlist_remove_all_items (node);
340 
341 	NODE_TYPE (node)->remove (node);
342 }
343 
344 static xmlDocPtr
node_to_xml(nodePtr node)345 node_to_xml (nodePtr node)
346 {
347 	xmlDocPtr	doc;
348 	xmlNodePtr	rootNode;
349 	gchar		*tmp;
350 
351 	doc = xmlNewDoc("1.0");
352 	rootNode = xmlNewDocNode(doc, NULL, "node", NULL);
353 	xmlDocSetRootElement(doc, rootNode);
354 
355 	xmlNewTextChild(rootNode, NULL, "title", node_get_title(node));
356 
357 	tmp = g_strdup_printf("%u", node->unreadCount);
358 	xmlNewTextChild(rootNode, NULL, "unreadCount", tmp);
359 	g_free(tmp);
360 
361 	tmp = g_strdup_printf("%u", g_slist_length(node->children));
362 	xmlNewTextChild(rootNode, NULL, "children", tmp);
363 	g_free(tmp);
364 
365 	return doc;
366 }
367 
368 gchar *
node_default_render(nodePtr node)369 node_default_render (nodePtr node)
370 {
371 	gchar		*result;
372 	xmlDocPtr	doc;
373 
374 	doc = node_to_xml (node);
375 	result = render_xml (doc, NODE_TYPE(node)->id, NULL);
376 	xmlFreeDoc (doc);
377 
378 	return result;
379 }
380 
381 /* helper functions to be used with node_foreach* */
382 
383 void
node_save(nodePtr node)384 node_save(nodePtr node)
385 {
386 	NODE_TYPE(node)->save(node);
387 }
388 
389 /* node attributes encapsulation */
390 
391 void
node_set_title(nodePtr node,const gchar * title)392 node_set_title (nodePtr node, const gchar *title)
393 {
394 	g_free (node->title);
395 	node->title = g_strstrip (g_strdelimit (g_strdup (title), "\r\n", ' '));
396 }
397 
398 const gchar *
node_get_title(nodePtr node)399 node_get_title (nodePtr node)
400 {
401 	return node->title;
402 }
403 
404 void
node_load_icon(nodePtr node)405 node_load_icon (nodePtr node)
406 {
407 	/* Load pixbuf for all widget based rendering */
408 	if (node->icon)
409 		g_object_unref (node->icon);
410 
411 	node->icon = favicon_load_from_cache (node->id, 32);
412 
413 	/* Create filename for HTML rendering */
414 	g_free (node->iconFile);
415 
416 	if (node->icon)
417 		node->iconFile = common_create_cache_filename ("favicons", node->id, "png");
418 	else
419 		node->iconFile = g_build_filename (PACKAGE_DATA_DIR, PACKAGE, "pixmaps", "default.png", NULL);
420 }
421 
422 /* determines the nodes favicon or default icon */
423 gpointer
node_get_icon(nodePtr node)424 node_get_icon (nodePtr node)
425 {
426 	if (!node->icon)
427 		return (gpointer) NODE_TYPE(node)->icon;
428 
429 	return node->icon;
430 }
431 
432 const gchar *
node_get_favicon_file(nodePtr node)433 node_get_favicon_file (nodePtr node)
434 {
435 	return node->iconFile;
436 }
437 
438 void
node_set_id(nodePtr node,const gchar * id)439 node_set_id (nodePtr node, const gchar *id)
440 {
441 	if (!nodes)
442 		nodes = g_hash_table_new(g_str_hash, g_str_equal);
443 
444 	if (node->id) {
445 		g_hash_table_remove (nodes, node->id);
446 		g_free (node->id);
447 	}
448 	node->id = g_strdup (id);
449 
450 	g_hash_table_insert (nodes, node->id, node);
451 }
452 
453 const gchar *
node_get_id(nodePtr node)454 node_get_id (nodePtr node)
455 {
456 	return node->id;
457 }
458 
459 gboolean
node_set_sort_column(nodePtr node,nodeViewSortType sortColumn,gboolean reversed)460 node_set_sort_column (nodePtr node, nodeViewSortType sortColumn, gboolean reversed)
461 {
462 	if (node->sortColumn == sortColumn &&
463 	    node->sortReversed == reversed)
464 	    	return FALSE;
465 
466 	node->sortColumn = sortColumn;
467 	node->sortReversed = reversed;
468 
469 	return TRUE;
470 }
471 
472 void
node_set_view_mode(nodePtr node,nodeViewType viewMode)473 node_set_view_mode (nodePtr node, nodeViewType viewMode)
474 {
475 	gint	defaultViewMode;
476 
477 	/* To allow users to select a default viewing mode for the layout
478 	   we need to store only exceptions from this mode, which is why
479 	   we compare the mode to be set with the default and if it's equal
480 	   we just set NODE_VIEW_MODE_DEFAULT.
481 
482 	   This allows to not OPML export the viewMode attribute for nodes
483 	   the are in default viewing mode, which then allows to follow
484 	   a switch in the preference to a new default viewing mode.
485 
486 	   This of course also means that the we use some state on each
487 	   changing of the view mode preference.
488         */
489 
490 	conf_get_int_value (DEFAULT_VIEW_MODE, &defaultViewMode);
491 
492 	if (viewMode != (nodeViewType)defaultViewMode)
493 		node->viewMode = viewMode;
494 	else
495 		node->viewMode = NODE_VIEW_MODE_DEFAULT;
496 }
497 
498 nodeViewType
node_get_view_mode(nodePtr node)499 node_get_view_mode (nodePtr node)
500 {
501 	gint	defaultViewMode;
502 
503 	conf_get_int_value (DEFAULT_VIEW_MODE, &defaultViewMode);
504 
505 	if (NODE_VIEW_MODE_DEFAULT == node->viewMode)
506 		return defaultViewMode;
507 	else
508 		return node->viewMode;
509 }
510 
511 const gchar *
node_get_base_url(nodePtr node)512 node_get_base_url(nodePtr node)
513 {
514 	const gchar 	*baseUrl = NULL;
515 
516 	if (node->subscription) {
517 		baseUrl = subscription_get_homepage (node->subscription);
518 		if (!baseUrl)
519 			baseUrl = subscription_get_source (node->subscription);
520 	}
521 
522 
523 	/* prevent feed scraping commands to end up as base URI */
524 	if (!((baseUrl != NULL) &&
525 	      (baseUrl[0] != '|') &&
526 	      (strstr(baseUrl, "://") != NULL)))
527 	   	baseUrl = NULL;
528 
529 	return baseUrl;
530 }
531 
532 gboolean
node_can_add_child_feed(nodePtr node)533 node_can_add_child_feed (nodePtr node)
534 {
535 	g_assert (node->source->root);
536 
537 	if (!(NODE_TYPE (node->source->root)->capabilities & NODE_CAPABILITY_ADD_CHILDS))
538 		return FALSE;
539 
540 	return (NODE_SOURCE_TYPE (node)->capabilities & NODE_SOURCE_CAPABILITY_ADD_FEED);
541 }
542 
543 gboolean
node_can_add_child_folder(nodePtr node)544 node_can_add_child_folder (nodePtr node)
545 {
546 	g_assert (node->source->root);
547 
548 	if (!(NODE_TYPE (node->source->root)->capabilities & NODE_CAPABILITY_ADD_CHILDS))
549 		return FALSE;
550 
551 	return (NODE_SOURCE_TYPE (node)->capabilities & NODE_SOURCE_CAPABILITY_ADD_FOLDER);
552 }
553 
554 /* node children iterating interface */
555 
556 void
node_foreach_child_full(nodePtr node,gpointer func,gint params,gpointer user_data)557 node_foreach_child_full (nodePtr node, gpointer func, gint params, gpointer user_data)
558 {
559 	GSList		*children, *iter;
560 
561 	g_assert (NULL != node);
562 
563 	/* We need to copy because func might modify the list */
564 	iter = children = g_slist_copy (node->children);
565 	while (iter) {
566 		nodePtr childNode = (nodePtr)iter->data;
567 
568 		/* Apply the method to the child */
569 		if (0 == params)
570 			((nodeActionFunc)func) (childNode);
571 		else
572 			((nodeActionDataFunc)func) (childNode, user_data);
573 
574 		/* Never descend! */
575 
576 		iter = g_slist_next (iter);
577 	}
578 
579 	g_slist_free (children);
580 }
581 
582