+/**
+ * Flags used by the cpsi command.
+ *
+ * \sa com_cpsi().
+ */
+enum cpsi_flags {
+ /** Whether the lyrics id should be copied. */
+ CPSI_FLAG_COPY_LYRICS_ID = 1,
+ /** Whether the image id should be copied. */
+ CPSI_FLAG_COPY_IMAGE_ID = 2,
+ /** Whether the lastplayed time should be copied. */
+ CPSI_FLAG_COPY_LASTPLAYED = 4,
+ /** Whether the numplayed count should be copied. */
+ CPSI_FLAG_COPY_NUMPLAYED = 8,
+ /** Whether the attributes should be copied. */
+ CPSI_FLAG_COPY_ATTRIBUTES = 16,
+ /** Activates verbose mode. */
+ CPSI_FLAG_VERBOSE = 32,
+};
+
+/** Data passed to the action handler of com_cpsi(). */
+struct cpsi_action_data {
+ /** command line flags (see \ref cpsi_flags). */
+ unsigned flags;
+ /** Message buffer. */
+ struct para_buffer pb;
+ /** Values are copied from here. */
+ struct afs_info source_afsi;
+};
+
+static int copy_selector_info(__a_unused struct osl_table *table,
+ struct osl_row *row, const char *name, void *data)
+{
+ struct cpsi_action_data *cad = data;
+ struct osl_object target_afsi_obj;
+ int ret;
+ struct afs_info old_afsi, target_afsi;
+ struct afsi_change_event_data aced;
+
+ ret = get_afsi_object_of_row(row, &target_afsi_obj);
+ if (ret < 0)
+ return ret;
+ load_afsi(&target_afsi, &target_afsi_obj);
+ old_afsi = target_afsi;
+ if (cad->flags & CPSI_FLAG_COPY_LYRICS_ID)
+ target_afsi.lyrics_id = cad->source_afsi.lyrics_id;
+ if (cad->flags & CPSI_FLAG_COPY_IMAGE_ID)
+ target_afsi.image_id = cad->source_afsi.image_id;
+ if (cad->flags & CPSI_FLAG_COPY_LASTPLAYED)
+ target_afsi.last_played = cad->source_afsi.last_played;
+ if (cad->flags & CPSI_FLAG_COPY_NUMPLAYED)
+ target_afsi.num_played = cad->source_afsi.num_played;
+ if (cad->flags & CPSI_FLAG_COPY_ATTRIBUTES)
+ target_afsi.attributes = cad->source_afsi.attributes;
+ save_afsi(&target_afsi, &target_afsi_obj); /* in-place update */
+ if (cad->flags & CPSI_FLAG_VERBOSE) {
+ ret = para_printf(&cad->pb, "copied afsi to %s\n", name);
+ if (ret < 0)
+ return ret;
+ }
+ aced.aft_row = row;
+ aced.old_afsi = &old_afsi;
+ afs_event(AFSI_CHANGE, &cad->pb, &aced);
+ return 1;
+}
+
+static void com_cpsi_callback(int fd, const struct osl_object *query)
+{
+ struct cpsi_action_data cad = {
+ .flags = *(unsigned *)query->data,
+ .pb = {
+ .max_size = shm_get_shmmax(),
+ .private_data = &fd,
+ .max_size_handler = afs_max_size_handler
+ }
+ };
+ int ret;
+ char *source_path = (char *)query->data + sizeof(cad.flags);
+ struct pattern_match_data pmd = {
+ .table = audio_file_table,
+ .loop_col_num = AFTCOL_HASH,
+ .match_col_num = AFTCOL_PATH,
+ .patterns = {.data = source_path + strlen(source_path) + 1,
+ .size = query->size - sizeof(cad.flags)
+ - strlen(source_path) - 1},
+ .data = &cad,
+ .action = copy_selector_info
+ };
+
+ ret = get_afsi_of_path(source_path, &cad.source_afsi);
+ if (ret < 0)
+ goto out;
+ ret = for_each_matching_row(&pmd);
+out:
+ if (ret < 0)
+ para_printf(&cad.pb, "%s\n", para_strerror(-ret));
+ else if (cad.flags & CPSI_FLAG_VERBOSE) {
+ if (pmd.num_matches > 0)
+ para_printf(&cad.pb, "copied requested afsi from %s "
+ "to %u files\n", source_path, pmd.num_matches);
+ else
+ para_printf(&cad.pb, "nothing copied\n");
+ }
+ if (cad.pb.offset)
+ pass_buffer_as_shm(fd, SBD_OUTPUT, cad.pb.buf, cad.pb.offset);
+ free(cad.pb.buf);
+}
+
+int com_cpsi(struct command_context *cc)
+{
+ unsigned flags = 0;
+ int i, ret;
+ struct osl_object options = {.data = &flags, .size = sizeof(flags)};
+
+ for (i = 1; i < cc->argc; i++) {
+ const char *arg = cc->argv[i];
+ if (arg[0] != '-')
+ break;
+ if (!strcmp(arg, "--")) {
+ i++;
+ break;
+ }
+ if (!strcmp(arg, "-y")) {
+ flags |= CPSI_FLAG_COPY_LYRICS_ID;
+ continue;
+ }
+ if (!strcmp(arg, "-i")) {
+ flags |= CPSI_FLAG_COPY_IMAGE_ID;
+ continue;
+ }
+ if (!strcmp(arg, "-l")) {
+ flags |= CPSI_FLAG_COPY_LASTPLAYED;
+ continue;
+ }
+ if (!strcmp(arg, "-n")) {
+ flags |= CPSI_FLAG_COPY_NUMPLAYED;
+ continue;
+ }
+ if (!strcmp(arg, "-a")) {
+ flags |= CPSI_FLAG_COPY_ATTRIBUTES;
+ continue;
+ }
+ if (!strcmp(arg, "-v")) {
+ flags |= CPSI_FLAG_VERBOSE;
+ continue;
+ }
+ break;
+ }
+ if (i + 1 >= cc->argc) /* need at least source file and pattern */
+ return -E_AFT_SYNTAX;
+ if (!(flags & ~CPSI_FLAG_VERBOSE)) /* no copy flags given */
+ flags = ~(unsigned)CPSI_FLAG_VERBOSE | flags;
+ ret = send_option_arg_callback_request(&options, cc->argc - i,
+ cc->argv + i, com_cpsi_callback, afs_cb_result_handler, cc);
+ if (ret < 0)
+ send_strerror(cc, -ret);
+ return ret;
+}
+
+struct change_atts_data {
+ uint64_t add_mask, del_mask;
+ struct para_buffer pb;
+};
+
+static int change_atts(__a_unused struct osl_table *table,
+ struct osl_row *row, __a_unused const char *name, void *data)
+{
+ int ret;
+ struct osl_object obj;
+ struct afs_info old_afsi, new_afsi;
+ struct afsi_change_event_data aced = {
+ .aft_row = row,
+ .old_afsi = &old_afsi
+ };
+ struct change_atts_data *cad = data;
+
+ ret = get_afsi_object_of_row(row, &obj);
+ if (ret < 0)
+ return ret;
+ ret = load_afsi(&old_afsi, &obj);
+ if (ret < 0)
+ return ret;
+ new_afsi = old_afsi;
+ new_afsi.attributes |= cad->add_mask;
+ new_afsi.attributes &= ~cad->del_mask;
+ save_afsi(&new_afsi, &obj); /* in-place update */
+ afs_event(AFSI_CHANGE, &cad->pb, &aced);
+ return 1;
+}
+
+static void com_setatt_callback(int fd, const struct osl_object *query)
+{
+ char *p;
+ int ret;
+ size_t len;
+ struct change_atts_data cad = {
+ .pb = {
+ .max_size = shm_get_shmmax(),
+ .max_size_handler = afs_max_size_handler,
+ .private_data = &(struct afs_max_size_handler_data) {
+ .fd = fd,
+ .band = SBD_OUTPUT
+ }
+ }
+ };
+ struct pattern_match_data pmd = {
+ .table = audio_file_table,
+ .loop_col_num = AFTCOL_HASH,
+ .match_col_num = AFTCOL_PATH,
+ .pm_flags = PM_SKIP_EMPTY_NAME,
+ .data = &cad,
+ .action = change_atts
+ };
+
+ for (p = query->data; p < (char *)query->data + query->size; p += len + 1) {
+ char c;
+ unsigned char bitnum;
+
+ len = strlen(p);
+ ret = -E_ATTR_SYNTAX;
+ if (len == 0)
+ goto out;
+ c = p[len - 1];
+ if (c != '+' && c != '-')
+ break;
+ p[len - 1] = '\0';
+ ret = get_attribute_bitnum_by_name(p, &bitnum);
+ if (ret < 0)
+ goto out;
+ if (c == '+')
+ cad.add_mask |= (1UL << bitnum);
+ else
+ cad.del_mask |= (1UL << bitnum);
+ }
+ ret = -E_ATTR_SYNTAX;
+ if (!cad.add_mask && !cad.del_mask)
+ goto out;
+ pmd.patterns.data = p;
+ assert(p < (char *)query->data + query->size);
+ pmd.patterns.size = (char *)query->data + query->size - p;
+ ret = for_each_matching_row(&pmd);
+ if (ret < 0)
+ goto out;
+ if (pmd.num_matches == 0)
+ para_printf(&cad.pb, "no matches\n");
+out:
+ if (ret < 0)
+ para_printf(&cad.pb, "%s\n", para_strerror(-ret));
+ if (cad.pb.offset)
+ pass_buffer_as_shm(fd, SBD_OUTPUT, cad.pb.buf, cad.pb.offset);
+ free(cad.pb.buf);
+}
+
+int com_setatt(struct command_context *cc)
+{
+ if (cc->argc < 3)
+ return -E_ATTR_SYNTAX;
+ return send_standard_callback_request(cc->argc - 1, cc->argv + 1,
+ com_setatt_callback, afs_cb_result_handler, cc);
+}
+
+static void afs_stat_callback(int fd, const struct osl_object *query)
+{
+ int *parser_friendly = query->data;
+ char *buf = *parser_friendly?
+ parser_friendly_status_items : status_items;
+
+ if (!buf)
+ return;
+ pass_buffer_as_shm(fd, SBD_OUTPUT, buf, strlen(buf));
+}
+
+/**
+ * Get the current afs status items from the afs process and send it.
+ *
+ * \param cc The command context, used e.g. for data encryption.
+ * \param parser_friendly Whether parser-friendly output format should be used.
+ *
+ * As the contents of the afs status items change in time and the command
+ * handler only has a COW version created at fork time, it can not send
+ * up-to-date afs status items directly. Therefore the usual callback mechanism
+ * is used to pass the status items from the afs process to the command handler
+ * via a shared memory area and a pipe.
+ *
+ * \return The return value of the underyling call to \ref send_callback_request().
+ */
+int send_afs_status(struct command_context *cc, int parser_friendly)
+{
+ struct osl_object query = {.data = &parser_friendly,
+ .size = sizeof(parser_friendly)};
+
+ return send_callback_request(afs_stat_callback, &query,
+ afs_cb_result_handler, cc);
+}
+