]> git.tuebingen.mpg.de Git - paraslash.git/commitdiff
Merge topic branch t/afs-select into master
authorAndre Noll <maan@tuebingen.mpg.de>
Sat, 21 Oct 2023 16:56:48 +0000 (18:56 +0200)
committerAndre Noll <maan@tuebingen.mpg.de>
Sat, 21 Oct 2023 16:58:22 +0000 (18:58 +0200)
A single patch which silences the select command. The merge conflicted
in afs.c, but that was trivial to resolve.

* refs/heads/t/afs-select:
  server: Implement select -verbose.

1  2 
NEWS.md
afs.c
m4/lls/server_cmd.suite.m4
mood.c

diff --combined NEWS.md
index 22816b1911a7613a233481df0ff4ac7e07bb6a81,598db71fae93734158509fa5f76853ceb09ccb7b..aa515e5af091eb23f266549a0cd33e974f993972
+++ b/NEWS.md
@@@ -1,24 -1,6 +1,27 @@@
  NEWS
  ====
  
 +----------------------------------------------
 +0.7.3 (to be announced) "weighted correctness"
 +----------------------------------------------
 +
 +- Old style PEM keys are now deprecated. They still work but their
 +  use results in a run-time warning. The removal of PEM key support is
 +  scheduled for paraslash-0.8.0.
 +- Version 1.0 of the openssl library has been deprecated. A warning
 +  is printed at compile-time on systems which have this outdated version
 +  because it will no longer be supported once paraslash-0.8.0 comes out.
 +- A spring cleanup for the senescent code in fd.c.
 +- The --admissible option of the ls command now takes an optional
 +  argument. When invoked like --admissible=m/foo, only files which are
 +  admissible with respect to mood foo are listed.
++- The select server command is now quiet by default, The new --verbose
++  option can be used to show information about the newly loaded mood
++  or playlist.
 +
 +Downloads:
 +[tarball](./releases/paraslash-git.tar.xz)
 +
  -------------------------------------
  0.7.2 (2023-03-08) "optical friction"
  -------------------------------------
