/* * Universal Media Server, for streaming any media to DLNA * compatible renderers based on the http://www.ps3mediaserver.org. * Copyright (C) 2012 UMS developers. * * This program is a free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; version 2 * of the License only. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package net.pms.dlna.protocolinfo; import java.util.ArrayList; import java.util.List; import org.apache.commons.lang3.StringUtils; import net.pms.dlna.MediaType; import net.pms.dlna.protocolinfo.ProtocolInfoAttributeName.KnownProtocolInfoAttributeName; /** * This class is immutable and represents the {@code DLNA.ORG_FLAGS} parameter. * This can only be used for DLNA content. * * @author Nadahar */ public class DLNAOrgFlags implements ProtocolInfoAttribute { private static final long serialVersionUID = 1L; /** The first/most significant bits/flags. */ protected final long high; /** The last/least significant bits/flags. */ protected final long low; /** The static name of this attribute. */ public static final ProtocolInfoAttributeName NAME = KnownProtocolInfoAttributeName.DLNA_ORG_FLAGS; /** * A {@link DLNAOrgFlags} instance that represents the implied flags where * all flags are 0 and results in an empty parameter string. */ public static final DLNAOrgFlags IMPLIED = new DLNAOrgFlags(0, 0); /** * Calculates the "effective" flags for a {@link DLNAOrgFlags} instance by * applying DLNA conditional rules for some flags. {@code null} can be * passed to get the flags that represent an omitted {@code DLNA.ORG_FLAGS} * parameter. * * @param flags the {@link DLNAOrgFlags} instance to process. * @param mediaType the {@link MediaType} of the media for which this flag * applies. * @return A new {@link DLNAOrgFlags} instance where illegal combinations * have been corrected. */ public static DLNAOrgFlags getEffectiveFlags(DLNAOrgFlags flags, MediaType mediaType) { long high = flags == null ? 0 : flags.high; if (flags == null || !flags.isDLNA15()) { high &= ~(1L << 63); high &= ~(1L << 62); high &= ~(1L << 61); high &= ~(1L << 60); high &= ~(1L << 59); high &= ~(1L << 58); high &= ~(1L << 57); if (mediaType == MediaType.AUDIO || mediaType == MediaType.VIDEO) { high |= 1L << 56; } else { high &= ~(1L << 56); } // XHTML Print documents and media collection files too, when/if there is a MediaType for those if (mediaType == MediaType.IMAGE) { high |= 1L << 55; } else { high &= ~(1L << 55); } high &= ~(1L << 54); if (flags != null) { if (flags.isCleartextByteFullDataSeek()) { high |= 1L << 48; } } high &= ~(1L << 46); } else { if (flags.isCleartextByteFullDataSeek() || flags.isCleartextLimitedDataSeek()) { high |= 1L << 48; } } if (high == 0 && (flags == null || flags.low == 0)) { return IMPLIED; } return new DLNAOrgFlags(high, flags == null ? 0 : flags.low); } /** * Creates a new {@link DLNAOrgFlagsBuilder} instance which can be used * to {@link #build} an {@link DLNAOrgFlags} instance. * * @param isDLNA15 Sets bit 20: dlna-v1.5-flag (DLNA v1.5 versioning * flag). * @return The {@link DLNAOrgFlagsBuilder} instance. */ public static DLNAOrgFlagsBuilder builder(boolean isDLNA15) { return new DLNAOrgFlagsBuilder(isDLNA15); } /** * Creates a new {@link DLNAOrgFlags} instance based on a string of * hexadecimal digits. The argument can be either only the "primary-flags" * (the first 8 digits) or the full flag (all 32 digits). values. * * @param hexValue the {@link String} containing only characters * representing hexadecimal values ({@code 0-9, a-f, A-F}). */ public DLNAOrgFlags(String hexValue) { if (StringUtils.isBlank(hexValue)) { throw new IllegalArgumentException("hexValue cannot be empty"); } try { if (hexValue.length() == 8) { high = Long.parseLong(hexValue, 16) << 32; low = 0; } else if (hexValue.length() == 32) { high = Long.parseLong(hexValue.substring(0, 8), 16) << 32 + Long.parseLong(hexValue.substring(8, 16), 16); //Unsigned low = Long.parseLong(hexValue.substring(16, 24), 16) << 32 + Long.parseLong(hexValue.substring(24), 16); //Unsigned } else { throw new IllegalArgumentException("hexValue must be 8 or 32 digits long"); } } catch (NumberFormatException e) { throw new IllegalArgumentException("hexValue must be a valid hexadecimal number with 8 or 32 digits", e); } } /** * Creates a new {@link DLNAOrgFlags} instance based on the given bit * values. * * @param highValue the first 64 bits/flags. * @param lowValue the last 64 bits/flags. */ public DLNAOrgFlags(long highValue, long lowValue) { this.high = highValue; this.low = lowValue; } /** * Bit-31: sp-flag (Sender Paced flag). * * @return {@code true} if the flag is {@code 1}, {@code false} if the flag * is {@code 0}. */ public boolean isSenderPaced() { return ((high >> 63) & 1) == 1; } /** * Bit-30: lop-npt (Limited Operations flag: Time-Based Seek). * * @return {@code true} if the flag is {@code 1}, {@code false} if the flag * is {@code 0}. */ public boolean isLimitedOperationsTimeBasedSeek() { return ((high >> 62) & 1) == 1; } /** * Bit-29: lop-bytes (Limited Operations flag: Byte-Based Seek). * * @return {@code true} if the flag is {@code 1}, {@code false} if the flag * is {@code 0}. */ public boolean isLimitedOperationsByteBasedSeek() { return ((high >> 61) & 1) == 1; } /** * Bit-28: playcontainer-param (DLNA PlayContainer flag). * * @return {@code true} if the flag is {@code 1}, {@code false} if the flag * is {@code 0}. */ public boolean isPlayContainer() { return ((high >> 60) & 1) == 1; } /** * Bit 27: s0-increasing (UCDAM s0 Increasing flag). * * @return {@code true} if the flag is {@code 1}, {@code false} if the flag * is {@code 0}. */ public boolean isS0Increasing() { return ((high >> 59) & 1) == 1; } /** * Bit 26: sN-increasing (UCDAM sN Increasing flag). * * @return {@code true} if the flag is {@code 1}, {@code false} if the flag * is {@code 0}. */ public boolean isSNIncreasing() { return ((high >> 58) & 1) == 1; } /** * Bit-25: rtsp-pause (Pause media operation support for RTP Serving * Endpoints). * * @return {@code true} if the flag is {@code 1}, {@code false} if the flag * is {@code 0}. */ public boolean isRtspPause() { return ((high >> 57) & 1) == 1; } /** * Bit 24: tm-s (Streaming mode flag). * * @return {@code true} if the flag is {@code 1}, {@code false} if the flag * is {@code 0}. */ public boolean isStreamingMode() { return ((high >> 56) & 1) == 1; } /** * Bit 23: tm-i (Interactive mode flag). * * @return {@code true} if the flag is {@code 1}, {@code false} if the flag * is {@code 0}. */ public boolean isInteractive() { return ((high >> 55) & 1) == 1; } /** * Bit 22: tm-b (Background mode flag). * * @return {@code true} if the flag is {@code 1}, {@code false} if the flag * is {@code 0}. */ public boolean isBackgroundMode() { return ((high >> 54) & 1) == 1; } /** * Bit 21: http-stalling (HTTP Connection Stalling flag). * * @return {@code true} if the flag is {@code 1}, {@code false} if the flag * is {@code 0}. */ public boolean isHttpConnectionStalling() { return ((high >> 53) & 1) == 1; } /** * Bit 20: dlna-v1.5-flag (DLNA v1.5 versioning flag). * * @return {@code true} if the flag is {@code 1}, {@code false} if the flag * is {@code 0}. */ public boolean isDLNA15() { return ((high >> 52) & 1) == 1; } /** * Bit 16: LP-flag (Link Protected content flag). * * @return {@code true} if the flag is {@code 1}, {@code false} if the flag * is {@code 0}. */ public boolean isLinkProtectedContent() { return ((high >> 48) & 1) == 1; } /** * Bit 15: cleartextbyteseek-full flag (Cleartext Byte Full Data Seek flag). * * @return {@code true} if the flag is {@code 1}, {@code false} if the flag * is {@code 0}. */ public boolean isCleartextByteFullDataSeek() { return ((high >> 47) & 1) == 1; } /** * Bit 14: lop-cleartextbytes flag (Cleartext Limited Data Seek flag). * * @return {@code true} if the flag is {@code 1}, {@code false} if the flag * is {@code 0}. */ public boolean isCleartextLimitedDataSeek() { return ((high >> 46) & 1) == 1; } /** * Calculates the "effective" flags for this {@link DLNAOrgFlags} instance * by applying DLNA conditional rules for some flags. * * @param mediaType the {@link MediaType} of the media for which this * instance applies. * @return The new {@link DLNAOrgFlags} instance where illegal combinations * have been corrected. */ public DLNAOrgFlags getEffectiveFlags(MediaType mediaType) { return DLNAOrgFlags.getEffectiveFlags(this, mediaType); } @Override public ProtocolInfoAttributeName getName() { return NAME; } @Override public String getNameString() { return NAME.getName(); } @Override public String getValue() { DLNAOrgFlags flags = DLNAOrgFlags.getEffectiveFlags(this, MediaType.UNKNOWN); return String.format("%016x", flags.high) + String.format("%016x", flags.low); } /** * Returns a formatted {@code DLNA.ORG_FLAGS} attribute string for use in * {@code protocolInfo}. If this {@link DLNAOrgFlags} instance has an empty * value, or represents the implied default * {@code DLNA.ORG_FLAGS=00000000000000000000000000000000}, an empty string * is returned. * * @return The formatted {@code DLNA.ORG_FLAGS} attribute or an empty * {@link String}. */ @Override public String getAttributeString() { DLNAOrgFlags flags = DLNAOrgFlags.getEffectiveFlags(this, MediaType.UNKNOWN); if (flags.high == 0 || (flags.high & ~(3L << 55)) == 0) { return ""; } return NAME + "=" + String.format("%016x", flags.high) + String.format("%016x", flags.low); } /** * Gets the first 32 bits representing the {@code primary-flags}. * * @return The most significant {@link Integer}. */ public int getPrimaryValue() { return (int) (high >> 32); } /** * Gets the first 64 bits of the 128 bits in {@code DLNA.ORG_FLAGS}. * * @return The most significant {@link Long}. */ public long getHighValue() { return high; } /** * Gets the last 64 bits of the 128 bits in {@code DLNA.ORG_FLAGS}. * * @return The least significant {@link Long}. */ public long getLowValue() { return low; } @Override public String toString() { List<String> enabledFlags = new ArrayList<>(); if (isSenderPaced()) { enabledFlags.add("Sender Paced"); } if (isLimitedOperationsTimeBasedSeek()) { enabledFlags.add("Limited Operations: Time Based Seek"); } if (isLimitedOperationsByteBasedSeek()) { enabledFlags.add("Limited Operations: Byte Based Seek"); } if (isS0Increasing()) { enabledFlags.add("UCDAM s0 Increasing"); } if (isSNIncreasing()) { enabledFlags.add("UCDAM sN Increasing"); } if (isRtspPause()) { enabledFlags.add("RTSP Pause"); } if (isStreamingMode()) { enabledFlags.add("Streaming Mode"); } if (isInteractive()) { enabledFlags.add("Interactive"); } if (isBackgroundMode()) { enabledFlags.add("Background Mode"); } if (isHttpConnectionStalling()) { enabledFlags.add("HTTP Connection Stalling"); } if (isDLNA15()) { enabledFlags.add("DLNA 1.5"); } if (isLinkProtectedContent()) { enabledFlags.add("Link Protected Content"); } if (isCleartextByteFullDataSeek()) { enabledFlags.add("Cleartext Byte Full Data Seek"); } if (isCleartextLimitedDataSeek()) { enabledFlags.add("Cleartext Limited Data Seek"); } return NAME + " = " + enabledFlags.toString(); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + (int) (high ^ (high >>> 32)); result = prime * result + (int) (low ^ (low >>> 32)); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (!(obj instanceof DLNAOrgFlags)) { return false; } DLNAOrgFlags other = (DLNAOrgFlags) obj; if (high != other.high) { return false; } if (low != other.low) { return false; } return true; } /** * A builder class to build {@link DLNAOrgFlags} instances by setting * individual flags. */ public static class DLNAOrgFlagsBuilder { /** The first/most significant bits/flags. */ protected long high; /** The last/least significant bits/flags. */ protected long low; /** * Creates a new {@link DLNAOrgFlagsBuilder} instance which can be used * to {@link #build} an {@link DLNAOrgFlags} instance. * * @param isDLNA15 Sets bit 20: dlna-v1.5-flag (DLNA v1.5 versioning * flag). */ public DLNAOrgFlagsBuilder(boolean isDLNA15) { high = (isDLNA15 ? 1L : 0L) << 52; low = 0; } /** * Creates a {@link DLNAOrgFlags} instance from this {@link DLNAOrgFlagsBuilder}. * * @return The new {@link DLNAOrgFlags} instance. */ public DLNAOrgFlags build() { return high == 0 && low == 0 ? IMPLIED : new DLNAOrgFlags(high, low); } /** * Sets bit-31: sp-flag (Sender Paced flag). * * @return The {@link DLNAOrgFlagsBuilder} instance. */ public DLNAOrgFlagsBuilder senderPaced() { high |= ((high >> 52) & 1) == 1 ? 1L << 63 : 0; return this; } /** * Sets bit-30: lop-npt (Limited Operations flag: Time-Based Seek). * * @return The {@link DLNAOrgFlagsBuilder} instance. */ public DLNAOrgFlagsBuilder limitedOperationsTimeBasedSeek() { high |= ((high >> 52) & 1) == 1 ? 1L << 62 : 0; return this; } /** * Sets bit-29: lop-bytes (Limited Operations flag: Byte-Based Seek). * * @return The {@link DLNAOrgFlagsBuilder} instance. */ public DLNAOrgFlagsBuilder limitedOperationsByteBasedSeek() { high |= ((high >> 52) & 1) == 1 ? 1L << 61 : 0; return this; } /** * Sets bit-28: playcontainer-param (DLNA PlayContainer flag). * * @return The {@link DLNAOrgFlagsBuilder} instance. */ public DLNAOrgFlagsBuilder playContainer() { high |= ((high >> 52) & 1) == 1 ? 1L << 60 : 0; return this; } /** * Sets bit 27: s0-increasing (UCDAM s0 Increasing flag). * * @return The {@link DLNAOrgFlagsBuilder} instance. */ public DLNAOrgFlagsBuilder s0Increasing() { high |= ((high >> 52) & 1) == 1 ? 1L << 59 : 0; return this; } /** * Sets bit 26: sN-increasing (UCDAM sN Increasing flag). * * @return The {@link DLNAOrgFlagsBuilder} instance. */ public DLNAOrgFlagsBuilder sNIncreasing() { high |= ((high >> 52) & 1) == 1 ? 1L << 58 : 0; return this; } /** * Sets bit-25: rtsp-pause (Pause media operation support for RTP * Serving Endpoints). * * @return The {@link DLNAOrgFlagsBuilder} instance. */ public DLNAOrgFlagsBuilder rtspPause() { high |= ((high >> 52) & 1) == 1 ? 1L << 57 : 0; return this; } /** * Sets bit 24: tm-s (Streaming mode flag). * * @return The {@link DLNAOrgFlagsBuilder} instance. */ public DLNAOrgFlagsBuilder streamingMode() { high |= ((high >> 52) & 1) == 1 ? 1L << 56 : 0; return this; } /** * Sets bit 23: tm-i (Interactive mode flag). * * @return The {@link DLNAOrgFlagsBuilder} instance. */ public DLNAOrgFlagsBuilder interactive() { high |= ((high >> 52) & 1) == 1 ? 1L << 55 : 0; return this; } /** * Sets bit 22: tm-b (Background mode flag). * * @return The {@link DLNAOrgFlagsBuilder} instance. */ public DLNAOrgFlagsBuilder backgroundMode() { high |= ((high >> 52) & 1) == 1 ? 1L << 54 : 0; return this; } /** * Sets bit 21: http-stalling (HTTP Connection Stalling flag). * * @return The {@link DLNAOrgFlagsBuilder} instance. */ public DLNAOrgFlagsBuilder httpConnectionStalling() { high |= 1L << 53; return this; } /** * Sets bit 16: LP-flag (Link Protected content flag). * * @return The {@link DLNAOrgFlagsBuilder} instance. */ public DLNAOrgFlagsBuilder linkProtectedContent() { high |= 1L << 48; return this; } /** * Sets bit 15: cleartextbyteseek-full flag (Cleartext Byte Full Data * Seek flag). * * @return The {@link DLNAOrgFlagsBuilder} instance. */ public DLNAOrgFlagsBuilder cleartextByteFullDataSeek() { high |= 1L << 47; high |= 1L << 48; return this; } /** * Sets bit 14: lop-cleartextbytes flag (Cleartext Limited Data Seek * flag). * * @return The {@link DLNAOrgFlagsBuilder} instance. */ public DLNAOrgFlagsBuilder cleartextLimitedDataSeek() { high |= ((high >> 52) & 1) == 1 ? 1L << 46 : 0; if (((high >> 46) & 1) == 1) { high |= 1L << 48; } return this; } } }