]> git.tuebingen.mpg.de Git - paraslash.git/commitdiff
server: Deprecate setatt in favor of touch.
authorAndre Noll <maan@tuebingen.mpg.de>
Sun, 16 Mar 2025 23:15:40 +0000 (00:15 +0100)
committerAndre Noll <maan@tuebingen.mpg.de>
Mon, 19 May 2025 18:09:06 +0000 (20:09 +0200)
This adds --set-attribute and --unset-attribute to the touch subcommand,
re-implementing the features of the setatt command with a saner syntax.

We augment the change_atts_data structure using pre-computed values for
verbose and dry-run mode and pass a pointer to this structure rather than
the general callback arg pointer. The existing setatt subcommand neither
sets nor consults the two new booleans.

The touch completer of para_client is updated to complete the two new options,
executing the lsatt subcommand to get the attribute names. The manual and
the test suite also need minor adjustments.

aft.c
client.c
m4/lls/server_cmd.suite.m4
t/t0004-server.sh
web/manual.md

diff --git a/aft.c b/aft.c
index 132f511940c6af0961fd9ca59087150df98b4b26..36b5a490c7020823f3ee78a9179570b7df3fc00e 100644 (file)
--- a/aft.c
+++ b/aft.c
@@ -2036,12 +2036,18 @@ out:
 }
 EXPORT_SERVER_CMD_HANDLER(add);
 
