diff --git a/castget.1.ronn b/castget.1.ronn index 8d8d07e..2a7cac2 100644 --- a/castget.1.ronn +++ b/castget.1.ronn @@ -44,14 +44,26 @@ 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. + + * `-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. + 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. ### 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/configure.ac b/configure.ac index 17f6c8e..2ba9449 100644 --- a/configure.ac +++ b/configure.ac @@ -46,8 +46,13 @@ 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_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 PKG_CHECK_MODULES(TAGLIB, [taglib_c]) AC_SUBST(TAGLIB_CFLAGS) diff --git a/src/castget.c b/src/castget.c index 2498bc2..89ae78c 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,19 @@ int main(int argc, char **argv) enclosure_filter *filter = NULL; 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; + 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, @@ -100,8 +105,16 @@ 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" }, + { "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, "only process items whose enclosure names match a regular expression" }, @@ -130,20 +143,62 @@ 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_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) { version(); exit(0); } - if (catchup) + opts = option_info_new(); + 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; + 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 +261,8 @@ int main(int argc, char **argv) if (filter) enclosure_filter_free(filter); + option_info_free(opts); + g_free(rcfile); if (kf) @@ -394,7 +451,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 +472,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..1b1cfc4 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); @@ -293,21 +296,23 @@ 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; + int i, download_failed, eligible_items_seen = 0; 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; + 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, @@ -316,37 +321,43 @@ 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)) { + eligible_items_seen++; + 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) - 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; +} + } } + } - 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 +365,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); @@ -362,6 +373,43 @@ 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; + index->iteration = 0; + 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; + + index->iteration++; + 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 @@ -398,6 +446,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..ad61928 100644 --- a/src/channel.h +++ b/src/channel.h @@ -56,6 +56,27 @@ typedef struct _enclosure_filter { gboolean caseless; } enclosure_filter; +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; + int debug; + int show_progress_bar; +} option_info; + +typedef struct _channel_index { + int start; + int stop; + int reverse; + int current; + int ended; + int iteration; +} channel_index; + typedef void (*channel_callback)(void *user_data, channel_action action, channel_info *channel_info, enclosure *enclosure, const char *filename); @@ -65,9 +86,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);