From f204bc420bd6e7495be3ee28b41e448a542e8eee Mon Sep 17 00:00:00 2001 From: John Jetmore Date: Sat, 21 Jan 2023 11:28:25 -0500 Subject: [PATCH 1/6] remove stray ']' from taglib line in configure help output before: ` --without-taglib disable taglib support]` after: ` --without-taglib disable taglib support` --- configure.ac | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/configure.ac b/configure.ac index 17f6c8e..d60549e 100644 --- a/configure.ac +++ b/configure.ac @@ -46,8 +46,8 @@ else AC_SUBST(CURL_LIBS) fi -#AC_ARG_WITH(taglib, [ --without-taglib disable taglib support]) -AC_ARG_WITH(taglib, AC_HELP_STRING([--without-taglib], [disable taglib support])]) +#AC_ARG_WITH(taglib, [ --without-taglib disable taglib support) +AC_ARG_WITH(taglib, AC_HELP_STRING([--without-taglib], [disable taglib support])) if test "x$with_taglib" != "xno"; then PKG_CHECK_MODULES(TAGLIB, [taglib_c]) AC_SUBST(TAGLIB_CFLAGS) From 4f77ba7354c112593e0598b84a3c1482625db20b Mon Sep 17 00:00:00 2001 From: John Jetmore Date: Sat, 21 Jan 2023 11:32:59 -0500 Subject: [PATCH 2/6] add configure check for ronn missing by default on macos --- configure.ac | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/configure.ac b/configure.ac index d60549e..2ba9449 100644 --- a/configure.ac +++ b/configure.ac @@ -46,6 +46,11 @@ else AC_SUBST(CURL_LIBS) fi +AC_PATH_PROG(RONN, ronn, no) +if test "$RONN" = "no" ; then + AC_MSG_ERROR(Required tool ronn not found) +fi + #AC_ARG_WITH(taglib, [ --without-taglib disable taglib support) AC_ARG_WITH(taglib, AC_HELP_STRING([--without-taglib], [disable taglib support])) if test "x$with_taglib" != "xno"; then From 985145a45bf7e5a1294870b90c310eebe7ba9fc0 Mon Sep 17 00:00:00 2001 From: John Jetmore Date: Sat, 21 Jan 2023 17:42:45 -0500 Subject: [PATCH 3/6] create options_info type - simplify the interface to channel_update() to make adding new options easier - change as many Options variables as possible from global to main() --- src/castget.c | 52 +++++++++++++++++++++++++++++++++------------------ src/channel.c | 34 ++++++++++++++++++++------------- src/channel.h | 17 ++++++++++++++--- 3 files changed, 69 insertions(+), 34 deletions(-) diff --git a/src/castget.c b/src/castget.c index 2498bc2..3b416aa 100644 --- a/src/castget.c +++ b/src/castget.c @@ -53,16 +53,8 @@ static int playlist_add(const gchar *playlist_file, const gchar *media_file); static gboolean verbose = FALSE; static gboolean quiet = FALSE; -static gboolean first_only = FALSE; -static gboolean resume = FALSE; -static gboolean debug = FALSE; -static gboolean show_progress_bar = FALSE; -static gboolean show_version = FALSE; static gboolean new_only = FALSE; -static gboolean list = FALSE; -static gboolean catchup = FALSE; -static gchar *rcfile = NULL; -static gchar *filter_regex = NULL; +static option_info *opts = NULL; int main(int argc, char **argv) { @@ -76,6 +68,15 @@ int main(int argc, char **argv) enclosure_filter *filter = NULL; GError *error = NULL; GOptionContext *context; + static gboolean first_only = FALSE; + static gboolean resume = FALSE; + static gboolean debug = FALSE; + static gboolean show_progress_bar = FALSE; + static gchar *rcfile = NULL; + static gchar *filter_regex = NULL; + static gboolean list = FALSE; + static gboolean catchup = FALSE; + static gboolean show_version = FALSE; static GOptionEntry options[] = { { "catchup", 'c', 0, G_OPTION_ARG_NONE, &catchup, @@ -136,14 +137,30 @@ int main(int argc, char **argv) exit(0); } - if (catchup) + opts = option_info_new(); + opts->no_download = 0; + opts->no_mark_read = 0; + opts->first_only = first_only; + opts->resume = resume; + opts->debug = debug; + opts->show_progress_bar = show_progress_bar; + + if (catchup) { op = OP_CATCHUP; + opts->no_download = 1; + opts->no_mark_read = 0; + } - if (list) + if (list) { op = OP_LIST; + opts->no_download = 1; + opts->no_mark_read = 1; + } + opts->filter = NULL; if (filter_regex) { filter = enclosure_filter_new(filter_regex, FALSE); + opts->filter = filter; g_free(filter_regex); } @@ -206,6 +223,8 @@ int main(int argc, char **argv) if (filter) enclosure_filter_free(filter); + option_info_free(opts); + g_free(rcfile); if (kf) @@ -394,7 +413,7 @@ static int _process_channel(const gchar *channel_directory, GKeyFile *kf, c = channel_new(channel_configuration->url, channel_file, channel_configuration->spool_directory, - channel_configuration->filename_pattern, resume); + channel_configuration->filename_pattern, opts->resume); g_free(channel_file); if (!c) { @@ -415,18 +434,15 @@ static int _process_channel(const gchar *channel_directory, GKeyFile *kf, switch (op) { case OP_UPDATE: - channel_update(c, channel_configuration, update_callback, 0, 0, first_only, - resume, filter, debug, show_progress_bar); + channel_update(c, channel_configuration, update_callback, opts); break; case OP_CATCHUP: - channel_update(c, channel_configuration, catchup_callback, 1, 0, first_only, - 0, filter, debug, show_progress_bar); + channel_update(c, channel_configuration, catchup_callback, opts); break; case OP_LIST: - channel_update(c, channel_configuration, list_callback, 1, 1, first_only, 0, - filter, debug, show_progress_bar); + channel_update(c, channel_configuration, list_callback, opts); break; } diff --git a/src/channel.c b/src/channel.c index 09b9335..bc3ccbb 100644 --- a/src/channel.c +++ b/src/channel.c @@ -293,15 +293,13 @@ static int _do_catchup(channel *c, channel_info *channel_info, rss_item *item, } int channel_update(channel *c, void *user_data, channel_callback cb, - int no_download, int no_mark_read, int first_only, - int resume, enclosure_filter *filter, int debug, - int show_progress_bar) + option_info *opts) { int i, download_failed; rss_file *f; /* Retrieve the RSS file. */ - f = _get_rss(c, user_data, cb, debug); + f = _get_rss(c, user_data, cb, opts->debug); if (!f) return 1; @@ -316,37 +314,37 @@ int channel_update(channel *c, void *user_data, channel_callback cb, item = f->items[i]; - if (!filter || _enclosure_pattern_match(filter, item->enclosure)) { - if (no_download) + if (!opts->filter || _enclosure_pattern_match(opts->filter, item->enclosure)) { + if (opts->no_download) download_failed = _do_catchup(c, &(f->channel_info), item, user_data, cb); else download_failed = - _do_download(c, &(f->channel_info), item, user_data, cb, resume, - debug, show_progress_bar); + _do_download(c, &(f->channel_info), item, user_data, cb, opts->resume, + opts->debug, opts->show_progress_bar); if (download_failed) break; - if (!no_mark_read) { + if (!opts->no_mark_read) { /* Mark enclosure as downloaded and immediately save channel file to ensure that it reflects the change. */ g_hash_table_insert(c->downloaded_enclosures, f->items[i]->enclosure->url, (gpointer)get_rfc822_time()); - _cast_channel_save(c, debug); + _cast_channel_save(c, opts->debug); } /* If we have been instructed to deal only with the first available enclosure, it is time to break out of the loop. */ - if (first_only) + if (opts->first_only) break; } } } - if (!no_mark_read) { + if (!opts->no_mark_read) { /* Update the RSS last fetched time and save the channel file again. */ if (c->rss_last_fetched) @@ -354,7 +352,7 @@ int channel_update(channel *c, void *user_data, channel_callback cb, c->rss_last_fetched = g_strdup(f->fetched_time); - _cast_channel_save(c, debug); + _cast_channel_save(c, opts->debug); } rss_close(f); @@ -398,6 +396,16 @@ static gboolean _enclosure_pattern_match(enclosure_filter *filter, return match; } +option_info *option_info_new() +{ + option_info *opts = g_try_malloc0(sizeof(struct _option_info)); + return opts; +} + +void option_info_free(option_info *opts) { + g_free(opts); +} + enclosure_filter *enclosure_filter_new(const gchar *pattern, gboolean caseless) { enclosure_filter *e = g_malloc(sizeof(struct _enclosure_filter)); diff --git a/src/channel.h b/src/channel.h index 786577a..a15a5ad 100644 --- a/src/channel.h +++ b/src/channel.h @@ -56,6 +56,16 @@ typedef struct _enclosure_filter { gboolean caseless; } enclosure_filter; +typedef struct _option_info { + int no_download; + int no_mark_read; + int first_only; + int resume; + enclosure_filter *filter; + int debug; + int show_progress_bar; +} option_info; + typedef void (*channel_callback)(void *user_data, channel_action action, channel_info *channel_info, enclosure *enclosure, const char *filename); @@ -65,9 +75,10 @@ channel *channel_new(const char *url, const char *channel_file, int resume); void channel_free(channel *c); int channel_update(channel *c, void *user_data, channel_callback cb, - int no_download, int no_mark_read, int first_only, - int resume, enclosure_filter *filter, int debug, - int progress_bar); + option_info *opt); + +option_info *option_info_new(); +void option_info_free(option_info *e); enclosure_filter *enclosure_filter_new(const gchar *pattern, gboolean caseless); void enclosure_filter_free(enclosure_filter *e); From 22231c11bcbb23136cf3a3df80d659087f08cb69 Mon Sep 17 00:00:00 2001 From: John Jetmore Date: Sat, 21 Jan 2023 19:06:39 -0500 Subject: [PATCH 4/6] implement --reverse option fixes #41 --- castget.1.ronn | 3 +++ src/castget.c | 4 ++++ src/channel.c | 45 ++++++++++++++++++++++++++++++++++++++++++++- src/channel.h | 9 +++++++++ 4 files changed, 60 insertions(+), 1 deletion(-) diff --git a/castget.1.ronn b/castget.1.ronn index 8d8d07e..e8a3d06 100644 --- a/castget.1.ronn +++ b/castget.1.ronn @@ -52,6 +52,9 @@ options as arguments. ### Global options + * `-R`, `--reverse`: + Reverse the order in which items in feeds are processed. + * `-r`, `--resume`: Resume aborted downloads. Make sure not to use this option if the RSS feed uses the same filename for multiple enclosures as this will corrupt existing downloads. diff --git a/src/castget.c b/src/castget.c index 3b416aa..987ed7c 100644 --- a/src/castget.c +++ b/src/castget.c @@ -71,6 +71,7 @@ int main(int argc, char **argv) static gboolean first_only = FALSE; static gboolean resume = FALSE; static gboolean debug = FALSE; + static gboolean reverse = FALSE; static gboolean show_progress_bar = FALSE; static gchar *rcfile = NULL; static gchar *filter_regex = NULL; @@ -103,6 +104,8 @@ int main(int argc, char **argv) { "quiet", 'q', 0, G_OPTION_ARG_NONE, &quiet, "only print error messages" }, { "first-only", '1', 0, G_OPTION_ARG_NONE, &first_only, "only process the most recent item from each channel" }, + { "reverse", 'R', 0, G_OPTION_ARG_NONE, &reverse, + "process the channel in reverse order" }, { "filter", 'f', 0, G_OPTION_ARG_STRING, &filter_regex, "only process items whose enclosure names match a regular expression" }, @@ -143,6 +146,7 @@ int main(int argc, char **argv) opts->first_only = first_only; opts->resume = resume; opts->debug = debug; + opts->reverse = reverse; opts->show_progress_bar = show_progress_bar; if (catchup) { diff --git a/src/channel.c b/src/channel.c index bc3ccbb..ff568b4 100644 --- a/src/channel.c +++ b/src/channel.c @@ -34,6 +34,9 @@ #include #include +void _initialize_index(channel_index *index, int num_items, int reverse); +int _next_index(channel_index *index); + static int _enclosure_pattern_match(enclosure_filter *filter, const enclosure *enclosure); @@ -304,8 +307,12 @@ int channel_update(channel *c, void *user_data, channel_callback cb, if (!f) return 1; + channel_index index; + _initialize_index(&index, f->num_items, opts->reverse); + /* Check enclosures in RSS file. */ - for (i = 0; i < f->num_items; i++) + while (_next_index(&index)) { + i = index.current; if (f->items[i]->enclosure) { if (!g_hash_table_lookup_extended(c->downloaded_enclosures, f->items[i]->enclosure->url, NULL, @@ -343,6 +350,7 @@ int channel_update(channel *c, void *user_data, channel_callback cb, } } } + } if (!opts->no_mark_read) { /* Update the RSS last fetched time and save the channel file again. */ @@ -360,6 +368,41 @@ int channel_update(channel *c, void *user_data, channel_callback cb, return 0; } +void _initialize_index(channel_index *index, int num_items, int reverse) { + index->ended = 0; + index->reverse = reverse; + if (reverse) { + index->start = num_items -1; + index->stop = 0; + index->current = num_items; + } else { + index->start = 0; + index->stop = num_items - 1; + index->current = -1; + } +} + +int _next_index(channel_index *index) { + if (index->ended) + return 0; + + if (index->reverse) { + index->current--; + if (index->current < index->stop) { + index->current = -1; + index->ended = 1; + } + } else { + index->current++; + if (index->current > index->stop) { + index->current = -1; + index->ended = 1; + } + } + + return index->ended ? 0 : 1; +} + /* Match the (file) name of an enclosure against a regexp. Letters in the pattern match both upper and lower case letters if 'caseless' is TRUE. Returns TRUE if the pattern matches, FALSE diff --git a/src/channel.h b/src/channel.h index a15a5ad..8368efb 100644 --- a/src/channel.h +++ b/src/channel.h @@ -60,12 +60,21 @@ typedef struct _option_info { int no_download; int no_mark_read; int first_only; + int reverse; int resume; enclosure_filter *filter; int debug; int show_progress_bar; } option_info; +typedef struct _channel_index { + int start; + int stop; + int reverse; + int current; + int ended; +} channel_index; + typedef void (*channel_callback)(void *user_data, channel_action action, channel_info *channel_info, enclosure *enclosure, const char *filename); From c8c212c9ab5c751780e4a1f46b5d180526ea4c89 Mon Sep 17 00:00:00 2001 From: John Jetmore Date: Sat, 21 Jan 2023 19:36:38 -0500 Subject: [PATCH 5/6] implement --stop-after=ARG option preserves --first-only, but reimplements it in terms of --stop-after=1 This satisfies the underlying desire in #34 but via a channel-agnostic command line option instead of the suggested configuration option --- castget.1.ronn | 5 ++++- src/castget.c | 16 +++++++++++++++- src/channel.c | 5 +++-- src/channel.h | 2 +- 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/castget.1.ronn b/castget.1.ronn index e8a3d06..0f114e3 100644 --- a/castget.1.ronn +++ b/castget.1.ronn @@ -44,8 +44,11 @@ options as arguments. configuration file will not be considered as 'new' if it is added to the configuration again at a later time. + * `-s` , `--stop-after`=: + Stop after items are processed. This is implemented per-channel. + * `-1`, `--first-only`: - Restrict operation to the most recent item in each channel only. + Restrict operation to the most recent item in each channel only. Equivalent to --stop-after=1. * `-f` , `--filter`=: Restrict operation to enclosures whose download URLs match the regular expression `pattern`. Note that this will override any regular expression filters given in the configuration file. diff --git a/src/castget.c b/src/castget.c index 987ed7c..909c277 100644 --- a/src/castget.c +++ b/src/castget.c @@ -69,6 +69,7 @@ int main(int argc, char **argv) GError *error = NULL; GOptionContext *context; static gboolean first_only = FALSE; + static gint stop_after_count = 0; static gboolean resume = FALSE; static gboolean debug = FALSE; static gboolean reverse = FALSE; @@ -102,6 +103,8 @@ int main(int argc, char **argv) { "new-only", 'n', 0, G_OPTION_ARG_NONE, &new_only, "only process new channels" }, { "quiet", 'q', 0, G_OPTION_ARG_NONE, &quiet, "only print error messages" }, + { "stop-after", 's', 0, G_OPTION_ARG_INT, &stop_after_count, + "stop after processing ARGUMENT items from each channel" }, { "first-only", '1', 0, G_OPTION_ARG_NONE, &first_only, "only process the most recent item from each channel" }, { "reverse", 'R', 0, G_OPTION_ARG_NONE, &reverse, @@ -134,6 +137,17 @@ int main(int argc, char **argv) exit(1); } + if (stop_after_count < 0) { + g_print("--stop-after must be greater than 0\n"); + exit(1); + } + if (first_only && stop_after_count) { + g_print("--stop-after and --first-only are incompatible\n"); + exit(1); + } + if (first_only) + stop_after_count = 1; + /* Decide on the action to take */ if (show_version) { version(); @@ -143,7 +157,7 @@ int main(int argc, char **argv) opts = option_info_new(); opts->no_download = 0; opts->no_mark_read = 0; - opts->first_only = first_only; + opts->stop_after_count = stop_after_count; opts->resume = resume; opts->debug = debug; opts->reverse = reverse; diff --git a/src/channel.c b/src/channel.c index ff568b4..9357a53 100644 --- a/src/channel.c +++ b/src/channel.c @@ -298,7 +298,7 @@ static int _do_catchup(channel *c, channel_info *channel_info, rss_item *item, int channel_update(channel *c, void *user_data, channel_callback cb, option_info *opts) { - int i, download_failed; + int i, download_failed, eligible_items_seen = 0; rss_file *f; /* Retrieve the RSS file. */ @@ -322,6 +322,7 @@ int channel_update(channel *c, void *user_data, channel_callback cb, item = f->items[i]; if (!opts->filter || _enclosure_pattern_match(opts->filter, item->enclosure)) { + eligible_items_seen++; if (opts->no_download) download_failed = _do_catchup(c, &(f->channel_info), item, user_data, cb); @@ -345,7 +346,7 @@ int channel_update(channel *c, void *user_data, channel_callback cb, /* If we have been instructed to deal only with the first available enclosure, it is time to break out of the loop. */ - if (opts->first_only) + if (opts->stop_after_count && eligible_items_seen >= opts->stop_after_count) break; } } diff --git a/src/channel.h b/src/channel.h index 8368efb..40b692f 100644 --- a/src/channel.h +++ b/src/channel.h @@ -59,7 +59,7 @@ typedef struct _enclosure_filter { typedef struct _option_info { int no_download; int no_mark_read; - int first_only; + int stop_after_count; int reverse; int resume; enclosure_filter *filter; From 5224f3f319008e7d39ed262f59b04b5c099ed577 Mon Sep 17 00:00:00 2001 From: John Jetmore Date: Sat, 21 Jan 2023 20:01:27 -0500 Subject: [PATCH 6/6] implement --count-disregards-eligibility and --first-only-disregard-eligibility with this change, `--first-only --count-disregards-eligibility` will now cease channel processing after it sees the first item in the channel, regardless of whether that item was actually eligible for processing --first-only-disregard-eligibility is just a convenience for `--first-only --count-disregards-eligibility` fixes #26 --- castget.1.ronn | 8 +++++++- src/castget.c | 20 ++++++++++++++++++++ src/channel.c | 16 +++++++++++----- src/channel.h | 2 ++ 4 files changed, 40 insertions(+), 6 deletions(-) diff --git a/castget.1.ronn b/castget.1.ronn index 0f114e3..2a7cac2 100644 --- a/castget.1.ronn +++ b/castget.1.ronn @@ -47,8 +47,14 @@ options as arguments. * `-s` , `--stop-after`=: Stop after items are processed. This is implemented per-channel. + * `-x`, `--count-disregards-eligibility`: + By default, `--stop-after` only counts items that castget considers eligible for processing (e.g. matches filters and has not been previously downloaded (or marked as downloaded with `--catchup`)). This option modifies `--stop-after` so that the count applies to all items seen, regardless of whether the item was eligible for processing. + * `-1`, `--first-only`: - Restrict operation to the most recent item in each channel only. Equivalent to --stop-after=1. + Restrict operation to the most recent item in each channel only. Equivalent to `--stop-after=1`. + + * `-o`, `--first-only-disregards-eligibility`: + Stop processing when one item has been seen regardless of item's eligibility for processing. Equivalent to `--stop-after=1 --count-disregards-eligibility`. * `-f` , `--filter`=: Restrict operation to enclosures whose download URLs match the regular expression `pattern`. Note that this will override any regular expression filters given in the configuration file. diff --git a/src/castget.c b/src/castget.c index 909c277..89ae78c 100644 --- a/src/castget.c +++ b/src/castget.c @@ -69,7 +69,9 @@ int main(int argc, char **argv) GError *error = NULL; GOptionContext *context; static gboolean first_only = FALSE; + static gboolean first_only_disregard_eligibility = FALSE; static gint stop_after_count = 0; + static gboolean count_disregards_eligibility = FALSE; static gboolean resume = FALSE; static gboolean debug = FALSE; static gboolean reverse = FALSE; @@ -105,8 +107,12 @@ int main(int argc, char **argv) { "quiet", 'q', 0, G_OPTION_ARG_NONE, &quiet, "only print error messages" }, { "stop-after", 's', 0, G_OPTION_ARG_INT, &stop_after_count, "stop after processing ARGUMENT items from each channel" }, + { "count-disregards-eligibility", 'x', 0, G_OPTION_ARG_NONE, &count_disregards_eligibility, + "when processing --stop-after, count considers all items, not just those eligible" }, { "first-only", '1', 0, G_OPTION_ARG_NONE, &first_only, "only process the most recent item from each channel" }, + { "first-only-disregard-eligibility", 'c', 0, G_OPTION_ARG_NONE, &first_only_disregard_eligibility, + "only process the most recent item from each channel" }, { "reverse", 'R', 0, G_OPTION_ARG_NONE, &reverse, "process the channel in reverse order" }, { "filter", 'f', 0, G_OPTION_ARG_STRING, &filter_regex, @@ -145,8 +151,21 @@ int main(int argc, char **argv) g_print("--stop-after and --first-only are incompatible\n"); exit(1); } + if (first_only_disregard_eligibility && stop_after_count) { + g_print("--stop-after and --first-only-disregard-eligibility are incompatible\n"); + exit(1); + } + if (first_only_disregard_eligibility && first_only) { + g_print("--first-only and --first-only-disregard-eligibility are incompatible\n"); + exit(1); + } + if (first_only) stop_after_count = 1; + if (first_only_disregard_eligibility) { + stop_after_count = 1; + count_disregards_eligibility = TRUE; + } /* Decide on the action to take */ if (show_version) { @@ -158,6 +177,7 @@ int main(int argc, char **argv) opts->no_download = 0; opts->no_mark_read = 0; opts->stop_after_count = stop_after_count; + opts->count_disregards_eligibility = count_disregards_eligibility; opts->resume = resume; opts->debug = debug; opts->reverse = reverse; diff --git a/src/channel.c b/src/channel.c index 9357a53..1b1cfc4 100644 --- a/src/channel.c +++ b/src/channel.c @@ -343,13 +343,17 @@ int channel_update(channel *c, void *user_data, channel_callback cb, _cast_channel_save(c, opts->debug); } - - /* If we have been instructed to deal only with the first - available enclosure, it is time to break out of the loop. */ - if (opts->stop_after_count && eligible_items_seen >= opts->stop_after_count) - break; } } + + /* If we have been instructed to only process a certain number of items + and we have seen that number, exit the loop */ + if (opts->stop_after_count) { + int check_index = (opts->count_disregards_eligibility) ? index.iteration : eligible_items_seen; + if (check_index >= opts->stop_after_count) { + break; +} + } } } @@ -372,6 +376,7 @@ int channel_update(channel *c, void *user_data, channel_callback cb, void _initialize_index(channel_index *index, int num_items, int reverse) { index->ended = 0; index->reverse = reverse; + index->iteration = 0; if (reverse) { index->start = num_items -1; index->stop = 0; @@ -387,6 +392,7 @@ int _next_index(channel_index *index) { if (index->ended) return 0; + index->iteration++; if (index->reverse) { index->current--; if (index->current < index->stop) { diff --git a/src/channel.h b/src/channel.h index 40b692f..ad61928 100644 --- a/src/channel.h +++ b/src/channel.h @@ -60,6 +60,7 @@ typedef struct _option_info { int no_download; int no_mark_read; int stop_after_count; + int count_disregards_eligibility; int reverse; int resume; enclosure_filter *filter; @@ -73,6 +74,7 @@ typedef struct _channel_index { int reverse; int current; int ended; + int iteration; } channel_index; typedef void (*channel_callback)(void *user_data, channel_action action,