+struct change_atts_data {
+       uint64_t add_mask, del_mask;
+       struct afs_callback_arg *aca;
+       bool verbose, dry_run;
+};
+
 static int touch_audio_file(__a_unused struct osl_table *table,
                struct osl_row *row, const char *name, void *data)
 {
-       struct afs_callback_arg *aca = data;
-       bool v_given = SERVER_CMD_OPT_GIVEN(TOUCH, VERBOSE, aca->lpr);
-       const struct lls_opt_result *r_n, *r_l, *r_i, *r_y, *r_a;
+       struct change_atts_data *cad = data;
+       struct afs_callback_arg *aca = cad->aca;
+       const struct lls_opt_result *r_n, *r_l, *r_i, *r_y, *r_a, *r_s, *r_u;
        int ret;
        struct osl_object obj;
        struct afs_info old_afsi, new_afsi;
@@ -2053,8 +2059,11 @@ static int touch_audio_file(__a_unused struct osl_table *table,
        r_i = SERVER_CMD_OPT_RESULT(TOUCH, IMAGE_ID, aca->lpr);
        r_y = SERVER_CMD_OPT_RESULT(TOUCH, LYRICS_ID, aca->lpr);
        r_a = SERVER_CMD_OPT_RESULT(TOUCH, AMP, aca->lpr);
+       r_s = SERVER_CMD_OPT_RESULT(TOUCH, SET_ATTRIBUTE, aca->lpr);
+       r_u = SERVER_CMD_OPT_RESULT(TOUCH, UNSET_ATTRIBUTE, aca->lpr);
        no_options = !lls_opt_given(r_n) && !lls_opt_given(r_l) && !lls_opt_given(r_i)
-               && !lls_opt_given(r_y) && !lls_opt_given(r_a);
+               && !lls_opt_given(r_y) && !lls_opt_given(r_a)
+               && !lls_opt_given(r_s) && !lls_opt_given(r_u);
 
        ret = get_afsi_object_of_row(row, &obj);
        if (ret < 0) {
@@ -2070,7 +2079,7 @@ static int touch_audio_file(__a_unused struct osl_table *table,
        if (no_options) {
                new_afsi.num_played++;
                new_afsi.last_played = time(NULL);
-               if (v_given)
+               if (cad->verbose)
                        para_printf(&aca->pbout, "%s: num_played = %u, "
                                "last_played = now()\n", name,
                                new_afsi.num_played);
@@ -2085,33 +2094,70 @@ static int touch_audio_file(__a_unused struct osl_table *table,
                        new_afsi.lyrics_id = lls_uint32_val(0, r_y);
                if (lls_opt_given(r_a))
                        new_afsi.amp = lls_uint32_val(0, r_a);
-               if (v_given)
+               new_afsi.attributes |= cad->add_mask;
+               new_afsi.attributes &= ~cad->del_mask;
+               if (cad->verbose)
                        para_printf(&aca->pbout, "touching %s\n", name);
        }
-       save_afsi(&new_afsi, &obj); /* in-place update */
+       if (!cad->dry_run)
+               save_afsi(&new_afsi, &obj); /* in-place update */
        aced.aft_row = row;
        aced.old_afsi = &old_afsi;
        return afs_event(AFSI_CHANGE, &aca->pbout, &aced);
 }
 
+/*
+ * Embed a change_atts_data structure into a pattern_match_data structure
+ * for the for_each_matching_row() iterator, with touch_audio_file() as
+ * the iterator callback for matching files.
+ */
 static int com_touch_callback(struct afs_callback_arg *aca)
 {
        const struct lls_command *cmd = SERVER_CMD_CMD_PTR(TOUCH);
        bool p_given;
-       const struct lls_opt_result *r_i, *r_y;
-       int ret;
+       const struct lls_opt_result *r_s, *r_u, *r_i, *r_y;
+       const uint64_t one = 1;
+       int ret, s_given, u_given;
+       struct change_atts_data cad = {.aca = aca};
        struct pattern_match_data pmd = {
                .table = audio_file_table,
                .loop_col_num = AFTCOL_HASH,
                .match_col_num = AFTCOL_PATH,
-               .data = aca,
+               .data = &cad,
                .action = touch_audio_file
        };
 
        ret = lls_deserialize_parse_result(aca->query.data, cmd, &aca->lpr);
        assert(ret >= 0);
        pmd.lpr = aca->lpr;
+       cad.dry_run = SERVER_CMD_OPT_GIVEN(TOUCH, DRY_RUN, aca->lpr);
+       cad.verbose = cad.dry_run || SERVER_CMD_OPT_GIVEN(TOUCH,
+               VERBOSE, aca->lpr);
 
+       r_s = SERVER_CMD_OPT_RESULT(TOUCH, SET_ATTRIBUTE, aca->lpr);
+       s_given = SERVER_CMD_OPT_GIVEN(TOUCH, SET_ATTRIBUTE, aca->lpr);
+       for (int i = 0; i < s_given; i++) {
+               unsigned char bitnum;
+               const char *arg = lls_string_val(i, r_s);
+               ret = get_attribute_bitnum_by_name(arg, &bitnum);
+               if (ret < 0) {
+                       afs_error(aca, "invalid attribute: %s\n", arg);
+                       goto out;
+               }
+               cad.add_mask |= (one << bitnum);
+       }
+       r_u = SERVER_CMD_OPT_RESULT(TOUCH, UNSET_ATTRIBUTE, aca->lpr);
+       u_given = SERVER_CMD_OPT_GIVEN(TOUCH, UNSET_ATTRIBUTE, aca->lpr);
+       for (int i = 0; i < u_given; i++) {
+               unsigned char bitnum;
+               const char *arg = lls_string_val(i, r_u);
+               ret = get_attribute_bitnum_by_name(arg, &bitnum);
+               if (ret < 0) {
+                       afs_error(aca, "invalid attribute: %s\n", arg);
+                       goto out;
+               }
+               cad.del_mask |= (one << bitnum);
+       }
        r_i = SERVER_CMD_OPT_RESULT(TOUCH, IMAGE_ID, aca->lpr);
        if (lls_opt_given(r_i)) {
                uint32_t id = lls_uint32_val(0, r_i);
@@ -2136,6 +2182,7 @@ static int com_touch_callback(struct afs_callback_arg *aca)
        ret = for_each_matching_row(&pmd);
        if (ret >= 0 && pmd.num_matches == 0)
                ret = -E_NO_MATCH;
+out:
        lls_free_parse_result(aca->lpr, cmd);
        return ret;
 }
@@ -2333,11 +2380,6 @@ static int com_cpsi(struct command_context *cc, struct lls_parse_result *lpr)
 }
 EXPORT_SERVER_CMD_HANDLER(cpsi);
 
-struct change_atts_data {
-       uint64_t add_mask, del_mask;
-       struct afs_callback_arg *aca;
-};
-
 static int change_atts(__a_unused struct osl_table *table,
                struct osl_row *row, __a_unused const char *name, void *data)
 {
@@ -2426,6 +2468,7 @@ out:
        return ret;
 }
 
+/* This function and its callback can be removed after 0.8.0 */
 static int com_setatt(struct command_context *cc, struct lls_parse_result *lpr)
 {
        const struct lls_command *cmd = SERVER_CMD_CMD_PTR(SETATT);
@@ -2436,6 +2479,10 @@ static int com_setatt(struct command_context *cc, struct lls_parse_result *lpr)
                send_errctx(cc, errctx);
                return ret;
        }
+       send_sb_va(&cc->scc, SBD_WARNING_LOG,
+               "Warning: 'setatt' is obsolete, "
+               "use 'touch --set-attribute' instead.\n"
+       );
        return send_lls_callback_request(com_setatt_callback, cmd, lpr, cc);
 }
 EXPORT_SERVER_CMD_HANDLER(setatt);
index 4ae60f23c45f34a8ec7c2132d423199612c78602..444af57aca7cdb96e2c9de05abc1385660eb51ea 100644 (file)
--- a/client.c
+++ b/client.c
@@ -159,24 +159,39 @@ out:
        return ret;
 }
 
-static int extract_matches_from_command(const char *word, char *cmd,
-               char ***matches)
+static int extract_matches_from_command_pfx(const char *word, char *cmd,
+               const char *pfx, char ***matches)
 {
-       char *buf, **sl;
-       int ret;
+       char *buf, **argv;
+       int argc, ret;
 
        ret = execute_client_command(cmd, &buf);
        if (ret < 0)
                return ret;
-       ret = create_argv(buf, "\n", &sl);
+       ret = create_argv(buf, "\n", &argv);
        free(buf);
        if (ret < 0)
                return ret;
-       ret = i9e_extract_completions(word, sl, matches);
-       free_argv(sl);
+       argc = ret;
+       if (pfx) {
+               char **nargv = arr_alloc(argc + 1, sizeof(char *));
+               for (unsigned n = 0; n < argc; n++)
+                       nargv[n] = make_message("%s%s", pfx, argv[n]);
+               nargv[argc] = NULL;
+               free_argv(argv);
+               argv = nargv;
+       }
+       ret = i9e_extract_completions(word, argv, matches);
+       free_argv(argv);
        return ret;
 }
 
+static int extract_matches_from_command(const char *word, char *cmd,
+               char ***matches)
+{
+       return extract_matches_from_command_pfx(word, cmd, NULL, matches);
+}
+
 static int complete_attributes(const char *word, char ***matches)
 {
        return extract_matches_from_command(word, "lsatt", matches);
@@ -477,12 +492,29 @@ static void touch_completer(struct i9e_completion_info *ci,
                struct i9e_completion_result *cr)
 {
        char * const opts[] = {LSG_SERVER_CMD_TOUCH_OPTS, NULL};
+       int num = i9e_cword_is_option_arg(opts, ci);
+       const char *opt;
 
-       if (i9e_cword_is_option_arg(opts, ci) >= 0)
+       if (num < 0) { /* no option arg, is it an option? */
+               if (ci->word[0] == '-')
+                       return i9e_complete_option(opts, ci, cr);
+               cr->filename_completion_desired = true;
                return;
-       if (ci->word[0] == '-')
-               return i9e_complete_option(opts, ci, cr);
-       cr->filename_completion_desired = true;
+       }
+       opt = opts[num];
+       /* only attribute option args can be completed */
+       if (strcmp(opt, "-s=") && strcmp(opt, "--set-attribute=") &&
+                       strcmp(opt, "-u=") && strcmp(opt, "--unset-attribute="))
+               return;
+
+       if (strncmp(opt, ci->word, strlen(opt))) {
+               /* complete on unprefixed attributes (-s att syntax) */
+               extract_matches_from_command(ci->word, "lsatt", &cr->matches);
+               return;
+       }
+       /* complete on prefixed attributes (-s=att syntax) */
+       extract_matches_from_command_pfx(ci->word, "lsatt", opt, &cr->matches);
+       return;
 }
 
 static void cpsi_completer(struct i9e_completion_info *ci,
index 6056541c1a73af8e9863bb9cfe8c79be6deea07e..9a447ad0ae7408f71b43893c9160baa935dd73d0 100644 (file)
@@ -478,17 +478,13 @@ m4_include(`com_ll.m4')
        [/description]
 
 [subcommand setatt]
-       purpose = set or unset attributes
+       purpose = obsolete way to set or unset attributes
        synopsis = attribute{+|-}... pattern...
        aux_info = AFS_READ | AFS_WRITE
        [description]
-               Set ('+') or unset ('-') the given attributes for all audio files
-               matching the given pattern. Example:
-
-                       setatt rock+ punk+ pop- '*foo.mp3'
-
-               sets the 'rock' and the 'punk' attribute and unsets the 'pop' attribute
-               of all files ending with 'foo.mp3'.
+               This subcommand is obsolete and should no longer be used. It has been
+               superseded by the --set-atribute and --unset-attribute options of
+               the touch subcommand.
        [/description]
 
 [subcommand si]
@@ -544,9 +540,10 @@ m4_include(`com_ll.m4')
                This command modifies the afs info structure of all rows of the audio
                file table whose path matches at least one of the given patters.
 
-               If at least one option is given which takes a number as its argument,
-               only those fields of the afs info structure are updated which
-               correspond to the given options while all other fields stay unmodified.
+               If one or more options which affect the afs information are given, only
+               the corresponding fields of the afs info structure are updated. All other
+               fields stay unmodified. For example, "touch --numplayed=42" only modifies
+               the numplayed field.
 
                If no such option is given, the lastplayed field is set to the current
                time and the value of the numplayed field is increased by one while
@@ -637,9 +634,33 @@ m4_include(`com_ll.m4')
                        The amp filter of para_audiod amplifies the volume according to
                        this value.
                [/help]
+       [option set-attribute]
+               short_opt = s
+               summary = turn on a bit in the attribute bitmask of each matching file
+               arg_type = string
+               arg_info = required_arg
+               typestr = attr
+               default_val = none
+               flag multiple
+               [help]
+                       May be given more than once to set multiple attributes in one invocation.
+               [/help]
+       [option unset-attribute]
+               short_opt = u
+               summary = turn off a bit in the attribute bitmask of each matching file
+               arg_type = string
+               arg_info = required_arg
+               typestr = attribute
+               default_val = none
+               flag multiple
+               [help]
+                       Like --set-attribute, this may be given multiple times.
+               [/help]
        [option verbose]
                short_opt = v
                summary = explain what is being done
+       [option dry-run]
+               summary = do not store the modified afs information
        [option pathname-match]
                short_opt = p
                summary = modify matching behaviour
index f7a407dc9e4bd99a71a3b3e364a6386fc55f1d44..dff7f23ad41c507646a3deaef1a505207b77b946 100755 (executable)
@@ -74,9 +74,9 @@ cmdline[$i]="lsatt"
 good[$i]='^1$'
 
 let i++
-commands[$i]='setatt'
+commands[$i]='touch'
 required_objects[$i]='ogg_afh'
-cmdline[$i]="setatt 33+ ${oggs[@]}"
+cmdline[$i]="touch --set-attribute=33 ${oggs[@]}"
 bad[$i]='.'
 
 let i++
index 4c7d3eb499462c49905f372033e534ef9d1bb316..f81c341ace2ec700b5f0da0e33d8d0384ddac493 100644 (file)
@@ -865,16 +865,16 @@ and
 lists all available attributes. You can set the "test" attribute for
 an audio file by executing
 
-       para_client setatt test+ /path/to/the/audio/file
+       para_client -- touch --set-attribute=test /path/to/the/audio/file
 
 Similarly, the "test" bit can be removed from an audio file with
 
-       para_client setatt test- /path/to/the/audio/file
+       para_client -- touch --unset-attribute=test /path/to/the/audio/file
 
 Instead of a path you may use a shell wildcard pattern. The attribute
 is applied to all audio files matching this pattern:
 
-       para_client setatt test+ '/test/directory/*'
+       para_client -- touch --set-attribute=test '/test/directory/*'
 
 The command
 
@@ -883,7 +883,7 @@ The command
 gives you a verbose listing of your audio files also showing which
 attributes are set.
 
-In case you wonder why the double-dash in the above command is needed:
+In case you wonder why the double-dash in the above commands is needed:
 It tells para_client to not interpret the options after the dashes. If
 you find this annoying, just say
 
@@ -898,7 +898,7 @@ The "test" attribute can be dropped from the database with
 Read the output of
 
        para help ls
-       para help setatt
+       para help touch
 
 for more information and a complete list of command line options to
 these commands.