/* * 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 static org.apache.commons.lang3.StringUtils.isBlank; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.fourthline.cling.support.model.Protocol; import net.pms.dlna.protocolinfo.ProtocolInfoAttributeName.KnownProtocolInfoAttributeName; import net.pms.util.ParseException; /** * This class represents {@code DLNA.ORG_OP} attributes. This can be used for * both DLNA and non-DLNA content. * * @author Nadahar */ public abstract class DLNAOrgOperations implements ProtocolInfoAttribute { private static final long serialVersionUID = 1L; /** The static attribute name always used for this class */ public static final ProtocolInfoAttributeName NAME = KnownProtocolInfoAttributeName.DLNA_ORG_OP; /** * The static factory singleton instance used to retrieve * {@link DLNAOrgOperations} instances. */ public static final DLNAOrgOperationsFactory FACTORY = new DLNAOrgOperationsFactory(); /** The state */ protected final byte state; /** * For internal use only, use {@link #FACTORY} to get instances. * * @param flagA flag A. * @param flagB flag B. */ protected DLNAOrgOperations(boolean flagA, boolean flagB) { state = packFlags(flagA, flagB); } @Override public ProtocolInfoAttributeName getName() { return NAME; } @Override public String getNameString() { return NAME.getName(); } @Override public String getValue() { return state > 0 ? ((state & 2) > 0 ? "1" : "0") + ((state & 1) > 0 ? "1" : "0") : ""; } @Override public String getAttributeString() { String result = getValue(); return isBlank(result) ? "" : NAME + "=" + result; } /** * Validates if this {@link DLNAOrgOperations} instance can be used together * with a given {@link DLNAOrgFlags} instance without breaking DLNA rules. * * @param flags the {@link DLNAOrgFlags} instance to validate against. * @return {@code true} if validation succeeded or {@code flags} was * {@code null}, {@code false} otherwise. */ public boolean validate(DLNAOrgFlags flags) { // Assuming that "null" means no FLAGS parameter if (flags != null && state > 0) { if (flags.isS0Increasing() || flags.isLimitedOperationsTimeBasedSeek() || flags.isLimitedOperationsByteBasedSeek()) { return false; } } return true; } /** * Validates if this {@link DLNAOrgOperations} instance can be used in a * given {@link ProtocolInfo} instance without breaking DLNA rules. * <p> * <b>Note:</b> Currently {@code res@size} and {@code res@duration} isn't * implemented in {@link ProtocolInfo}, which means that the related * requirements can't be verified. * * @param protocolInfo the {@link ProtocolInfo} instance to verify for. * @return {@code true} if the validation succeeded, false otherwise. */ public boolean validate(ProtocolInfo protocolInfo) { return validate(protocolInfo.getFlags()); // XXX When implemented in ProtocolInfo, verify that Size is given if B // flag is true // XXX When implemented in ProtocolInfo, verify that Duration is given // if A flag is true } /** * For internal use only, packs the flags into bits in a {@code byte}. * * @param flagA flag A. * @param flagB flag B. * @return A byte with the corresponding bits set. */ protected static byte packFlags(boolean flagA, boolean flagB) { return (byte) ((flagA ? 2 : 0) | (flagB ? 1 : 0)); } /** * A factory for retrieving static {@link DLNAOrgOperations} instances. */ public static class DLNAOrgOperationsFactory { /** Internal regex pattern for validation of strings for parsing */ protected static final Pattern STRING_PATTERN = Pattern.compile("^\\s*([01])([01])\\s*$"); /** * For internal use only, use {@link DLNAOrgOperations#FACTORY} to get * the singleton instance. */ protected DLNAOrgOperationsFactory() { } /** * Retrieves the correct static {@link DLNAOrgOperationsHTTP} or * {@link DLNAOrgOperationsRTP} instance by parsing a * {@code DLNA.ORG_OP} string value. Only the form {@code "nn"} where * {@code n} is either {@code 0} or {@code 1} is valid. * <p> * If {@code protocol} is anything but {@link Protocol#HTTP_GET} or * {@link Protocol#RTSP_RTP_UDP}, {@code null} is returned. If * {@code value} is {@code null} or blank, it is parsed as if it were * {@code "00"}. * * @param protocol the {@link Protocol} for which this * {@link DLNAOrgOperations} instance applies. * @param value the {@code DLNA.ORG_OP} string value. * @return The corresponding static {@link DLNAOrgOperationsHTTP} or * {@link DLNAOrgOperationsRTP} instance, or {@code null} if * none could be found. * @throws ParseException if {@code value} can't be parsed. */ public DLNAOrgOperations getOperations(Protocol protocol, String value) throws ParseException { if (protocol == null) { return null; } if (isBlank(value)) { return getOperations(protocol, false, false); } Matcher matcher = STRING_PATTERN.matcher(value); if (!matcher.find()) { throw new ParseException("Unable to parse \"" + value + "\" as a DLNA operations value"); } return getOperations(protocol, "1".equals(matcher.group(1)), "1".equals(matcher.group(2))); } /** * Retrieves the correct static {@link DLNAOrgOperationsHTTP} or * {@link DLNAOrgOperationsRTP} instance based on {@code flags}. * {@link DLNAOrgOperationsFlags#TIME_SEEK} is ignored for * {@link Protocol#RTSP_RTP_UDP}. * <p> * If {@code protocol} is anything but {@link Protocol#HTTP_GET} or * {@link Protocol#RTSP_RTP_UDP}, {@code null} is returned. If * {@code flags} is {@code null}, it is parsed as if it were empty. * * @param protocol the {@link Protocol} for which this * {@link DLNAOrgOperations} instance applies. * @param flags the {@link DLNAOrgOperationsFlags} to set. * @return The corresponding static {@link DLNAOrgOperationsHTTP} or * {@link DLNAOrgOperationsRTP} instance, or {@code null} if * none could be found. */ public DLNAOrgOperations getOperations(Protocol protocol, DLNAOrgOperationsFlags... flags) { boolean flagA = false; boolean flagB = false; if (flags != null && protocol == Protocol.HTTP_GET) { for (DLNAOrgOperationsFlags flag : flags) { flagA = flag == DLNAOrgOperationsFlags.TIME_SEEK; flagB = flag == DLNAOrgOperationsFlags.HEADER; } } else if (flags != null && protocol == Protocol.RTSP_RTP_UDP) { for (DLNAOrgOperationsFlags flag : flags) { flagA = flag == DLNAOrgOperationsFlags.HEADER; } } else { return null; } return getOperations(protocol, flagA, flagB); } /** * Retrieves the correct static {@link DLNAOrgOperationsHTTP} or * {@link DLNAOrgOperationsRTP} instance based on {@code flagA} and * {@code flagB}. {@code flagB} is ignored for * {@link Protocol#RTSP_RTP_UDP}. * <p> * If {@code protocol} is anything but {@link Protocol#HTTP_GET} or * {@link Protocol#RTSP_RTP_UDP}, {@code null} is returned. * * @param protocol the {@link Protocol} for which this * {@link DLNAOrgOperations} instance applies. * @param flagA the first flag. * @param flagB the second flag. * @return The corresponding static {@link DLNAOrgOperationsHTTP} or * {@link DLNAOrgOperationsRTP} instance, or {@code null} if * none could be found. */ public DLNAOrgOperations getOperations(Protocol protocol, boolean flagA, boolean flagB) { if (protocol == Protocol.HTTP_GET) { switch (packFlags(flagA, flagB)) { case 1: return DLNAOrgOperationsHTTP.HTTP_HEADER; case 2: return DLNAOrgOperationsHTTP.HTTP_TIME_SEEK; case 3: return DLNAOrgOperationsHTTP.HTTP_BOTH; default: return DLNAOrgOperationsHTTP.NONE; } } else if (protocol == Protocol.RTSP_RTP_UDP) { switch (packFlags(flagA, flagB)) { case 2: return DLNAOrgOperationsRTP.RTP_HEADER; default: return DLNAOrgOperationsRTP.NONE; } } return null; } } /** * This class represents {@code DLNA.ORG_OP} attributes for HTTP transports. * This can be used for both DLNA and non-DLNA content. * * @author Nadahar */ public static class DLNAOrgOperationsHTTP extends DLNAOrgOperations { private static final long serialVersionUID = 1L; /** Neither flag is set, corresponds to {@code "00"} */ public static final DLNAOrgOperations NONE = new DLNAOrgOperationsHTTP(false, false); /** * {@code TimeSeekRange.dlna.org} HTTP header support is set, corresponds * to {@code "10"} */ public static final DLNAOrgOperations HTTP_TIME_SEEK = new DLNAOrgOperationsHTTP(true, false); /** Range HTTP header support is set, corresponds to {@code "01"} */ public static final DLNAOrgOperations HTTP_HEADER = new DLNAOrgOperationsHTTP(false, true); /** * Both {@code TimeSeekRange.dlna.org} HTTP header and Range HTTP header * support is set, corresponds to {@code "11"} */ public static final DLNAOrgOperations HTTP_BOTH = new DLNAOrgOperationsHTTP(true, true); /** * For internal use only, use that static instances or * {@link DLNAOrgOperations#FACTORY} to retrieve instances. * * @see #NONE * @see #HTTP_TIME_SEEK * @see #HTTP_HEADER * @see #HTTP_BOTH * * @param timeSeekRange the {@code TimeSeekRange.dlna.org} HTTP header * flag. * @param rangeHttpHeader the Range HTTP header flag. */ protected DLNAOrgOperationsHTTP(boolean timeSeekRange, boolean rangeHttpHeader) { super(timeSeekRange, rangeHttpHeader); } } /** * This class represents {@code DLNA.ORG_OP} attributes for RTP transports. * This can be used for both DLNA and non-DLNA content. * * @author Nadahar */ public static class DLNAOrgOperationsRTP extends DLNAOrgOperations { private static final long serialVersionUID = 1L; /** Neither flag is set, corresponds to {@code "00"} */ public static final DLNAOrgOperations NONE = new DLNAOrgOperationsRTP(false); /** Range header support is set, corresponds to {@code "10"} */ public static final DLNAOrgOperations RTP_HEADER = new DLNAOrgOperationsRTP(true); /** * For internal use only, use that static instances or * {@link DLNAOrgOperations#FACTORY} to retrieve instances. * * @see #NONE * @see #RTP_HEADER * * @param rangeHeader the Range header flag. */ protected DLNAOrgOperationsRTP(boolean rangeHeader) { super(rangeHeader, false); } } /** * This {@code enum} represents the individual {@code DLNA.ORG_OP} flags. */ public enum DLNAOrgOperationsFlags { /** {@code TimeSeekRange.dlna.org} HTTP header */ TIME_SEEK, /** Range HTTP header or Range header depending on the {@link Protocol} */ HEADER; } }