Rework ID3v2 text frame values interface
Rework the interface for ID3v2 text frames by: - Switching from a mutable array to ImmutableList - Never leaving the array empty, instead filling it with "" if there are no values This makes the surface safer and more in line with the rest of the module.
This commit is contained in:
parent
c96b55029d
commit
157f1f0787
@ -811,7 +811,7 @@ public final class ImaServerSideAdInsertionMediaSource extends CompositeMediaSou
|
|||||||
if (entry instanceof TextInformationFrame) {
|
if (entry instanceof TextInformationFrame) {
|
||||||
TextInformationFrame textFrame = (TextInformationFrame) entry;
|
TextInformationFrame textFrame = (TextInformationFrame) entry;
|
||||||
if ("TXXX".equals(textFrame.id)) {
|
if ("TXXX".equals(textFrame.id)) {
|
||||||
streamPlayer.triggerUserTextReceived(textFrame.values[0]);
|
streamPlayer.triggerUserTextReceived(textFrame.values.get(0));
|
||||||
}
|
}
|
||||||
} else if (entry instanceof EventMessage) {
|
} else if (entry instanceof EventMessage) {
|
||||||
EventMessage eventMessage = (EventMessage) entry;
|
EventMessage eventMessage = (EventMessage) entry;
|
||||||
|
@ -592,7 +592,7 @@ public final class Mp3Extractor implements Extractor {
|
|||||||
Metadata.Entry entry = metadata.get(i);
|
Metadata.Entry entry = metadata.get(i);
|
||||||
if (entry instanceof TextInformationFrame
|
if (entry instanceof TextInformationFrame
|
||||||
&& ((TextInformationFrame) entry).id.equals("TLEN")) {
|
&& ((TextInformationFrame) entry).id.equals("TLEN")) {
|
||||||
return Util.msToUs(Long.parseLong(((TextInformationFrame) entry).values[0]));
|
return Util.msToUs(Long.parseLong(((TextInformationFrame) entry).values.get(0)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,33 +23,32 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.MediaMetadata;
|
import com.google.android.exoplayer2.MediaMetadata;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/** Text information ID3 frame. */
|
/** Text information ID3 frame. */
|
||||||
public final class TextInformationFrame extends Id3Frame {
|
public final class TextInformationFrame extends Id3Frame {
|
||||||
private final static String MULTI_VALUE_DELIMITER = ", ";
|
|
||||||
|
|
||||||
@Nullable public final String description;
|
@Nullable public final String description;
|
||||||
|
|
||||||
/** @deprecated Use {@code values} instead. */
|
/** @deprecated Use {@code values[0]} instead. */
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public final String value;
|
public final String value;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public final String[] values;
|
public final ImmutableList<String> values;
|
||||||
|
|
||||||
public TextInformationFrame(String id, @Nullable String description, @NonNull String[] values) {
|
public TextInformationFrame(String id, @Nullable String description, @NonNull String[] values) {
|
||||||
super(id);
|
super(id);
|
||||||
this.description = description;
|
this.description = description;
|
||||||
this.values = values;
|
|
||||||
|
|
||||||
if (values.length > 0) {
|
if( values.length > 0 ) {
|
||||||
this.value = values[0];
|
this.values = ImmutableList.copyOf(values);
|
||||||
} else {
|
} else {
|
||||||
this.value = null;
|
this.values = ImmutableList.of("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.value = this.values.get(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated Use {@code TextInformationFrame(String id, String description, String[] values} instead */
|
/** @deprecated Use {@code TextInformationFrame(String id, String description, String[] values} instead */
|
||||||
@ -61,8 +60,8 @@ public final class TextInformationFrame extends Id3Frame {
|
|||||||
/* package */ TextInformationFrame(Parcel in) {
|
/* package */ TextInformationFrame(Parcel in) {
|
||||||
super(castNonNull(in.readString()));
|
super(castNonNull(in.readString()));
|
||||||
description = in.readString();
|
description = in.readString();
|
||||||
values = in.createStringArray();
|
values = ImmutableList.copyOf(in.createStringArray());
|
||||||
this.value = values[0];
|
value = values.get(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -73,23 +72,23 @@ public final class TextInformationFrame extends Id3Frame {
|
|||||||
switch (id) {
|
switch (id) {
|
||||||
case "TT2":
|
case "TT2":
|
||||||
case "TIT2":
|
case "TIT2":
|
||||||
builder.setTitle(values[0]);
|
builder.setTitle(values.get(0));
|
||||||
break;
|
break;
|
||||||
case "TP1":
|
case "TP1":
|
||||||
case "TPE1":
|
case "TPE1":
|
||||||
builder.setArtist(String.join(MULTI_VALUE_DELIMITER, values));
|
builder.setArtist(String.join(", ", values));
|
||||||
break;
|
break;
|
||||||
case "TP2":
|
case "TP2":
|
||||||
case "TPE2":
|
case "TPE2":
|
||||||
builder.setAlbumArtist(String.join(MULTI_VALUE_DELIMITER, values));
|
builder.setAlbumArtist(String.join(", ", values));
|
||||||
break;
|
break;
|
||||||
case "TAL":
|
case "TAL":
|
||||||
case "TALB":
|
case "TALB":
|
||||||
builder.setAlbumTitle(values[0]);
|
builder.setAlbumTitle(values.get(0));
|
||||||
break;
|
break;
|
||||||
case "TRK":
|
case "TRK":
|
||||||
case "TRCK":
|
case "TRCK":
|
||||||
String[] trackNumbers = Util.split(values[0], "/");
|
String[] trackNumbers = Util.split(values.get(0), "/");
|
||||||
try {
|
try {
|
||||||
int trackNumber = Integer.parseInt(trackNumbers[0]);
|
int trackNumber = Integer.parseInt(trackNumbers[0]);
|
||||||
@Nullable
|
@Nullable
|
||||||
@ -103,7 +102,7 @@ public final class TextInformationFrame extends Id3Frame {
|
|||||||
case "TYE":
|
case "TYE":
|
||||||
case "TYER":
|
case "TYER":
|
||||||
try {
|
try {
|
||||||
builder.setRecordingYear(Integer.parseInt(values[0]));
|
builder.setRecordingYear(Integer.parseInt(values.get(0)));
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
// Do nothing, invalid input.
|
// Do nothing, invalid input.
|
||||||
}
|
}
|
||||||
@ -111,7 +110,7 @@ public final class TextInformationFrame extends Id3Frame {
|
|||||||
case "TDA":
|
case "TDA":
|
||||||
case "TDAT":
|
case "TDAT":
|
||||||
try {
|
try {
|
||||||
String date = values[0];
|
String date = values.get(0);
|
||||||
int month = Integer.parseInt(date.substring(2, 4));
|
int month = Integer.parseInt(date.substring(2, 4));
|
||||||
int day = Integer.parseInt(date.substring(0, 2));
|
int day = Integer.parseInt(date.substring(0, 2));
|
||||||
builder.setRecordingMonth(month).setRecordingDay(day);
|
builder.setRecordingMonth(month).setRecordingDay(day);
|
||||||
@ -120,7 +119,7 @@ public final class TextInformationFrame extends Id3Frame {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "TDRC":
|
case "TDRC":
|
||||||
List<Integer> recordingDate = parseId3v2point4TimestampFrameForDate(values[0]);
|
List<Integer> recordingDate = parseId3v2point4TimestampFrameForDate(values.get(0));
|
||||||
switch (recordingDate.size()) {
|
switch (recordingDate.size()) {
|
||||||
case 3:
|
case 3:
|
||||||
builder.setRecordingDay(recordingDate.get(2));
|
builder.setRecordingDay(recordingDate.get(2));
|
||||||
@ -138,7 +137,7 @@ public final class TextInformationFrame extends Id3Frame {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "TDRL":
|
case "TDRL":
|
||||||
List<Integer> releaseDate = parseId3v2point4TimestampFrameForDate(values[0]);
|
List<Integer> releaseDate = parseId3v2point4TimestampFrameForDate(values.get(0));
|
||||||
switch (releaseDate.size()) {
|
switch (releaseDate.size()) {
|
||||||
case 3:
|
case 3:
|
||||||
builder.setReleaseDay(releaseDate.get(2));
|
builder.setReleaseDay(releaseDate.get(2));
|
||||||
@ -157,15 +156,15 @@ public final class TextInformationFrame extends Id3Frame {
|
|||||||
break;
|
break;
|
||||||
case "TCM":
|
case "TCM":
|
||||||
case "TCOM":
|
case "TCOM":
|
||||||
builder.setComposer(String.join(MULTI_VALUE_DELIMITER, values));
|
builder.setComposer(String.join(", ", values));
|
||||||
break;
|
break;
|
||||||
case "TP3":
|
case "TP3":
|
||||||
case "TPE3":
|
case "TPE3":
|
||||||
builder.setConductor(String.join(MULTI_VALUE_DELIMITER, values));
|
builder.setConductor(String.join(", ", values));
|
||||||
break;
|
break;
|
||||||
case "TXT":
|
case "TXT":
|
||||||
case "TEXT":
|
case "TEXT":
|
||||||
builder.setWriter(String.join(MULTI_VALUE_DELIMITER, values));
|
builder.setWriter(String.join(", ", values));
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@ -183,7 +182,7 @@ public final class TextInformationFrame extends Id3Frame {
|
|||||||
TextInformationFrame other = (TextInformationFrame) obj;
|
TextInformationFrame other = (TextInformationFrame) obj;
|
||||||
return Util.areEqual(id, other.id)
|
return Util.areEqual(id, other.id)
|
||||||
&& Util.areEqual(description, other.description)
|
&& Util.areEqual(description, other.description)
|
||||||
&& Arrays.equals(values, other.values);
|
&& values.equals(other.values);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -191,13 +190,13 @@ public final class TextInformationFrame extends Id3Frame {
|
|||||||
int result = 17;
|
int result = 17;
|
||||||
result = 31 * result + id.hashCode();
|
result = 31 * result + id.hashCode();
|
||||||
result = 31 * result + (description != null ? description.hashCode() : 0);
|
result = 31 * result + (description != null ? description.hashCode() : 0);
|
||||||
result = 31 * result + Arrays.hashCode(values);
|
result = 31 * result + values.hashCode();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return id + ": description=" + description + ": value=" + String.join(MULTI_VALUE_DELIMITER, values);
|
return id + ": description=" + description + ": value=" + String.join(", ", values);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parcelable implementation.
|
// Parcelable implementation.
|
||||||
@ -206,7 +205,7 @@ public final class TextInformationFrame extends Id3Frame {
|
|||||||
public void writeToParcel(Parcel dest, int flags) {
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
dest.writeString(id);
|
dest.writeString(id);
|
||||||
dest.writeString(description);
|
dest.writeString(description);
|
||||||
dest.writeStringArray(values);
|
dest.writeStringArray(values.toArray(new String[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final Parcelable.Creator<TextInformationFrame> CREATOR =
|
public static final Parcelable.Creator<TextInformationFrame> CREATOR =
|
||||||
|
@ -52,7 +52,7 @@ public final class Id3DecoderTest {
|
|||||||
TextInformationFrame textInformationFrame = (TextInformationFrame) metadata.get(0);
|
TextInformationFrame textInformationFrame = (TextInformationFrame) metadata.get(0);
|
||||||
assertThat(textInformationFrame.id).isEqualTo("TXXX");
|
assertThat(textInformationFrame.id).isEqualTo("TXXX");
|
||||||
assertThat(textInformationFrame.description).isEmpty();
|
assertThat(textInformationFrame.description).isEmpty();
|
||||||
assertThat(textInformationFrame.values[0]).isEqualTo("mdialog_VINDICO1527664_start");
|
assertThat(textInformationFrame.values.get(0)).isEqualTo("mdialog_VINDICO1527664_start");
|
||||||
|
|
||||||
// Test UTF-16.
|
// Test UTF-16.
|
||||||
rawId3 =
|
rawId3 =
|
||||||
@ -67,7 +67,8 @@ public final class Id3DecoderTest {
|
|||||||
textInformationFrame = (TextInformationFrame) metadata.get(0);
|
textInformationFrame = (TextInformationFrame) metadata.get(0);
|
||||||
assertThat(textInformationFrame.id).isEqualTo("TXXX");
|
assertThat(textInformationFrame.id).isEqualTo("TXXX");
|
||||||
assertThat(textInformationFrame.description).isEqualTo("Hello World");
|
assertThat(textInformationFrame.description).isEqualTo("Hello World");
|
||||||
assertThat(textInformationFrame.values).isEmpty();
|
assertThat(textInformationFrame.values.size()).isEqualTo(1);
|
||||||
|
assertThat(textInformationFrame.values.get(0)).isEqualTo("");
|
||||||
|
|
||||||
// Test multiple values.
|
// Test multiple values.
|
||||||
rawId3 = buildSingleFrameTag(
|
rawId3 = buildSingleFrameTag(
|
||||||
@ -80,9 +81,9 @@ public final class Id3DecoderTest {
|
|||||||
assertThat(metadata.length()).isEqualTo(1);
|
assertThat(metadata.length()).isEqualTo(1);
|
||||||
textInformationFrame = (TextInformationFrame) metadata.get(0);
|
textInformationFrame = (TextInformationFrame) metadata.get(0);
|
||||||
assertThat(textInformationFrame.description).isEqualTo("Hello World");
|
assertThat(textInformationFrame.description).isEqualTo("Hello World");
|
||||||
assertThat(textInformationFrame.values.length).isEqualTo(2);
|
assertThat(textInformationFrame.values.size()).isEqualTo(2);
|
||||||
assertThat(textInformationFrame.values[0]).isEqualTo("Foo");
|
assertThat(textInformationFrame.values.get(0)).isEqualTo("Foo");
|
||||||
assertThat(textInformationFrame.values[1]).isEqualTo("Bar");
|
assertThat(textInformationFrame.values.get(1)).isEqualTo("Bar");
|
||||||
|
|
||||||
// Test empty.
|
// Test empty.
|
||||||
rawId3 = buildSingleFrameTag("TXXX", new byte[0]);
|
rawId3 = buildSingleFrameTag("TXXX", new byte[0]);
|
||||||
@ -96,7 +97,8 @@ public final class Id3DecoderTest {
|
|||||||
textInformationFrame = (TextInformationFrame) metadata.get(0);
|
textInformationFrame = (TextInformationFrame) metadata.get(0);
|
||||||
assertThat(textInformationFrame.id).isEqualTo("TXXX");
|
assertThat(textInformationFrame.id).isEqualTo("TXXX");
|
||||||
assertThat(textInformationFrame.description).isEmpty();
|
assertThat(textInformationFrame.description).isEmpty();
|
||||||
assertThat(textInformationFrame.values).isEmpty();
|
assertThat(textInformationFrame.values.size()).isEqualTo(1);
|
||||||
|
assertThat(textInformationFrame.values.get(0)).isEqualTo("");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -110,17 +112,17 @@ public final class Id3DecoderTest {
|
|||||||
TextInformationFrame textInformationFrame = (TextInformationFrame) metadata.get(0);
|
TextInformationFrame textInformationFrame = (TextInformationFrame) metadata.get(0);
|
||||||
assertThat(textInformationFrame.id).isEqualTo("TIT2");
|
assertThat(textInformationFrame.id).isEqualTo("TIT2");
|
||||||
assertThat(textInformationFrame.description).isNull();
|
assertThat(textInformationFrame.description).isNull();
|
||||||
assertThat(textInformationFrame.values.length).isEqualTo(1);
|
assertThat(textInformationFrame.values.size()).isEqualTo(1);
|
||||||
assertThat(textInformationFrame.values[0]).isEqualTo("Hello World");
|
assertThat(textInformationFrame.values.get(0)).isEqualTo("Hello World");
|
||||||
|
|
||||||
// Test multiple values.
|
// Test multiple values.
|
||||||
rawId3 = buildSingleFrameTag("TIT2", new byte[] {3, 70, 111, 111, 0, 66, 97, 114, 0});
|
rawId3 = buildSingleFrameTag("TIT2", new byte[] {3, 70, 111, 111, 0, 66, 97, 114, 0});
|
||||||
metadata = decoder.decode(rawId3, rawId3.length);
|
metadata = decoder.decode(rawId3, rawId3.length);
|
||||||
assertThat(metadata.length()).isEqualTo(1);
|
assertThat(metadata.length()).isEqualTo(1);
|
||||||
textInformationFrame = (TextInformationFrame) metadata.get(0);
|
textInformationFrame = (TextInformationFrame) metadata.get(0);
|
||||||
assertThat(textInformationFrame.values.length).isEqualTo(2);
|
assertThat(textInformationFrame.values.size()).isEqualTo(2);
|
||||||
assertThat(textInformationFrame.values[0]).isEqualTo("Foo");
|
assertThat(textInformationFrame.values.get(0)).isEqualTo("Foo");
|
||||||
assertThat(textInformationFrame.values[1]).isEqualTo("Bar");
|
assertThat(textInformationFrame.values.get(1)).isEqualTo("Bar");
|
||||||
|
|
||||||
// Test empty.
|
// Test empty.
|
||||||
rawId3 = buildSingleFrameTag("TIT2", new byte[0]);
|
rawId3 = buildSingleFrameTag("TIT2", new byte[0]);
|
||||||
@ -134,7 +136,8 @@ public final class Id3DecoderTest {
|
|||||||
textInformationFrame = (TextInformationFrame) metadata.get(0);
|
textInformationFrame = (TextInformationFrame) metadata.get(0);
|
||||||
assertThat(textInformationFrame.id).isEqualTo("TIT2");
|
assertThat(textInformationFrame.id).isEqualTo("TIT2");
|
||||||
assertThat(textInformationFrame.description).isNull();
|
assertThat(textInformationFrame.description).isNull();
|
||||||
assertThat(textInformationFrame.values).isEmpty();
|
assertThat(textInformationFrame.values.size()).isEqualTo(1);
|
||||||
|
assertThat(textInformationFrame.values.get(0)).isEqualTo("");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -62,28 +62,28 @@ public class TextInformationFrameTest {
|
|||||||
|
|
||||||
List<Metadata.Entry> entries =
|
List<Metadata.Entry> entries =
|
||||||
ImmutableList.of(
|
ImmutableList.of(
|
||||||
new TextInformationFrame(/* id= */ "TT2", /* description= */ null, /* value= */ title),
|
new TextInformationFrame(/* id= */ "TT2", /* description= */ null, /* value= */ new String[] { title }),
|
||||||
new TextInformationFrame(/* id= */ "TP1", /* description= */ null, /* value= */ artist),
|
new TextInformationFrame(/* id= */ "TP1", /* description= */ null, /* value= */ new String[] { artist }),
|
||||||
new TextInformationFrame(
|
new TextInformationFrame(
|
||||||
/* id= */ "TAL", /* description= */ null, /* value= */ albumTitle),
|
/* id= */ "TAL", /* description= */ null, /* value= */ new String[] { albumTitle }),
|
||||||
new TextInformationFrame(
|
new TextInformationFrame(
|
||||||
/* id= */ "TP2", /* description= */ null, /* value= */ albumArtist),
|
/* id= */ "TP2", /* description= */ null, /* value= */ new String[] { albumArtist }),
|
||||||
new TextInformationFrame(
|
new TextInformationFrame(
|
||||||
/* id= */ "TRK", /* description= */ null, /* value= */ trackNumberInfo),
|
/* id= */ "TRK", /* description= */ null, /* value= */ new String[] { trackNumberInfo}),
|
||||||
new TextInformationFrame(
|
new TextInformationFrame(
|
||||||
/* id= */ "TYE", /* description= */ null, /* value= */ recordingYear),
|
/* id= */ "TYE", /* description= */ null, /* value= */ new String[] { recordingYear }),
|
||||||
new TextInformationFrame(
|
new TextInformationFrame(
|
||||||
/* id= */ "TDA",
|
/* id= */ "TDA",
|
||||||
/* description= */ null,
|
/* description= */ null,
|
||||||
/* value= */ recordingDay + recordingMonth),
|
/* value= */ new String[] { recordingDay + recordingMonth }),
|
||||||
new TextInformationFrame(
|
new TextInformationFrame(
|
||||||
/* id= */ "TDRL", /* description= */ null, /* value= */ releaseDate),
|
/* id= */ "TDRL", /* description= */ null, /* value= */ new String[] { releaseDate }),
|
||||||
new TextInformationFrame(
|
new TextInformationFrame(
|
||||||
/* id= */ "TCM", /* description= */ null, /* value= */ composer),
|
/* id= */ "TCM", /* description= */ null, /* value= */ new String[] { composer }),
|
||||||
new TextInformationFrame(
|
new TextInformationFrame(
|
||||||
/* id= */ "TP3", /* description= */ null, /* value= */ conductor),
|
/* id= */ "TP3", /* description= */ null, /* value= */ new String[] { conductor }),
|
||||||
new TextInformationFrame(
|
new TextInformationFrame(
|
||||||
/* id= */ "TXT", /* description= */ null, /* value= */ writer));
|
/* id= */ "TXT", /* description= */ null, /* value= */ new String[] { writer }));
|
||||||
MediaMetadata.Builder builder = MediaMetadata.EMPTY.buildUpon();
|
MediaMetadata.Builder builder = MediaMetadata.EMPTY.buildUpon();
|
||||||
|
|
||||||
for (Metadata.Entry entry : entries) {
|
for (Metadata.Entry entry : entries) {
|
||||||
@ -107,5 +107,21 @@ public class TextInformationFrameTest {
|
|||||||
assertThat(mediaMetadata.composer.toString()).isEqualTo(composer);
|
assertThat(mediaMetadata.composer.toString()).isEqualTo(composer);
|
||||||
assertThat(mediaMetadata.conductor.toString()).isEqualTo(conductor);
|
assertThat(mediaMetadata.conductor.toString()).isEqualTo(conductor);
|
||||||
assertThat(mediaMetadata.writer.toString()).isEqualTo(writer);
|
assertThat(mediaMetadata.writer.toString()).isEqualTo(writer);
|
||||||
|
|
||||||
|
// Test empty value array
|
||||||
|
entries =
|
||||||
|
ImmutableList.of(
|
||||||
|
new TextInformationFrame(/* id= */ "TT2", /* description= */ null, /* value= */ new String[0])
|
||||||
|
);
|
||||||
|
|
||||||
|
builder = MediaMetadata.EMPTY.buildUpon();
|
||||||
|
|
||||||
|
for (Metadata.Entry entry : entries) {
|
||||||
|
entry.populateMediaMetadata(builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
mediaMetadata = builder.build();
|
||||||
|
|
||||||
|
assertThat(mediaMetadata.title.toString()).isEqualTo("");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user