diff --combined afs.c
index 3083084c25ac793cfd3a5cad1f93c6e78f0fa413,cb3ead27e55d43df0d7230f2ca404499439348ec..445d5871097b79cdcd14170c2a1f59998354dc98
--- 1/afs.c
--- 2/afs.c
+++ b/afs.c
@@@ -437,18 -437,18 +437,18 @@@ static int activate_mood_or_playlist(co
        int ret;
        char *msg;
  
 -      if (!arg) {
 -              ret = mood_load(NULL, &msg);
 +      if (!arg) { /* load dummy mood */
 +              ret = mood_load(NULL, NULL, &msg);
                mode = PLAY_MODE_MOOD;
        } else if (!strncmp(arg, "p/", 2)) {
 -              ret = playlist_load(arg + 2, &msg);
 +              ret = playlist_load(arg + 2, NULL, &msg);
                mode = PLAY_MODE_PLAYLIST;
        } else if (!strncmp(arg, "m/", 2)) {
 -              ret = mood_load(arg + 2, &msg);
 +              ret = mood_load(arg + 2, NULL, &msg);
                mode = PLAY_MODE_MOOD;
        } else {
                ret = -ERRNO_TO_PARA_ERROR(EINVAL);
 -              msg = make_message("%s: parse error", arg);
 +              msg = make_message("%s: parse error\n", arg);
        }
        if (pb)
                para_printf(pb, "%s", msg);
@@@ -580,6 -580,17 +580,6 @@@ static void get_database_dir(void
        PARA_INFO_LOG("afs_database dir %s\n", database_dir);
  }
  
 -static int make_database_dir(void)
 -{
 -      int ret;
 -
 -      get_database_dir();
 -      ret = para_mkdir(database_dir, 0777);
 -      if (ret >= 0 || ret == -ERRNO_TO_PARA_ERROR(EEXIST))
 -              return 1;
 -      return ret;
 -}
 -
  static int open_afs_tables(void)
  {
        int i, ret;
@@@ -951,8 -962,7 +951,8 @@@ __noreturn void afs_init(int socket_fd
        }
        ret = schedule(&s);
        sched_shutdown(&s);
 -      mood_unload();
 +      mood_unload(NULL);
 +      playlist_unload(NULL);
  out_close:
        close_afs_tables();
  out:
@@@ -970,29 -980,32 +970,32 @@@ static int com_select_callback(struct a
        const struct lls_command *cmd = SERVER_CMD_CMD_PTR(SELECT);
        const char *arg;
        int ret;
+       struct para_buffer *pbout;
  
        ret = lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr);
        assert(ret >= 0);
        arg = lls_input(0, aca->lpr);
+       pbout = SERVER_CMD_OPT_GIVEN(SELECT, VERBOSE, aca->lpr)?
+               &aca->pbout : NULL;
        score_clear();
        if (current_play_mode == PLAY_MODE_MOOD)
 -              mood_unload();
 +              mood_unload(NULL);
        else
 -              playlist_unload();
 +              playlist_unload(NULL);
-       ret = activate_mood_or_playlist(arg, &aca->pbout);
+       ret = activate_mood_or_playlist(arg, pbout);
        if (ret >= 0)
                goto free_lpr;
        /* ignore subsequent errors (but log them) */
        if (current_mop && strcmp(current_mop, arg) != 0) {
                int ret2;
                afs_error(aca, "switching back to %s\n", current_mop);
-               ret2 = activate_mood_or_playlist(current_mop, &aca->pbout);
+               ret2 = activate_mood_or_playlist(current_mop, pbout);
                if (ret2 >= 0)
                        goto free_lpr;
                afs_error(aca, "could not reactivate %s: %s\n", current_mop,
                        para_strerror(-ret2));
        }
-       activate_mood_or_playlist(NULL, &aca->pbout);
+       activate_mood_or_playlist(NULL, pbout);
  free_lpr:
        lls_free_parse_result(aca->lpr, cmd);
        return ret;
@@@ -1051,8 -1064,7 +1054,8 @@@ static int com_init(struct command_cont
                .size = sizeof(table_mask)};
        unsigned num_inputs = lls_num_inputs(lpr);
  
 -      ret = make_database_dir();
 +      get_database_dir();
 +      ret = para_mkdir(database_dir);
        if (ret < 0)
                return ret;
        if (num_inputs > 0) {
index 1cec68164a13d46fdd96bfcb50c2b0ea160a0585,8784fe7816f60e25503e7b19f4107714dca56587..5b5c59ad58ad4ca07edefa3bd01311155ca1e5d5
@@@ -233,19 -233,9 +233,19 @@@ m4_include(`com_ll.m4'
        [option admissible]
                short_opt = a
                summary = list only admissible files
 +              arg_type = string
 +              arg_info = optional_arg
 +              typestr = specifier/name
 +              default_val = .
                [help]
 -                      List only files which are admissible with respect to the current mood
 -                      or playlist.
 +                      If the optional argument is supplied, it must be of the form "p/foo"
 +                      or "m/bar" (which refer to the playlist named "foo" and the mood named
 +                      "bar", respectively). The command then restricts its output to the set
 +                      of files which are admissible with respect to the thusly identified
 +                      mood or playlist.
 +
 +                      If no argument is given, or if the argument is the special value ".",
 +                      the current mood or playlist is assumed.
                [/help]
        [option reverse]
                short_opt = r
  
                activates the mood named 'foo'.
        [/description]
+       [option verbose]
+               short_opt = v
+               summary = print information about the loaded mood or playlist
  
  [subcommand sender]
        purpose = control paraslash senders
diff --combined mood.c
index b4d50c88e73533c69e7473c072e3aaef1c196d99,7c0d2b4fba9feb13a9287d55281e1451df65e276..1e15ef0e081480381fcbc4fdad2179848f429c1f
--- 1/mood.c
--- 2/mood.c
+++ b/mood.c
@@@ -59,8 -59,6 +59,8 @@@ struct mood_instance 
        struct mp_context *parser_context;
        /** To compute the score. */
        struct afs_statistics stats;
 +      /** NULL means to operate on the global score table. */
 +      struct osl_table *score_table;
  };
  
  /*
@@@ -134,8 -132,6 +134,8 @@@ static void destroy_mood(struct mood_in
        if (!m)
                return;
        mp_shutdown(m->parser_context);
 +      if (m->score_table)
 +              score_close(m->score_table);
        free(m->name);
        free(m);
  }
@@@ -441,7 -437,7 +441,7 @@@ static void update_afs_statistics(struc
  }
  
  static int add_to_score_table(const struct osl_row *aft_row,
 -              const struct afs_statistics *stats)
 +              struct mood_instance *m)
  {
        long score;
        struct afs_info afsi;
  
        if (ret < 0)
                return ret;
 -      score = compute_score(&afsi, stats);
 -      return score_add(aft_row, score);
 +      score = compute_score(&afsi, &m->stats);
 +      return score_add(aft_row, score, m->score_table);
  }
  
  static int delete_from_statistics_and_score_table(const struct osl_row *aft_row)
@@@ -508,7 -504,7 +508,7 @@@ static int mood_update_audio_file(cons
                ret = add_afs_statistics(aft_row, &current_mood->stats);
                if (ret < 0)
                        return ret;
 -              return add_to_score_table(aft_row, &current_mood->stats);
 +              return add_to_score_table(aft_row, current_mood);
        }
        /* update score */
        ret = get_afsi_of_row(aft_row, &afsi);
@@@ -533,34 -529,27 +533,37 @@@ static char *get_statistics(struct mood
        unsigned n = m->stats.num;
        int mean_days, sigma_days;
  
 +      if (n == 0)
 +              return make_message("no admissible files\n");
        mean_days = (sse - m->stats.last_played_sum / n) / 3600 / 24;
        sigma_days = int_sqrt(m->stats.last_played_qd / n) / 3600 / 24;
        return make_message(
                "loaded mood %s (%u files)\n"
                "last_played mean/sigma: %d/%d days\n"
                "num_played mean/sigma: %" PRId64 "/%" PRIu64 "\n"
+               "correction factor ratio: %.2lf\n"
        ,
                m->name? m->name : "(dummy)",
                n,
                mean_days, sigma_days,
                m->stats.num_played_sum / n,
-                       int_sqrt(m->stats.num_played_qd / n)
+               int_sqrt(m->stats.num_played_qd / n),
+               86400.0 * m->stats.last_played_correction /
+                       m->stats.num_played_correction
        );
  }
  
 -/** Free all resources of the current mood, if any. */
 -void mood_unload(void)
 +/**
 + * Free all resources of a mood instance.
 + *
 + * \param m As obtained by \ref mood_load(). If NULL, unload the current mood.
 + *
 + * It's OK to call this with m == NULL even if no current mood is loaded.
 + */
 +void mood_unload(struct mood_instance *m)
  {
 +      if (m)
 +              return destroy_mood(m);
        destroy_mood(current_mood);
        current_mood = NULL;
  }
@@@ -582,42 -571,23 +585,42 @@@ static void compute_correction_factors(
  }
  
  /**
 - * Change the current mood.
 + * Populate a score table with admissible files for the given mood.
 + *
 + * This consults the mood table to initialize the mood parser with the mood
 + * expression stored in the blob object which corresponds to the given name. A
 + * score table is allocated and populated with references to those entries of
 + * the audio file table which evaluate as admissible with respect to the mood
 + * expression. For each audio file a score value is computed and stored along
 + * with the file reference.
   *
   * \param mood_name The name of the mood to load.
 + * \param result Opaque, refers to the mood parser and the score table.
   * \param msg Error message or mood info is returned here.
   *
 - * If \a mood_name is \a NULL, load the dummy mood that accepts every audio file
 - * and uses a scoring method based only on the \a last_played information.
 + * If the mood name is NULL, the dummy mood is loaded. This mood regards every
 + * audio file as admissible.
 + *
 + * A NULL result pointer instructs the function to operate on the current mood.
 + * That is, on the mood instance which is used by the server to select the next
 + * audio file for streaming. In this mode of operation, the mood which was
 + * active before the call, if any, is unloaded on success.
 + *
 + * If result is not NULL, the current mood is unaffected and *result points to
 + * an initialized mood instance on success. The caller can pass this reference
 + * to \ref mood_loop() to iterate over the admissible files, and should call
 + * \ref mood_unload() to free the mood instance afterwards.
   *
   * If the message pointer is not NULL, a suitable message is returned there in
   * all cases. The caller must free this string.
   *
 - * \return The number of admissible files on success, negative on errors. It is
 + * \return The number of admissible files on success, negative on errors. On
 + * errors, the current mood remains unaffected even if result is NULL. It is
   * not considered an error if no files are admissible.
   *
 - * \sa struct \ref afs_info::last_played, \ref mp_eval_row().
 + * \sa \ref mp_eval_row().
   */
 -int mood_load(const char *mood_name, char **msg)
 +int mood_load(const char *mood_name, struct mood_instance **result, char **msg)
  {
        int i, ret;
        struct admissible_array aa = {.size = 0};
        }
        clock_get_realtime(&rnow);
        compute_correction_factors(rnow.tv_sec, &aa.m->stats);
 -      if (aa.m->stats.num == 0) {
 -              if (msg)
 -                      *msg = make_message("no admissible files\n");
 -              ret = 0;
 -              goto out;
 -      }
 +      if (result)
 +              score_open(&aa.m->score_table);
        for (i = 0; i < aa.m->stats.num; i++) {
 -              ret = add_to_score_table(aa.array[i], &aa.m->stats);
 +              ret = add_to_score_table(aa.array[i], aa.m);
                if (ret < 0) {
                        if (msg)
                                *msg = make_message(
        if (msg)
                *msg = get_statistics(aa.m, rnow.tv_sec);
        ret = aa.m->stats.num;
 -      mood_unload();
 -      current_mood = aa.m;
 +      if (result)
 +              *result = aa.m;
 +      else {
 +              mood_unload(NULL);
 +              current_mood = aa.m;
 +      }
        ret = 1;
  out:
        free(aa.array);
        return ret;
  }
  
 +/**
 + * Iterate over the admissible files of a mood instance.
 + *
 + * This wrapper around \ref score_loop() is the mood counterpart of \ref
 + * playlist_loop().
 + *
 + * \param m Determines the score table to iterate. Must not be NULL.
 + * \param func See \ref score_loop().
 + * \param data See \ref score_loop().
 + *
 + * \return See \ref score_loop(), \ref playlist_loop().
 + */
 +int mood_loop(struct mood_instance *m, osl_rbtree_loop_func *func, void *data)
 +{
 +      return score_loop(func, m->score_table, data);
 +}
 +
  /*
   * Empty the score table and start over.
   *
 - * This function is called on events which render the current list of
 - * admissible files useless, for example if an attribute is removed from the
 - * attribute table.
 + * This function is called on events which render the current set of admissible
 + * files invalid, for example if an attribute is removed from the attribute
 + * table.
   */
  static int reload_current_mood(void)
  {
                current_mood->name : "(dummy)");
        if (current_mood->name)
                mood_name = para_strdup(current_mood->name);
 -      mood_unload();
 -      ret = mood_load(mood_name, NULL);
 +      mood_unload(NULL);
 +      ret = mood_load(mood_name, NULL, NULL);
        free(mood_name);
        return ret;
  }