/// <summary>
        /// Creates a programmer-friendly object for using a device
        /// that happens implement a MediaServer.
        /// </summary>
        /// <param name="device"></param>
        public CpMediaServer(UPnPDevice device)
        {
            UPnPService sCM = device.GetServices(CpConnectionManager.SERVICE_NAME)[0];
            UPnPService sCD = device.GetServices(CpContentDirectory.SERVICE_NAME)[0];

            CpConnectionManager cpCM = new CpConnectionManager(sCM);
            CpContentDirectory cpCD = new CpContentDirectory(sCD);

            UDN = device.UniqueDeviceName;

            if (
                (cpCD.HasAction_GetSearchCapabilities == false) ||
                (cpCD.HasAction_GetSortCapabilities == false) ||
                (cpCD.HasAction_GetSystemUpdateID == false) ||
                (cpCD.HasAction_Browse == false)
                )
            {
                throw new UPnPCustomException(0, "MediaServer does not implement minimum features.");
            }

            this.m_ConnectionManager = cpCM;
            this.m_ContentDirectory = cpCD;

            //create a virtualized root container with the desired settings
            m_Root = new CpRootContainer(this);
            m_Root.UDN = device.UniqueDeviceName;
        }
 /// <summary>
 /// Allows the programmer to request that a transfer stop.
 /// </summary>
 /// <param name="TransferID">
 /// The transfer to stop, identified by its ID,
 /// which can be obtained from the CDS TransferIDs comma-separated value list
 /// state variable.
 /// </param>
 /// <param name="Tag">
 /// Miscellaneous, user-provided object for tracking this 
 /// asynchronous call. Can be used as a means to pass a 
 /// user-defined "state object" at invoke-time so that 
 /// the executed callback during results-processing can be
 /// aware of the component's state at the time of the call.
 /// </param>
 /// <param name="callback">the callback to execute when results become available</param>
 public void RequestStopTransferResource(uint TransferID, object Tag, CpContentDirectory.Delegate_OnResult_StopTransferResource callback)
 {
     this.ContentDirectory.StopTransferResource(TransferID, Tag, callback);
 }
        /// <summary>
        /// Processes the results of Browse requests.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="ObjectID"></param>
        /// <param name="BrowseFlag"></param>
        /// <param name="Filter"></param>
        /// <param name="StartingIndex"></param>
        /// <param name="RequestedCount"></param>
        /// <param name="SortCriteria"></param>
        /// <param name="Result"></param>
        /// <param name="NumberReturned"></param>
        /// <param name="TotalMatches"></param>
        /// <param name="UpdateID"></param>
        /// <param name="e"></param>
        /// <param name="_Tag"></param>
        private void OnBrowseDone(CpContentDirectory sender, System.String ObjectID, OpenSource.UPnP.AV.CpContentDirectory.Enum_A_ARG_TYPE_BrowseFlag BrowseFlag, System.String Filter, System.UInt32 StartingIndex, System.UInt32 RequestedCount, System.String SortCriteria, System.String Result, System.UInt32 NumberReturned, System.UInt32 TotalMatches, System.UInt32 UpdateID, UPnPInvokeException e, object _Tag)
        {
            _RequestState state = (_RequestState) _Tag;
            if (e != null)
            {
                if (state.Callback_Browse1 != null)
                {
                    state.Callback_Browse1(this, ObjectID, BrowseFlag, Filter, StartingIndex, RequestedCount, SortCriteria, e, null, state.Tag, null, NumberReturned, TotalMatches, UpdateID);
                }
                else if (state.Callback_Browse2 != null)
                {
                    state.Callback_Browse2(this, e, null, state.Tag, null, NumberReturned, TotalMatches, UpdateID);
                }
            }
            else
            {
                ArrayList al = null;
                try
                {
                    al = CpMediaBuilder.BuildMediaBranches(Result);
                }
                catch (Exception parseError)
                {
                    if (state.Callback_Browse1 != null)
                    {
                        state.Callback_Browse1(this, ObjectID, BrowseFlag, Filter, StartingIndex, RequestedCount, SortCriteria, e, parseError, state.Tag, null, NumberReturned, TotalMatches, UpdateID);
                    }
                    else if (state.Callback_Browse2 != null)
                    {
                        state.Callback_Browse2(this, e, parseError, state.Tag, null, NumberReturned, TotalMatches, UpdateID);
                    }
                    al = null;
                }

                if (al != null)
                {
                    if (state.Callback_Browse1 != null)
                    {
                        state.Callback_Browse1(this, ObjectID, BrowseFlag, Filter, StartingIndex, RequestedCount, SortCriteria, e, null, state.Tag, (IUPnPMedia[]) al.ToArray(typeof(IUPnPMedia)), NumberReturned, TotalMatches, UpdateID);
                    }
                    else if (state.Callback_Browse2 != null)
                    {
                        state.Callback_Browse2(this, e, null, state.Tag, (IUPnPMedia[]) al.ToArray(typeof(IUPnPMedia)), NumberReturned, TotalMatches, UpdateID);
                    }
                }
            }
        }
 /// <summary>
 /// Requests the remote media server to provide its SystemUpdateID value.
 /// </summary>
 /// <param name="Tag">
 /// Miscellaneous, user-provided object for tracking this 
 /// asynchronous call. Can be used as a means to pass a 
 /// user-defined "state object" at invoke-time so that 
 /// the executed callback during results-processing can be
 /// aware of the component's state at the time of the call.
 /// </param>
 /// <param name="callback">the callback to execute when results become available</param>
 public void RequestGetSystemUpdateID(object Tag, CpContentDirectory.Delegate_OnResult_GetSystemUpdateID callback)
 {
     this.ContentDirectory.GetSystemUpdateID(Tag, callback);
 }
 /// <summary>
 /// Allows the programmer to query the progress of this transfer object.
 /// </summary>
 /// <param name="TransferID">
 /// The transfer to obtain information on, identified by its ID,
 /// which can be obtained from the CDS TransferIDs comma-separated value list
 /// state variable.
 /// </param>
 /// <param name="Tag">
 /// Miscellaneous, user-provided object for tracking this 
 /// asynchronous call. Can be used as a means to pass a 
 /// user-defined "state object" at invoke-time so that 
 /// the executed callback during results-processing can be
 /// aware of the component's state at the time of the call.
 /// </param>
 /// <param name="callback">the callback to execute when results become available</param>
 public void RequestGetTransferProgress(uint TransferID, object Tag, CpContentDirectory.Delegate_OnResult_GetTransferProgress callback)
 {
     this.ContentDirectory.GetTransferProgress(TransferID, Tag, callback);
 }
        /// <summary>
        /// Executes when the ContentDirectory events for the first time.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="newVal"></param>
        private void Sink_OnCdEvented(CpContentDirectory sender, UInt32 newVal)
        {
            sender.OnStateVariable_SystemUpdateID -= new CpContentDirectory.StateVariableModifiedHandler_SystemUpdateID(this.Sink_OnCdEvented);
            string udn = sender.GetUPnPService().ParentDevice.UniqueDeviceName;

            lock (LockHashes)
            {
                InitStatus status = (InitStatus) UdnToInitStatus[udn];
                if (status != null)
                {
                    status.ZeroMeansDone--;
                    status.EventedCD = true;
                    this.ProcessInitStatusChange(udn);
                }
            }
        }
        public static CdsBrowseSearchResults GetContainerMetadataAndValidate(string containerID, CpContentDirectory cds, CdsSubTest test, CdsSubTestArgument arg, CdsResult_BrowseAll details, out IUPnPMedia metadata)
        {
            BrowseInput input = new BrowseInput();
            input.ObjectID = containerID;
            input.BrowseFlag = CpContentDirectory.Enum_A_ARG_TYPE_BrowseFlag.BROWSEMETADATA;
            input.Filter = "*";
            input.StartingIndex = 0;
            input.RequestedCount = 0;
            input.SortCriteria = "";
            metadata = null;

            test.SetExpectedTestingTime ((++details.ExpectedTotalBrowseRequests) * 30);
            arg.ActiveTests.UpdateTimeAndProgress( details.TotalBrowseRequests * 30);

            CdsBrowseSearchResults results = Browse(input, test, arg, cds, details);

            test.SetExpectedTestingTime ((details.ExpectedTotalBrowseRequests) * 30);
            arg.ActiveTests.UpdateTimeAndProgress( (details.TotalBrowseRequests) * 30);

            if (results.InvokeError == null)
            {
                if (results.NumberReturned != 1)
                {
                    //results.SetError(UPnPTestStates.Warn);
                    string msg = "\"" + test.Name + "\": " + input.PrintBrowseParams() + " returned NumberReturned=" +results.NumberReturned+ " when it should logically be 1. This output parameter is not really useful for BrowseMetadata so this logic error does not prevent towards certification, but it should be fixed.";
                    //arg._TestGroup.AddEvent(LogImportance.Low, test.Name, msg);
                    results.ResultErrors.Add( new Exception(msg) );
                }

                if (results.TotalMatches != 1)
                {
                    //results.SetError(UPnPTestStates.Warn);
                    string msg = "\"" + test.Name + "\": " + input.PrintBrowseParams() + " returned TotalMatches=" +results.TotalMatches+ " when it should logically be 1. This output parameter is not really useful BrowseMetadata so this logic error does not prevent towards certification, but it should be fixed.";
                    //arg._TestGroup.AddEvent(LogImportance.Low, test.Name, msg);
                    results.ResultErrors.Add( new Exception(msg) );
                }

                if (results.MediaObjects != null)
                {
                    if (results.MediaObjects.Count == 1)
                    {
                        metadata = results.MediaObjects[0] as IUPnPMedia;

                        if (metadata == null)
                        {
                            throw new TestException(test.Name + " has a TEST LOGIC ERROR. Browse returned without errors but the container's metadata is not stored in an IUPnPMedia object. The offending type is " + results.MediaObjects[0].GetType().ToString(), results.MediaObjects[0]);
                        }

                        IMediaContainer imc = metadata as IMediaContainer;

                        //
                        // check metadata
                        //

                        if (imc == null)
                        {
                            //results.SetError(UPnPTestStates.Failed);
                            string msg = "\"" + test.Name + "\": " + input.PrintBrowseParams() + " returned a DIDL-Lite media object but it was not declared with a \"container\" element.";
                            //arg._TestGroup.AddEvent(LogImportance.Critical, test.Name, msg);
                            results.ResultErrors.Add( new Exception(msg) );
                        }
                        else
                        {
                        }

                        if (metadata.ID != containerID)
                        {
                            //results.SetError(UPnPTestStates.Failed);
                            string msg = "\"" + test.Name + "\": " + input.PrintBrowseParams() + " returned a DIDL-Lite media object with ID=\"" +metadata.ID+ "\" when it should be \"" +containerID+"\" as indicated by the input parameter.";
                            //arg._TestGroup.AddEvent(LogImportance.Critical, test.Name, msg);
                            results.ResultErrors.Add( new Exception(msg) );
                        }

                        if (containerID == "0")
                        {
                            if (metadata.ParentID != "-1")
                            {
                                //results.SetError(UPnPTestStates.Failed);
                                string msg = "\"" + test.Name + "\": " + input.PrintBrowseParams() + " returned a DIDL-Lite media object with parentID=\"" +metadata.ID+ "\" when it must be \"-1\" because the container is the root container.";
                                //arg._TestGroup.AddEvent(LogImportance.Critical, test.Name, msg);
                                results.ResultErrors.Add( new Exception(msg) );
                            }

                            // no need to check parentID values for other containers because
                            // they get checked when getting the children for this container.
                        }

                        if (results.WorstError < UPnPTestStates.Failed)
                        {
                            //string msg = "\"" + test.Name + "\": " + input.PrintBrowseParams() + " succeeded.";
                            //arg._TestGroup.AddEvent(LogImportance.Remark, test.Name, msg);
                        }
                    }
                    else
                    {
                        //results.SetError(UPnPTestStates.Failed);
                        //string msg = "\"" + test.Name + "\": " + input.PrintBrowseParams() + " did not yield exactly one CDS-compliant media object in its result. Instantiated a total of " +results.MediaObjects.Count+ " media objects.";
                        //arg._TestGroup.AddEvent(LogImportance.Critical, test.Name, msg);
                    }
                }
                else
                {
                    //throw new TestException(test.Name + " has a TEST LOGIC ERROR. Browse returned without errors but no media objects were instantiated.", null);
                }
            }

            if ((results.InvokeError != null) || (results.ResultErrors.Count > 0))
            {
                StringBuilder msg = new StringBuilder();
                results.SetError(UPnPTestStates.Failed);

                msg.AppendFormat("\"{0}\": {1} did not yield exactly one CDS-compliant media object in its result. Instantiated a total of {2} media objects.", test.Name, input.PrintBrowseParams(), results.MediaObjects.Count);
                msg.AppendFormat("\r\nAdditional Information:");
                if (results.InvokeError != null)
                {
                    msg.AppendFormat("\r\n{0}", results.InvokeError.Message);
                }

                foreach (Exception e in results.ResultErrors)
                {
                    msg.AppendFormat("\r\n{0}", e.Message);
                }

                arg._TestGroup.AddEvent(LogImportance.Critical, test.Name, msg.ToString());
            }

            return results;
        }
 public override void RequestDestroyObject(DestroyObjectRequest request, CpContentDirectory.Delegate_OnResult_DestroyObject callback)
 {
 }
 public override void RequestUpdateObject(UpdateObjectRequest request, CpContentDirectory.Delegate_OnResult_UpdateObject callback)
 {
 }
        /// <summary>
        /// Tests filters for metadata.
        /// </summary>
        /// <param name="testThis"></param>
        /// <param name="test"></param>
        /// <param name="arg"></param>
        /// <param name="cds"></param>
        /// <param name="stats"></param>
        /// <returns></returns>
        public static CdsBrowseSearchResults TestFiltersBrowseMetadata(IUPnPMedia testThis, CpContentDirectory.Enum_A_ARG_TYPE_BrowseFlag browseFlag, Cds_BrowseFilter test, CdsSubTestArgument arg, CpContentDirectory cds, CdsResult_BrowseFilter stats)
        {
            CdsBrowseSearchResults r = new CdsBrowseSearchResults();

            try
            {
                BrowseInput input = new BrowseInput();
                input.BrowseFlag = browseFlag;
                if (input.BrowseFlag == CpContentDirectory.Enum_A_ARG_TYPE_BrowseFlag.BROWSEMETADATA)
                {
                    input.ObjectID = testThis.ID;
                }
                else
                {
                    input.ObjectID = testThis.ParentID;
                }
                input.SortCriteria = "";

                input.StartingIndex = 0;
                input.RequestedCount = 0;

                //assume the test will pass, but worsen the result when we find errors
                r.SetError(UPnPTestStates.Pass);

                ArrayList propNames = new ArrayList((ICollection)testThis.Properties.PropertyNames);

                // add property names related to resources
                propNames.Add(T[_DIDL.Res]);
                foreach (string str in MediaResource.GetPossibleAttributes())
                {
                    propNames.Add(T[_DIDL.Res] + "@" + str);
                    propNames.Add("@" + str);
                }
                propNames.Add(T[_DIDL.Desc]);

                if (propNames.Count != stats.NumberOfProperties)
                {
                    throw new TestException("Number of calculated metadata properties (" + propNames.Count + ") doesn't match number of actual metadata properties (" + propNames.Count + ") for testing.", stats);
                }

                test._Details.Filters = propNames;

                int inc = propNames.Count / 4;
                if (inc == 0)
                {
                    inc = 1;
                }

                for (int numProps = 0; numProps < propNames.Count; numProps++)
                {
                    for (int iProp = 0; iProp < propNames.Count; iProp += inc)
                    {
                        IList filterSettings = GetFilterSettings(propNames, numProps, iProp, iProp);

                        input.Filter = GetCSVString(filterSettings);

                        CdsBrowseSearchResults br;

                        arg.ActiveTests.UpdateTimeAndProgress(90 * stats.TotalBrowseRequests);

                        br = Cds_BrowseAll.Browse(input, test, arg, cds, stats);

                        if (br.WorstError >= UPnPTestStates.Failed)
                        {
                            StringBuilder teMsg = new StringBuilder();

                            teMsg.AppendFormat("\"{0}\" is terminating early because {1} returned with an error or had problems with the DIDL-Lite.", test.Name, input.PrintBrowseParams());
                            if (br.Result != "")
                            {
                                teMsg.AppendFormat(" Returned DIDL=\"{0}\".", br.Result);
                            }
                            throw new TerminateEarly(teMsg.ToString());
                        }
                        //							if (br.InvokeError != null)
                        //							{
                        //								throw new TerminateEarly("\"" + test.Name + "\" is terminating early because " +input.PrintBrowseParams()+ " returned with an error" + br.InvokeError.Message, br.InvokeError);
                        //							}

                        if (input.BrowseFlag == CpContentDirectory.Enum_A_ARG_TYPE_BrowseFlag.BROWSEMETADATA)
                        {
                            if (br.MediaObjects.Count != 1)
                            {
                                throw new TerminateEarly("\"" + test.Name + "\" is terminating early because " + input.PrintBrowseParams() + " did not return with exactly one media object in its DIDL-Lite response. DIDL-Lite => " + br.Result);
                            }

                            IUPnPMedia mo = (IUPnPMedia)br.MediaObjects[0];

                            CheckReturnedMetadata(mo, testThis, input, filterSettings, br, test);
                            arg.TestGroup.AddEvent(LogImportance.Remark, test.Name, "\"" + test.Name + "\" did a " + input.PrintBrowseParams() + " and encountered no errors in the results.");
                        }
                        else
                        {
                            IList childList = testThis.Parent.CompleteList;

                            foreach (IUPnPMedia gotChild in br.MediaObjects)
                            {

                                IUPnPMedia testAgainstChild = null;
                                foreach (IUPnPMedia child in childList)
                                {
                                    if (child.ID == gotChild.ID)
                                    {
                                        testAgainstChild = child;
                                        break;
                                    }
                                }
                                if (testAgainstChild == null)
                                {
                                    throw new TerminateEarly("\"" + test.Name + "\" is terminating early because " + input.PrintBrowseParams() + " returned media object with ID=\"" + gotChild.ID + "\", which was not a child object of containerID=\"" + input.ObjectID + "\" during a prerequisite test.");
                                }
                                CheckReturnedMetadata(gotChild, testAgainstChild, input, filterSettings, br, test);
                                arg.TestGroup.AddEvent(LogImportance.Remark, test.Name, "\"" + test.Name + "\" did a " + input.PrintBrowseParams() + " and encountered no errors in the results.");
                            }
                        }
                    }
                }

            }
            catch (TerminateEarly te)
            {
                string reason = "\"" + test.Name + "\" terminating early. Reason => " + te.Message;
                arg._TestGroup.AddEvent(LogImportance.Critical, test.Name, reason);

                r.SetError(UPnPTestStates.Failed);
                return r;
            }

            if (r.WorstError > UPnPTestStates.Warn)
            {
                throw new TestException("\"" + test.Name + "\" should not reach this code if the result is worse than " + UPnPTestStates.Warn.ToString() + ".", null);
            }

            return r;
        }
 /// <summary>
 /// No-op.
 /// </summary>
 /// <param name="br"></param>
 /// <param name="callback"></param>
 public override void RequestBrowse(BrowseRequest br, CpContentDirectory.Delegate_OnResult_Browse callback)
 {
 }
        /// <summary>
        /// Tests the container with BrowseDirectChildren and different ranges.
        /// </summary>
        /// <param name="c"></param>
        /// <param name="test"></param>
        /// <param name="arg"></param>
        /// <param name="cds"></param>
        /// <param name="stats"></param>
        /// <returns></returns>
        public static CdsBrowseSearchResults TestContainerRanges(IMediaContainer c, CdsSubTest test, CdsSubTestArgument arg, CpContentDirectory cds, CdsResult_BrowseStats stats)
        {
            CdsBrowseSearchResults r = new CdsBrowseSearchResults();

            try
            {
                BrowseInput input = new BrowseInput();
                input.BrowseFlag = CpContentDirectory.Enum_A_ARG_TYPE_BrowseFlag.BROWSEDIRECTCHILDREN;
                input.Filter = "*";
                input.ObjectID = c.ID;
                input.SortCriteria = "";

                input.StartingIndex = 0;
                input.RequestedCount = 0;

                if (c.CompleteList.Count != c.ChildCount)
                {
                    throw new TestException("\""+test.Name+"\" has c.CompleteList.Count=" +c.CompleteList.Count+ " but c.ChildCount=" +c.ChildCount+ ".", c);
                }

                //assume the test will pass, but worsen the result when we find errors
                r.SetError(UPnPTestStates.Pass);

                // test starting index = 0 to c.ChildCount+1
                // test requested count = 0 to c.ChildCoun+1
                for (uint start = 0; start < c.ChildCount+1; start++)
                {
                    for (uint requested = 0; requested < c.ChildCount+1; requested++)
                    {
                        uint start_requested = start+requested;
                        bool doBrowse = false;
                        int tenthCount = c.ChildCount / 10;
                        int quarterCount = c.ChildCount / 4;
                        if (quarterCount == 0)
                        {
                            quarterCount = 1;
                        }
                        if (tenthCount == 0)
                        {
                            tenthCount = 1;
                        }

                        if (start < LIMIT)
                        {
                            if (
                                (requested == 0) ||
                                (start_requested > c.ChildCount - LIMIT)
                                )
                            {
                                doBrowse = true;
                            }

                        }
                        else if (start >= c.ChildCount - LIMIT)
                        {
                            if (start_requested < c.ChildCount+LIMIT)
                            {
                                doBrowse = true;
                            }
                        }
                        else if ((start % quarterCount == 0) && (requested % tenthCount == 0))
                        {
                            doBrowse = true;
                        }

                        if (doBrowse == false)
                        {
                            //stats.TotalBrowseRequests++;
                            //arg.ActiveTests.UpdateTimeAndProgress(stats.TotalBrowseRequests * 300);
                        }
                        else
                        {
                            arg.ActiveTests.UpdateTimeAndProgress(stats.TotalBrowseRequests * 300);

                            input.StartingIndex = start;
                            input.RequestedCount = requested;
                            CdsBrowseSearchResults br;

                            br = Cds_BrowseAll.Browse(input, test, arg, cds, stats);

                            if (br.WorstError >= UPnPTestStates.Failed)
                            {
                                if (
                                    (input.StartingIndex < c.ChildCount)
                                    )
                                {
                                    throw new TerminateEarly("\"" + test.Name + "\" is terminating early because " +input.PrintBrowseParams()+ " returned with an error or had problems with the DIDL-Lite.");
                                }
                                else
                                {
                                    arg._TestGroup.AddEvent(LogImportance.High, test.Name, "\"" + test.Name + "\": Warning: " +input.PrintBrowseParams()+ " returned an error.");
                                    r.SetError(UPnPTestStates.Warn);
                                }
                            }

                            // check return values

                            if (br.NumberReturned != br.MediaObjects.Count)
                            {
                                throw new TerminateEarly("\""+test.Name+"\" did a "+ input.PrintBrowseParams() + " and the number of media objects instantiated from the DIDL-Lite (instantiated=" +br.MediaObjects.Count+ ") does not match NumberReturned=" +br.NumberReturned+".");
                            }

                            long expectedReturned;

                            if (input.StartingIndex == 0)
                            {
                                if (input.RequestedCount == 0)
                                {
                                    expectedReturned = c.ChildCount;
                                }
                                else if (input.RequestedCount > c.ChildCount)
                                {
                                    expectedReturned = c.ChildCount;
                                }
                                else if (input.RequestedCount <= c.ChildCount)
                                {
                                    expectedReturned = input.RequestedCount;
                                }
                                else
                                {
                                    throw new TestException("\""+test.Name+"\" should not reach here.", null);
                                }
                            }
                            else
                            {
                                if (input.RequestedCount == 0)
                                {
                                    expectedReturned = c.ChildCount - input.StartingIndex;
                                }
                                else
                                {
                                    expectedReturned = c.ChildCount - input.StartingIndex;

                                    if (expectedReturned > input.RequestedCount)
                                    {
                                        expectedReturned = input.RequestedCount;
                                    }
                                }
                            }

                            if ((expectedReturned < 0) || (expectedReturned > c.ChildCount))
                            {
                                throw new TestException("\""+test.Name+"\" did a " + input.PrintBrowseParams() + " and the expected number of media objects is invalid=" + expectedReturned + ".", br);
                            }

                            if (br.NumberReturned != expectedReturned)
                            {
                                throw new TerminateEarly("\""+test.Name+"\" did a "+ input.PrintBrowseParams() + " and NumberReturned=" +br.NumberReturned+ " but test expects " +expectedReturned+ " child objects according to results from a prerequisite test.");
                            }

                            if (br.TotalMatches != c.ChildCount)
                            {
                                throw new TerminateEarly("\""+test.Name+"\" did a "+ input.PrintBrowseParams() + " and TotalMatches=" +br.TotalMatches+ " but test found " +c.ChildCount+ " child objects in a prerequisite test.");
                            }

                            uint cUpdateID = 0;

                            try
                            {
                                cUpdateID = (uint) c.Tag;
                            }
                            catch (Exception ce)
                            {
                                throw new TestException("\""+test.Name+"\" could not cast c.Tag into a uint value", null, ce);
                            }

                            if (br.UpdateID != cUpdateID)
                            {
                                throw new TerminateEarly("\""+test.Name+"\" did a "+ input.PrintBrowseParams() + " and UpdateID=" +br.UpdateID+ " but test expected=" +cUpdateID+ " as found in a prerequisite test.");
                            }

                            arg.TestGroup.AddEvent(LogImportance.Remark, test.Name, "\""+test.Name+"\" did a " +input.PrintBrowseParams()+ " and encountered no errors in the results.");
                            arg.ActiveTests.UpdateTimeAndProgress(stats.TotalBrowseRequests * 300);
                        }
                    }
                }
            }
            catch (TerminateEarly te)
            {
                arg._TestGroup.AddEvent(LogImportance.Critical, test.Name, test.Name + " is terminating early because of the following error: " + te.Message);
                r.SetError(UPnPTestStates.Failed);
                return r;
            }

            if (r.WorstError > UPnPTestStates.Warn)
            {
                throw new TestException("\"" + test.Name + "\" should not reach this code if the result is worse than " + UPnPTestStates.Warn.ToString() + ".", null);
            }

            return r;
        }
        /// <summary>
        /// Performs a Browse invocation.
        /// </summary>
        /// <param name="input"></param>
        /// <param name="test"></param>
        /// <param name="arg"></param>
        /// <param name="cds"></param>
        /// <param name="stats"></param>
        /// <returns></returns>
        public static CdsBrowseSearchResults Browse(BrowseInput input, CdsSubTest test, CdsSubTestArgument arg, CpContentDirectory cds, CdsResult_BrowseStats stats)
        {
            CdsBrowseSearchResults results = new CdsBrowseSearchResults();
            results.SetError(UPnPTestStates.Pass);
            results.ResultErrors = new ArrayList();

            arg._TestGroup.AddEvent(LogImportance.Remark, test.Name, "\""+test.Name+"\" about to do " + input.PrintBrowseParams()+ ".");
            try
            {
                cds.Sync_Browse(input.ObjectID, input.BrowseFlag, input.Filter, input.StartingIndex, input.RequestedCount, input.SortCriteria, out results.Result, out results.NumberReturned, out results.TotalMatches, out results.UpdateID);
            }
            catch (UPnPInvokeException error)
            {
                results.InvokeError = error;
            }

            if (results.InvokeError == null)
            {
                arg._TestGroup.AddEvent(LogImportance.Remark, test.Name, "\""+test.Name+"\" completed " + input.PrintBrowseParams()+ " with no errors returned by the device.");
            }
            else
            {
                arg._TestGroup.AddEvent(LogImportance.Remark, test.Name, "\""+test.Name+"\" completed " + input.PrintBrowseParams()+ " with the device returning an error.");
            }

            stats.TotalBrowseRequests++;
            ArrayList branches = null;
            if (results.InvokeError == null)
            {
                try
                {
                    if (results.Result != null)
                    {
                        if (results.Result != "")
                        {
                            bool schemaOK = CheckDidlLiteSchema(results.Result);

                            if (schemaOK)
                            {
                                results.MediaObjects = branches = MediaBuilder.BuildMediaBranches(results.Result, typeof (MediaItem), typeof(MediaContainer), true);

                                if (branches.Count != results.NumberReturned)
                                {
                                    results.ResultErrors.Add (new CdsException(input.PrintBrowseParams() + " has the \"Result\" argument indicating the presence of " +branches.Count+ " media objects but the request returned NumberReturned=" +results.NumberReturned+"."));
                                }

                                if (input.BrowseFlag == CpContentDirectory.Enum_A_ARG_TYPE_BrowseFlag.BROWSEMETADATA)
                                {
                                    if (branches.Count != 1)
                                    {
                                        results.ResultErrors.Add (new CdsException(input.PrintBrowseParams() + " has the \"Result\" argument indicating the presence of " +branches.Count+ " media objects but the request should have only returned 1 media object."));
                                    }
                                }

                                foreach (IUPnPMedia mobj in branches)
                                {
                                    IMediaContainer imc = mobj as IMediaContainer;

                                    if (imc != null)
                                    {
                                        if (imc.CompleteList.Count > 0)
                                        {
                                            StringBuilder offendingList = new StringBuilder();
                                            int offenses = 0;
                                            foreach (IUPnPMedia offending in imc.CompleteList)
                                            {
                                                if (offenses > 0)
                                                {
                                                    offendingList.Append(",");
                                                }
                                                offendingList.AppendFormat("\"{0}\"", offending.ID);
                                                offenses++;
                                            }
                                            results.ResultErrors.Add (new CdsException(input.PrintBrowseParams() + " has the \"Result\" argument with a declared container (ID=\""+imc.ID+"\") element that also includes its immediate children. Illegally declared media objects in the response are: "+offendingList.ToString()));
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
                catch (Exception error2)
                {
                    results.ResultErrors.Add (error2);
                    if (results.MediaObjects == null)
                    {
                        results.MediaObjects = new ArrayList();
                    }
                }
            }

            // log any errors
            if ((results.InvokeError != null) || (results.ResultErrors.Count > 0))
            {
                LogErrors(arg._TestGroup, test, input, "Browse", results.InvokeError, results.ResultErrors);
                results.SetError(UPnPTestStates.Failed);
            }

            return results;
        }
        public static CdsBrowseSearchResults CheckMetadata(IUPnPMedia checkAgainstThis, CpContentDirectory cds, CdsSubTest test, CdsSubTestArgument arg, CdsResult_BrowseAll details)
        {
            //
            // Save a reference to the media object with the most filterable properties
            int numProperties = 0;
            if (details.MostMetadata != null)
            {
                numProperties = details.MostMetadata.Properties.Count;

                if (details.MostMetadata.DescNodes.Count > 0)
                {
                    numProperties ++;
                }
            }

            int checkValue = 0;

            if (checkAgainstThis.ID != "0")
            {
                if (checkAgainstThis.Resources.Length > 0)
                {
                    checkValue = checkAgainstThis.Properties.Count;
                    if (checkAgainstThis.DescNodes.Count > 0)
                    {
                        checkValue++;
                    }
                }
            }

            if (checkValue > numProperties)
            {
                details.MostMetadata = checkAgainstThis;
            }

            // do a browsemetadata on the media object and determine if the metadata matche.

            BrowseInput input = new BrowseInput();
            input.ObjectID = checkAgainstThis.ID;
            input.BrowseFlag = CpContentDirectory.Enum_A_ARG_TYPE_BrowseFlag.BROWSEMETADATA;
            input.Filter = "*";
            input.StartingIndex = 0;
            input.RequestedCount = 0;
            input.SortCriteria = "";
            IUPnPMedia metadata = null;

            CdsBrowseSearchResults results = Browse(input, test, arg, cds, details);

            test.SetExpectedTestingTime ((details.ExpectedTotalBrowseRequests) * 30);
            arg.ActiveTests.UpdateTimeAndProgress( (details.TotalBrowseRequests) * 30);

            if (results.WorstError <= UPnPTestStates.Warn)
            {
                if (results.NumberReturned != 1)
                {
                    results.SetError(UPnPTestStates.Warn);
                    string msg = "\"" + test.Name + "\": " + input.PrintBrowseParams() + " returned NumberReturned=" +results.NumberReturned+ " when it should logically be 1. This output parameter is not really useful for BrowseMetadata so this logic error does not prevent towards certification, but it should be fixed.";
                    arg._TestGroup.AddEvent(LogImportance.Low, test.Name, msg);
                }

                if (results.TotalMatches != 1)
                {
                    results.SetError(UPnPTestStates.Warn);
                    string msg = "\"" + test.Name + "\": " + input.PrintBrowseParams() + " returned TotalMatches=" +results.TotalMatches+ " when it should logically be 1. This output parameter is not really useful BrowseMetadata so this logic error does not prevent towards certification, but it should be fixed.";
                    arg._TestGroup.AddEvent(LogImportance.Low, test.Name, msg);
                }

                if (results.MediaObjects != null)
                {
                    if (results.MediaObjects.Count == 1)
                    {
                        metadata = results.MediaObjects[0] as IUPnPMedia;

                        if (metadata == null)
                        {
                            throw new TestException(test.Name + " has a TEST LOGIC ERROR. Browse returned without errors but the object's metadata is not stored in an IUPnPMedia object. The offending type is " + results.MediaObjects[0].GetType().ToString(), results.MediaObjects[0]);
                        }

                        if (metadata.ID != input.ObjectID)
                        {
                            results.SetError(UPnPTestStates.Failed);
                            string msg = "\"" + test.Name + "\": " + input.PrintBrowseParams() + " returned a DIDL-Lite media object with ID=\"" +metadata.ID+ "\" when it should be \"" +input.ObjectID+"\" as indicated by the input parameter.";
                            arg._TestGroup.AddEvent(LogImportance.Critical, test.Name, msg);
                        }

                        if (metadata.ParentID != checkAgainstThis.ParentID)
                        {
                            results.SetError(UPnPTestStates.Failed);
                            string msg = "\"" + test.Name + "\": " + input.PrintBrowseParams() + " returned a DIDL-Lite media object with parentID=\"" +metadata.ParentID+ "\" when it should be \"" +checkAgainstThis.ParentID+ "\".";
                            arg._TestGroup.AddEvent(LogImportance.Critical, test.Name, msg);
                        }

                        string original;
                        string received;

                        try
                        {
                            original = checkAgainstThis.ToDidl();
                            received = metadata.ToDidl();
                        }
                        catch
                        {
                            results.SetError(UPnPTestStates.Failed);
                            string msg = "\"" + test.Name + "\": " + input.PrintBrowseParams() + " encountered errors with the DIDL-Lite.";
                            arg._TestGroup.AddEvent(LogImportance.Critical, test.Name, msg);
                            return results;
                        }

                        if (string.Compare(original, received) == 0)
                        {
                            //string msg = "\"" + test.Name + "\": " + input.PrintBrowseParams() + " successfully returned a DIDL-Lite media object that succesfully matches with previously seen metadata.";
                            //arg._TestGroup.AddEvent(LogImportance.Remark, test.Name, msg);
                        }
                        else
                        {
                            System.Xml.XmlDocument doc1 = new XmlDocument();
                            XmlDocument doc2 = new XmlDocument();
                            doc1.LoadXml(original);
                            doc2.LoadXml(received);

                            bool isMatch = true;
                            foreach (XmlElement el1 in doc1.GetElementsByTagName("*"))
                            {
                                bool foundElement = false;
                                foreach (XmlElement el2 in doc2.GetElementsByTagName("*"))
                                {
                                    if (
                                        (el1.Name != "item") &&
                                        (el1.Name != "container") &&
                                        (el1.OuterXml == el2.OuterXml)
                                        )
                                    {
                                        foundElement = true;
                                        break;
                                    }
                                    else if (
                                        (
                                        (el1.Name == "DIDL-Lite") ||
                                        (el1.Name == "item") ||
                                        (el1.Name == "container")
                                        ) &&
                                        (el1.Name == el2.Name)
                                        )
                                    {
                                        foundElement = true;
                                        break;
                                    }
                                }

                                if (foundElement == false)
                                {
                                    isMatch = false;
                                    break;
                                }
                            }

                            if (isMatch==false)
                            {
                                results.SetError(UPnPTestStates.Failed);
                                string msg = "\"" + test.Name + "\": " + input.PrintBrowseParams() + " returned a DIDL-Lite media object that failed to match with previously seen metadata.";
                                arg._TestGroup.AddEvent(LogImportance.Critical, test.Name, msg);
                                arg._TestGroup.AddEvent(LogImportance.Critical, test.Name, test.Name + ": Original=\""+original+"\" Received=\""+received+"\"");
                            }
                        }
                    }
                    else
                    {
                        results.SetError(UPnPTestStates.Failed);
                        string msg = "\"" + test.Name + "\": " + input.PrintBrowseParams() + " did not yield exactly one CDS-compliant media object in its result. Instantiated a total of " +results.MediaObjects.Count+ " media objects.";
                        arg._TestGroup.AddEvent(LogImportance.Critical, test.Name, msg);
                    }
                }
                else
                {
                    throw new TestException(test.Name + " has a TEST LOGIC ERROR. Browse returned without errors but no media objects were instantiated.", null);
                }
            }

            return results;
        }
        /// <summary>
        /// Method executes when a contentdirectory events a change in a container.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="NewValue"></param>
        private void Sink_OnContainerUpdateIDsChanged(CpContentDirectory sender, System.String NewValue)
        {
            string csv_containers = NewValue;
            Hashtable cache = new Hashtable();
            DText parser = new DText();
            DText parser2 = new DText();
            parser.ATTRMARK = ",";
            parser2.ATTRMARK = ",";

            if (csv_containers != "")
            {
                parser[0] = csv_containers;
                int dcnt = parser.DCOUNT();

                for (int i=1; i <= dcnt; i++)
                {
                    string id, update;
                    if (Accumulator_ContainerUpdateIDs.Delimitor == ",")
                    {
                        id = parser[i++];
                        update = parser[i];
                    }
                    else
                    {
                        string pair = parser[i];
                        parser2[0] = pair;
                        id = parser2[1];
                        update = parser2[2];
                    }

                    CpMediaContainer cpc = (CpMediaContainer) this.GetDescendent(id, cache);

                    if (cpc !=null)
                    {
                        try
                        {
                            UInt32 updateId = UInt32.Parse(update);
                            if (updateId != cpc.UpdateID)
                            {
                                cpc.ForceUpdate(false);
                            }
                        }
                        catch
                        {
                            cpc.ForceUpdate(false);
                        }
                    }
                }
            }

            cache.Clear();
        }
Esempio n. 16
0
        /// <summary>
        /// This method will invoke a CDS browse request and provide the results
        /// directly the application-caller.
        /// <para>
        /// Implementation simply calls the parent owner's implementation of 
        /// <see cref="CpMediaContainer.RequestBrowse "/>(ICpMedia, CpContentDirectory.Enum_A_ARG_TYPE_BrowseFlag, string, uint, uint, string).
        /// </para>
        /// </summary>
        /// <param name="BrowseFlag">browse metadata or direct children</param>
        /// <param name="Filter">
        /// Comma-separated value list of metadata names to include
        /// in the response. For all metadata, use * character.
        /// </param>
        /// <param name="StartingIndex">
        /// If obtaining children, the start index of the results.
        /// Otherwise set to zero.
        /// </param>
        /// <param name="RequestedCount">
        /// If obtaining children the max number of child objects
        /// to retrieve. Otherwise use zero.
        /// </param>
        /// <param name="SortCriteria">
        /// Comma-separated value list of metadata names to use for
        /// sorting, such that preceding each metadata name (but after
        /// the comma) a + or - character is present to indicate
        /// ascending or descending sort order for that property.
        /// </param>
        /// <param name="Tag">
        /// Miscellaneous, user-provided object for tracking this 
        /// asynchronous call. Can be used as a means to pass a 
        /// user-defined "state object" at invoke-time so that 
        /// the executed callback during results-processing can be
        /// aware of the component's state at the time of the call.
        /// </param>
        /// <param name="callback">the callback to execute when results become available</param>
        /// <exception cref="ApplicationException">
        /// Thrown if the BrowseFlag value is BrowseDirectChildren because only the
        /// object's metadata can be obtained use browse.
        /// </exception>
        public void RequestBrowse(CpContentDirectory.Enum_A_ARG_TYPE_BrowseFlag BrowseFlag, string Filter, uint StartingIndex, uint RequestedCount, string SortCriteria, object Tag, CpMediaDelegates.Delegate_ResultBrowse callback)
        {
            if (BrowseFlag == CpContentDirectory.Enum_A_ARG_TYPE_BrowseFlag.BROWSEDIRECTCHILDREN)
            {
                throw new ApplicationException("BrowseFlag cannot be BROWSEDIRECTCHILDREN");
            }

            CpMediaContainer parent = (CpMediaContainer) this.Parent;

            parent.RequestBrowse(this, BrowseFlag, Filter, StartingIndex, RequestedCount, SortCriteria, Tag, callback);
        }
 /// <summary>
 /// Method executes when a media server indicates that the tree
 /// hierarchy has changed... only executes when the service
 /// doesn't implement the OnContainerUpdateIDs state variable
 /// </summary>
 private void Sink_OnSystemUpdateIDChanged(CpContentDirectory sender, System.UInt32 NewValue)
 {
     if (sender.HasStateVariable_ContainerUpdateIDs == false)
     {
         this.ForceUpdate(false);
     }
 }
 /// <summary>
 /// Requests the remote media server to provide its sort capabilities.
 /// </summary>
 /// <param name="Tag">
 /// Miscellaneous, user-provided object for tracking this 
 /// asynchronous call. Can be used as a means to pass a 
 /// user-defined "state object" at invoke-time so that 
 /// the executed callback during results-processing can be
 /// aware of the component's state at the time of the call.
 /// </param>
 /// <param name="callback">the callback to execute when results become available</param>
 public void RequestGetSortCapabilities(object Tag, CpContentDirectory.Delegate_OnResult_GetSortCapabilities callback)
 {
     this.ContentDirectory.GetSortCapabilities(Tag, callback);
 }
        /// <summary>
        /// Executes when the ContentDirectory service returns on the subscribe status.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="success"></param>
        private void Sink_OnCdServiceSubscribe(CpContentDirectory sender, bool success)
        {
            sender.OnSubscribe -= new CpContentDirectory.SubscribeHandler(this.Sink_OnCdServiceSubscribe);
            string udn = sender.GetUPnPService().ParentDevice.UniqueDeviceName;

            lock (LockHashes)
            {
                InitStatus status = (InitStatus) UdnToInitStatus[udn];
                if (status != null)
                {
                    status.ZeroMeansDone--;
                    status.SubcribeCD = success;
                    this.ProcessInitStatusChange(udn);
                }
            }
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="cds"></param>
        /// <param name="test"></param>
        /// <param name="arg"></param>
        /// <param name="details"></param>
        /// <param name="C"></param>
        /// <returns></returns>
        /// <exception cref="TerminateEarly">
        /// </exception>
        public static ArrayList GetContainerChildrenAndValidate(IMediaContainer parent, CpContentDirectory cds, CdsSubTest test, CdsSubTestArgument arg, CdsResult_BrowseAll details, Queue C)
        {
            uint totalExpected = uint.MaxValue;
            uint currentChild = 0;

            ArrayList children = new ArrayList();

            while (currentChild < totalExpected)
            {
                BrowseInput input = new BrowseInput();
                input.ObjectID = parent.ID;
                input.BrowseFlag = CpContentDirectory.Enum_A_ARG_TYPE_BrowseFlag.BROWSEDIRECTCHILDREN;
                input.Filter = "*";
                input.StartingIndex = currentChild;
                input.RequestedCount = 1;
                input.SortCriteria = "";

                string containerID = parent.ID;

                if (currentChild == 0)
                {
                    test.SetExpectedTestingTime ((++details.ExpectedTotalBrowseRequests) * 30);
                    arg.ActiveTests.UpdateTimeAndProgress( details.TotalBrowseRequests * 30);
                }

                CdsBrowseSearchResults results = Browse(input, test, arg, cds, details);

                test.SetExpectedTestingTime ((details.ExpectedTotalBrowseRequests) * 30);
                arg.ActiveTests.UpdateTimeAndProgress( (details.TotalBrowseRequests) * 30);

                if (results.WorstError >= UPnPTestStates.Failed)
                {
                    throw new TerminateEarly("\"" + test.Name + "\" is terminating early because " +input.PrintBrowseParams()+ " returned with an error or had problems with the DIDL-Lite.");
                }
                else
                {
                    if (results.NumberReturned != 1)
                    {
                        if (currentChild != 0)
                        {
                            results.SetError(UPnPTestStates.Failed);
                            arg._TestGroup.AddEvent(LogImportance.Low, test.Name, "\"" + test.Name + "\": " + input.PrintBrowseParams() + " returned NumberReturned=" +results.NumberReturned+ " when it should logically be 1.");
                        }
                    }

                    if (results.TotalMatches != totalExpected)
                    {
                        if (currentChild != 0)
                        {
                            results.SetError(UPnPTestStates.Failed);
                            arg._TestGroup.AddEvent(LogImportance.Critical, test.Name, "\"" + test.Name + "\": " + input.PrintBrowseParams() + " returned TotalMatches=" +results.TotalMatches+ " when it should logically be " +totalExpected+ " as reported in an earlier Browse request. This portion of the test requires that a MediaServer device not be in a state where its content hierarchy will change.");
                        }
                        else
                        {
                            totalExpected = results.TotalMatches;
                            if (totalExpected > 0)
                            {
                                details.ExpectedTotalBrowseRequests += ((int)results.TotalMatches * 2) - 1;
                                test.SetExpectedTestingTime ((details.ExpectedTotalBrowseRequests) * 30);
                                arg.ActiveTests.UpdateTimeAndProgress( details.TotalBrowseRequests * 30);
                            }
                        }
                    }

                    if (results.MediaObjects != null)
                    {
                        if (results.MediaObjects.Count == 1)
                        {
                            IUPnPMedia child = results.MediaObjects[0] as IUPnPMedia;

                            if (child == null)
                            {
                                throw new TestException("\"" + test.Name + "\"" + " has a TEST LOGIC ERROR. Browse returned without errors but the child object's metadata is not stored in an IUPnPMedia object. The offending type is " + results.MediaObjects[0].GetType().ToString(), results.MediaObjects[0]);
                            }

                            // ensure no duplicates in object ID
                            foreach (IUPnPMedia previousChild in details.AllObjects)
                            {
                                if (previousChild.ID == child.ID)
                                {
                                    string msg = "\"" + test.Name + "\": " + input.PrintBrowseParams() + " returned an object with ID=\"" +child.ID+ "\" which conflicts with a previously seen media object in ParentContainerID=\"" +previousChild.ParentID+ "\".";
                                    arg._TestGroup.AddEvent(LogImportance.Critical, test.Name, msg);
                                    throw new TerminateEarly(msg);
                                }
                            }

                            // ensure updateID is the same between BrowseDirectChildren and earlier BrowseMetadata.

                            try
                            {
                                uint previousUpdateID = (uint) parent.Tag;
                                if (results.UpdateID != previousUpdateID)
                                {
                                    string msg = "\"" + test.Name + "\": " + input.PrintBrowseParams() + " returned an UpdateID=" +results.UpdateID+ " whilst an UpdateID=" +previousUpdateID+ " was obtained in a previous call for ContainerID=\"" +parent.ID+ "\" with BrowseMetadata.";
                                    arg._TestGroup.AddEvent(LogImportance.Critical, test.Name, msg);
                                    throw new TerminateEarly(msg);
                                }
                            }
                            catch (TerminateEarly te)
                            {
                                throw te;
                            }
                            catch (Exception e)
                            {
                                throw new TestException(test.Name + " has a TEST LOGIC ERROR. Error comparing UpdateID values", parent, e);
                            }

                            // add the child to lists: C, parent's child list, and Allobjects
                            try
                            {
                                parent.AddObject(child, false);
                            }
                            catch (Exception e)
                            {
                                AddObjectError aoe = new AddObjectError();
                                aoe.Parent = parent;
                                aoe.Child = child;
                                throw new TestException(test.Name + " has a TEST LOGIC ERROR. Browse returned without errors but the child object could not be added to its parent.", aoe, e);
                            }

                            details.AllObjects.Add(child);
                            children.Add(child);
                            if (child.IsContainer)
                            {
                                C.Enqueue(child);
                                details.TotalContainers++;
                            }
                            else
                            {
                                details.TotalItems++;
                            }

                            //
                            // Do a BrowseMetadata and check to see if the XML values are the same.
                            //

                            CdsBrowseSearchResults compareResults = CheckMetadata(child, cds, test, arg, details);

                            if (compareResults.InvokeError != null)
                            {
                                arg._TestGroup.AddEvent(LogImportance.High, test.Name, test.Name + ": Browse(BrowseDirectChildren,StartingIndex="+currentChild+",RequestedCount=0) on ContainerID=["+containerID+"] succeeded with warnings because a BrowseMetadata request was rejected by the CDS.");
                            }
                            else if (compareResults.ResultErrors.Count > 0)
                            {
                                string msg = "\"" + test.Name + "\": " + input.PrintBrowseParams() + " failed because a BrowseMetadata request succeeded but the DIDL-Lite could not be represented in object form. Invalid DIDL-Lite is most likely the cause.";
                                arg._TestGroup.AddEvent(LogImportance.Critical, test.Name, msg);
                                throw new TerminateEarly(msg);
                            }
                            else if (compareResults.WorstError >= UPnPTestStates.Failed)
                            {
                                string msg = "\"" + test.Name + "\": " + input.PrintBrowseParams() + " failed because one or more child object's failed a comparison of results between BrowseDirectChildren and BrowseMetadata or encountered some other critical error in that process.";
                                arg._TestGroup.AddEvent(LogImportance.Critical, test.Name, msg);
                                throw new TerminateEarly(msg);
                            }
                            else
                            {
                                //string msg = "\"" + test.Name + "\": " + input.PrintBrowseParams() + " succeeded.";
                                //arg._TestGroup.AddEvent(LogImportance.Remark, test.Name, msg);
                            }

                            //
                            // Track the metadata properties found so far
                            // as we may use them in Browse SortCriteria
                            //

                            // standard top-level attributes
                            Tags T = Tags.GetInstance();
                            AddTo(details.PropertyNames, "@"+T[_ATTRIB.id]);
                            AddTo(details.PropertyNames, "@"+T[_ATTRIB.parentID]);
                            AddTo(details.PropertyNames, "@"+T[_ATTRIB.restricted]);
                            if (child.IsContainer)
                            {
                                if (child.IsSearchable)
                                {
                                    AddTo(details.PropertyNames, "@"+T[_ATTRIB.searchable]);
                                    AddTo(details.PropertyNames, T[_DIDL.Container]+"@"+T[_ATTRIB.searchable]);
                                }

                                AddTo(details.PropertyNames, T[_DIDL.Container]+"@"+T[_ATTRIB.id]);
                                AddTo(details.PropertyNames, T[_DIDL.Container]+"@"+T[_ATTRIB.parentID]);
                                AddTo(details.PropertyNames, T[_DIDL.Container]+"@"+T[_ATTRIB.restricted]);
                            }
                            else if (child.IsItem)
                            {
                                if (child.IsReference)
                                {
                                    AddTo(details.PropertyNames, "@"+T[_ATTRIB.refID]);
                                    AddTo(details.PropertyNames, T[_DIDL.Item]+"@"+T[_ATTRIB.refID]);
                                }

                                AddTo(details.PropertyNames, T[_DIDL.Item]+"@"+T[_ATTRIB.id]);
                                AddTo(details.PropertyNames, T[_DIDL.Item]+"@"+T[_ATTRIB.parentID]);
                                AddTo(details.PropertyNames, T[_DIDL.Item]+"@"+T[_ATTRIB.restricted]);
                            }

                            // standard metadata
                            IMediaProperties properties = child.MergedProperties;
                            IList propertyNames = properties.PropertyNames;
                            foreach (string propertyName in propertyNames)
                            {
                                if (details.PropertyNames.Contains(propertyName) == false)
                                {
                                    details.PropertyNames.Add(propertyName);

                                    // add attributes if they are not added
                                    IList propertyValues = properties[propertyName];
                                    foreach (ICdsElement val in propertyValues)
                                    {
                                        ICollection attributes = val.ValidAttributes;
                                        foreach (string attribName in attributes)
                                        {
                                            StringBuilder sbpn = new StringBuilder();
                                            sbpn.AppendFormat("{0}@{1}", propertyName, attribName);
                                            string fullAttribName = sbpn.ToString();
                                            AddTo(details.PropertyNames, fullAttribName);
                                        }
                                    }
                                }

                                // resources
                                IList resources = child.MergedResources;
                                foreach (IMediaResource res in resources)
                                {
                                    ICollection attributes = res.ValidAttributes;
                                    foreach (string attribName in attributes)
                                    {
                                        string name1 = "res@"+attribName;
                                        string name2 = "@"+attribName;

                                        AddTo(details.PropertyNames, name1);
                                        AddTo(details.PropertyNames, name2);
                                    }
                                }

                                if (resources.Count > 0)
                                {
                                    AddTo(details.PropertyNames, T[_DIDL.Res]);
                                }
                            }
                        }
                        else
                        {
                            if (results.TotalMatches > 0)
                            {
                                results.SetError(UPnPTestStates.Failed);
                                string msg = "\"" + test.Name + "\": " + input.PrintBrowseParams() + " did not yield exactly one CDS-compliant media object in its result. Instantiated a total of " +results.MediaObjects.Count+ " media objects.";
                                arg._TestGroup.AddEvent(LogImportance.Critical, test.Name, msg);
                                throw new TerminateEarly(msg);
                            }
                        }
                    }
                    else
                    {
                        throw new TestException(test.Name + " has a TEST LOGIC ERROR. Browse returned without errors but no media objects were instantiated.", null);
                    }
                }

                currentChild++;
            }

            return children;
        }