+ ret = write_filename_items(b, d->path, opts->flags);
+ if (ret < 0)
+ goto out;
+ ret = write_score(b, d, opts);
+ if (ret < 0)
+ goto out;
+ ret = write_attribute_items(b, att_buf, afsi);
+ if (ret < 0)
+ goto out;
+ ret = write_image_items(b, afsi);
+ if (ret < 0)
+ goto out;
+ ret = write_lyrics_items(b, afsi);
+ if (ret < 0)
+ goto out;
+ hash_to_asc(d->hash, asc_hash);
+ ret = WRITE_STATUS_ITEM(b, SI_HASH, "%s\n", asc_hash);
+ if (ret < 0)
+ goto out;
+ ret = WRITE_STATUS_ITEM(b, SI_BITRATE, "%dkbit/s\n", afhi->bitrate);
+ if (ret < 0)
+ goto out;
+ ret = WRITE_STATUS_ITEM(b, SI_FORMAT, "%s\n",
+ audio_format_name(afsi->audio_format_id));
+ if (ret < 0)
+ goto out;
+ ret = WRITE_STATUS_ITEM(b, SI_FREQUENCY, "%dHz\n", afhi->frequency);
+ if (ret < 0)
+ goto out;
+ ret = WRITE_STATUS_ITEM(b, SI_CHANNELS, "%d\n", afhi->channels);
+ if (ret < 0)
+ goto out;
+ ret = WRITE_STATUS_ITEM(b, SI_DURATION, "%s\n", duration_buf);
+ if (ret < 0)
+ goto out;
+ ret = WRITE_STATUS_ITEM(b, SI_SECONDS_TOTAL, "%lu\n",
+ afhi->seconds_total);
+ if (ret < 0)
+ goto out;
+ ret = WRITE_STATUS_ITEM(b, SI_LAST_PLAYED, "%s\n", last_played_time);
+ if (ret < 0)
+ goto out;
+ ret = WRITE_STATUS_ITEM(b, SI_NUM_PLAYED, "%d\n", afsi->num_played);
+ if (ret < 0)
+ goto out;
+ ret = WRITE_STATUS_ITEM(b, SI_AMPLIFICATION, "%u\n", afsi->amp);
+ if (ret < 0)
+ goto out;
+ ret = WRITE_STATUS_ITEM(b, SI_CHUNK_TIME, "%lu\n",
+ tv2ms(&afhi->chunk_tv));
+ if (ret < 0)
+ goto out;
+ ret = WRITE_STATUS_ITEM(b, SI_NUM_CHUNKS, "%lu\n",
+ afhi->chunks_total);
+ if (ret < 0)
+ goto out;
+ ret = WRITE_STATUS_ITEM(b, SI_TECHINFO, "%s\n", afhi->techinfo);
+ if (ret < 0)
+ goto out;
+ ret = WRITE_STATUS_ITEM(b, SI_ARTIST, "%s\n", afhi->tags.artist);
+ if (ret < 0)
+ goto out;
+ ret = WRITE_STATUS_ITEM(b, SI_TITLE, "%s\n", afhi->tags.title);
+ if (ret < 0)
+ goto out;
+ ret = WRITE_STATUS_ITEM(b, SI_YEAR, "%s\n", afhi->tags.year);
+ if (ret < 0)
+ goto out;
+ ret = WRITE_STATUS_ITEM(b, SI_ALBUM, "%s\n", afhi->tags.album);
+ if (ret < 0)
+ goto out;
+ ret = WRITE_STATUS_ITEM(b, SI_COMMENT, "%s\n", afhi->tags.comment);
+ if (ret < 0)
+ goto out;
+ if (opts->mode == LS_MODE_MBOX) {
+ struct osl_object lyrics_def;
+ lyr_get_def_by_id(afsi->lyrics_id, &lyrics_def);
+ if (lyrics_def.data) {
+ ret = para_printf(b, "Lyrics:\n~~~~~~~\n%s",
+ (char *)lyrics_def.data);
+ osl_close_disk_object(&lyrics_def);
+ }
+ }
+out:
+ return ret;
+}
+
+static int make_status_items(struct audio_file_data *afd,
+ struct afs_info *afsi, char *path, long score,
+ unsigned char *hash)
+{
+ struct ls_data d = {
+ .afhi = afd->afhi,
+ .afsi = *afsi,
+ .path = path,
+ .score = score,
+ .hash = hash
+ };
+ struct ls_options opts = {
+ .flags = LS_FLAG_FULL_PATH | LS_FLAG_ADMISSIBLE_ONLY,
+ .mode = LS_MODE_VERBOSE,
+ };
+ struct para_buffer pb = {.max_size = SHMMAX - 1};
+ time_t current_time;
+ int ret;
+
+ time(¤t_time);
+ ret = print_list_item(&d, &opts, &pb, current_time);
+ if (ret < 0)
+ return ret;
+ free(status_items);
+ status_items = pb.buf;
+ memset(&pb, 0, sizeof(pb));
+ pb.max_size = SHMMAX - 1;
+ pb.flags = PBF_SIZE_PREFIX;
+ ret = print_list_item(&d, &opts, &pb, current_time);
+ if (ret < 0) {
+ free(status_items);
+ status_items = NULL;
+ return ret;
+ }
+ free(parser_friendly_status_items);
+ parser_friendly_status_items = pb.buf;
+ return 1;
+}
+
+/**
+ * Mmap the given audio file and update statistics.
+ *
+ * \param aft_row Determines the audio file to be opened and updated.
+ * \param score The score of the audio file.
+ * \param afd Result pointer.
+ *
+ * On success, the numplayed field of the audio file selector info is increased
+ * and the lastplayed time is set to the current time. Finally, the score of
+ * the audio file is updated.
+ *
+ * \return Positive shmid on success, negative on errors.
+ */
+int open_and_update_audio_file(struct osl_row *aft_row, long score,
+ struct audio_file_data *afd)
+{
+ unsigned char *aft_hash, file_hash[HASH_SIZE];
+ struct osl_object afsi_obj;
+ struct afs_info old_afsi, new_afsi;
+ int ret = get_hash_of_row(aft_row, &aft_hash);
+ struct afsi_change_event_data aced;
+ struct osl_object map, chunk_table_obj;
+ char *path;
+
+ if (ret < 0)
+ return ret;
+ ret = get_audio_file_path_of_row(aft_row, &path);
+ if (ret < 0)
+ return ret;
+ PARA_NOTICE_LOG("%s\n", path);
+ ret = get_afsi_object_of_row(aft_row, &afsi_obj);
+ if (ret < 0)
+ return ret;
+ ret = load_afsi(&old_afsi, &afsi_obj);
+ if (ret < 0)
+ return ret;
+ ret = get_afhi_of_row(aft_row, &afd->afhi);
+ if (ret < 0)
+ return ret;
+ afd->afhi.chunk_table = NULL;
+ ret = osl_open_disk_object(audio_file_table, aft_row,
+ AFTCOL_CHUNKS, &chunk_table_obj);
+ if (ret < 0)
+ goto err;
+ ret = mmap_full_file(path, O_RDONLY, &map.data,
+ &map.size, &afd->fd);
+ if (ret < 0)
+ goto err;
+ hash_function(map.data, map.size, file_hash);
+ ret = hash_compare(file_hash, aft_hash);
+ para_munmap(map.data, map.size);
+ if (ret) {
+ ret = -E_HASH_MISMATCH;
+ goto err;
+ }
+ new_afsi = old_afsi;
+ new_afsi.num_played++;
+ new_afsi.last_played = time(NULL);
+ save_afsi(&new_afsi, &afsi_obj); /* in-place update */
+
+ afd->audio_format_id = old_afsi.audio_format_id;
+ load_chunk_table(&afd->afhi, chunk_table_obj.data);
+ ret = make_status_items(afd, &old_afsi, path, score, file_hash);
+ if (ret < 0)
+ goto err;
+ aced.aft_row = aft_row;
+ aced.old_afsi = &old_afsi;
+ afs_event(AFSI_CHANGE, NULL, &aced);
+ ret = save_afd(afd);
+err:
+ free(afd->afhi.chunk_table);
+ osl_close_disk_object(&chunk_table_obj);
+ if (ret < 0)
+ PARA_ERROR_LOG("%s: %s\n", path, para_strerror(-ret));
+ return ret;
+}
+
+static int ls_audio_format_compare(const void *a, const void *b)
+{
+ struct ls_data *d1 = *(struct ls_data **)a, *d2 = *(struct ls_data **)b;
+ return NUM_COMPARE(d1->afsi.audio_format_id, d2->afsi.audio_format_id);
+}
+
+static int ls_duration_compare(const void *a, const void *b)
+{
+ struct ls_data *d1 = *(struct ls_data **)a, *d2 = *(struct ls_data **)b;
+ return NUM_COMPARE(d1->afhi.seconds_total, d2->afhi.seconds_total);
+}
+
+static int ls_bitrate_compare(const void *a, const void *b)
+{
+ struct ls_data *d1 = *(struct ls_data **)a, *d2 = *(struct ls_data **)b;
+ return NUM_COMPARE(d1->afhi.bitrate, d2->afhi.bitrate);
+}
+
+static int ls_lyrics_id_compare(const void *a, const void *b)
+{
+ struct ls_data *d1 = *(struct ls_data **)a, *d2 = *(struct ls_data **)b;
+ return NUM_COMPARE(d1->afsi.lyrics_id, d2->afsi.lyrics_id);
+}
+
+static int ls_image_id_compare(const void *a, const void *b)
+{
+ struct ls_data *d1 = *(struct ls_data **)a, *d2 = *(struct ls_data **)b;
+ return NUM_COMPARE(d1->afsi.image_id, d2->afsi.image_id);
+}
+
+static int ls_channels_compare(const void *a, const void *b)
+{
+ struct ls_data *d1 = *(struct ls_data **)a, *d2 = *(struct ls_data **)b;
+ return NUM_COMPARE(d1->afhi.channels, d2->afhi.channels);
+}
+
+static int ls_frequency_compare(const void *a, const void *b)
+{