+ unsigned char file_hash[HASH_SIZE];
+ struct osl_object afsi_obj;
+ struct afs_info new_afsi;
+ int ret;
+ struct afsi_change_event_data aced;
+ struct osl_object map, chunk_table_obj;
+ struct ls_data *d = &status_item_ls_data;
+again:
+ ret = score_get_best(¤t_aft_row, &d->score);
+ if (ret < 0)
+ return ret;
+ ret = get_hash_of_row(current_aft_row, &d->hash);
+ if (ret < 0)
+ return ret;
+ ret = get_audio_file_path_of_row(current_aft_row, &d->path);
+ if (ret < 0)
+ return ret;
+ PARA_NOTICE_LOG("%s\n", d->path);
+ ret = get_afsi_object_of_row(current_aft_row, &afsi_obj);
+ if (ret < 0)
+ return ret;
+ ret = load_afsi(&d->afsi, &afsi_obj);
+ if (ret < 0)
+ return ret;
+ ret = get_afhi_of_row(current_aft_row, &afd->afhi);
+ if (ret < 0)
+ return ret;
+ d->afhi = afd->afhi;
+ d->afhi.chunk_table = afd->afhi.chunk_table = NULL;
+ ret = osl(osl_open_disk_object(audio_file_table, current_aft_row,
+ AFTCOL_CHUNKS, &chunk_table_obj));
+ if (ret < 0) {
+ if (!afh_supports_dynamic_chunks(d->afsi.audio_format_id))
+ return ret;
+ PARA_INFO_LOG("no chunk table for %s\n", d->path);
+ chunk_table_obj.data = NULL;
+ chunk_table_obj.size = 0;
+ } else {
+ PARA_INFO_LOG("chunk table: %zu bytes\n", chunk_table_obj.size);
+ }
+ ret = mmap_full_file(d->path, O_RDONLY, &map.data, &map.size, &afd->fd);
+ if (ret < 0)
+ goto out;
+ hash_function(map.data, map.size, file_hash);
+ ret = hash_compare(file_hash, d->hash);
+ para_munmap(map.data, map.size);
+ if (ret) {
+ ret = -E_HASH_MISMATCH;
+ goto out;
+ }
+ new_afsi = d->afsi;
+ new_afsi.num_played++;
+ new_afsi.last_played = time(NULL);
+ save_afsi(&new_afsi, &afsi_obj); /* in-place update */
+
+ afd->audio_format_id = d->afsi.audio_format_id;
+ load_chunk_table(&afd->afhi, &chunk_table_obj);
+ aced.aft_row = current_aft_row;
+ aced.old_afsi = &d->afsi;
+ /*
+ * No need to update the status items as the AFSI_CHANGE event will
+ * recreate them.
+ */
+ ret = afs_event(AFSI_CHANGE, NULL, &aced);
+ if (ret < 0)
+ goto out;
+ ret = save_afd(afd);
+out:
+ free(afd->afhi.chunk_table);
+ if (chunk_table_obj.data)
+ osl_close_disk_object(&chunk_table_obj);
+ if (ret < 0) {
+ PARA_ERROR_LOG("%s: %s\n", d->path, para_strerror(-ret));
+ ret = score_delete(current_aft_row);
+ if (ret >= 0)
+ goto again;
+ }
+ return ret;
+}
+
+static int ls_hash_compare(const void *a, const void *b)
+{
+ struct ls_data *d1 = *(struct ls_data **)a, *d2 = *(struct ls_data **)b;
+ return memcmp(d1->hash, d2->hash, HASH_SIZE);
+}
+
+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);
+}