/// <summary> /// Throws an exception if the media object should not be added /// as a child. /// </summary> /// <param name="media"></param> /// <returns></returns> /// <exception cref="InvalidCastException"> /// Thrown if the "media" argument is not an <see cref="IDvItem"/> or an <see cref="IDvContainer"/> object. /// Also thrown if the underlying referenced item of "media" is not an <see cref="IDvItem"/>. /// </exception> /// <exception cref="Error_PendingDeleteException"> /// Thrown if the underlying referenced item of "media" is slated for deletion. /// </exception> private void ThrowExceptionIfBad(IDvMedia media) { if (media.IsItem) { // perform an explicit cast - throws InvalidCastException if error IDvItem dvItem = (IDvItem)media; if (media.IsReference) { IDvItem underlying = dvItem.RefItem as IDvItem; if (underlying == null) { throw new InvalidCastException("Cannot convert media.RefItem to IDvItem"); } else if (underlying.IsDeletePending) { throw new Error_PendingDeleteException(underlying); } } } else { // perform an explicit cast - throws InvalidCastException if error IDvContainer dvContainer = (IDvContainer)media; } }
/// <summary> /// Tells the owning object that something has changed. /// </summary> /// <exception cref="InvalidCastException"> /// thrown if the owner is not a <see cref="IDvMedia"/> object. /// </exception> private void NotifyOwnerOfChange() { if (this.Owner != null) { IDvMedia owner = (IDvMedia)this.Owner; owner.NotifyRootOfChange(); } }
/// <summary> /// AddObject() requires that the child be an <see cref="IDvMedia"/> object. /// </summary> /// <param name="newObject"></param> /// <param name="overWrite"></param> /// <exception cref="InvalidCastException"> /// Thrown if the added object is not an <see cref="IDvMedia"/> object. /// </exception> public override void AddObject(IUPnPMedia newObject, bool overWrite) { IDvMedia dv = (IDvMedia)newObject; #if (DEBUG) this.ThrowExceptionIfBad(dv); #endif base.AddObject(newObject, overWrite); this.NotifyRootOfChange(); }
/// <summary> /// This removes a container or item object from /// the child list. It is used by other /// RemoveXXX methods defined in this class /// and implements the portion that allows /// proper media server eventing. /// <para> /// Method properly tells items to notify all other /// items that reference it that it will be deleted. /// </para> /// <para> /// Method properly tells containers to recursively /// remove their child objects, so that descendent /// reference items are properly removed. /// </para> /// </summary> /// <param name="removeThis"></param> public override void RemoveObject(IUPnPMedia removeThis) { IDvMedia dv = removeThis as IDvMedia; IDvItem item = removeThis as IDvItem; IDvContainer container = removeThis as IDvContainer; // Before we go about doing any removal operations, // we fire the event to indicate this object is // about to be removed. ArrayList removeThese = new ArrayList(1); removeThese.Add(removeThis); if (this.OnChildrenToRemove != null) { this.OnChildrenToRemove(this, removeThese); } if (item != null) { // Notify any and all referring items that // this object is about to be removed // from its parent. item.NotifyPendingDelete(); } else if (container != null) { // Instruct all child containers to // remove their own children. IList children = container.CompleteList; foreach (IDvMedia child in children) { container.RemoveObject(child); } } // go ahead and remove this object base.RemoveObject(removeThis); // Notify that the child has formerly been // removed. if (this.OnChildrenRemoved != null) { this.OnChildrenRemoved(this, removeThese); } this.NotifyRootOfChange(); }
private static void EnableMetadataTracking(IDvMedia dvm) { DvMediaContainer dvc = dvm as DvMediaContainer; DvMediaItem dvi = dvm as DvMediaItem; if (dvc != null) { dvc.TrackMetadataChanges = true; foreach (IDvMedia child in dvc.CompleteList) { EnableMetadataTracking(child); } } else if (dvi != null) { dvi.TrackMetadataChanges = true; } }
public static void WriteInnerXmlResources(IUPnPMedia mo, InnerXmlWriter.DelegateShouldPrintResources shouldPrintResources, ToXmlFormatter formatter, ToXmlData data, XmlTextWriter xmlWriter) { IDvMedia dvm = (IDvMedia)mo; ToXmlDataDv txdv = (ToXmlDataDv)data; if (shouldPrintResources(txdv.DesiredProperties)) { if (txdv.BaseUris == null) { txdv.BaseUris = new ArrayList(); } if (txdv.BaseUris.Count == 0) { txdv.BaseUris.Add(txdv.BaseUri); } ToXmlFormatter resFormatter = formatter; resFormatter.StartElement = null; resFormatter.EndElement = null; resFormatter.WriteInnerXml = null; resFormatter.WriteValue = null; // Code is unfinished - intended to allow a media object to // print duplicate resource elements so that each resource // is printed once for every available network interface. foreach (string baseUri in txdv.BaseUris) { txdv.BaseUri = baseUri; foreach (IMediaResource res in dvm.MergedResources) { // Set up the resource formatter to use the // default StartElement, EndElement, WriteInnerXml, and WriteValue // implementations. This stuff has no effect // if the WriteResource field has been assigned. res.ToXml(resFormatter, txdv, xmlWriter); } } } }
/// <summary> /// Method executes when a control point invokes the ContentDirectory.CreateReference action. /// Method is supposed to create a child item that points to an existing /// media item that is somewhere else in the content hierarchy. /// The method should throw exceptions if related media objects are not /// found and should also prevent control points from creating /// references to other containers. /// </summary> /// <param name="containerID">Create a child item in this container.</param> /// <param name="objectID">The new child item should refer to this item.</param> /// <param name="NewID">Return the object ID of the new child item.</param> private void SinkCd_CreateReference(System.String containerID, System.String objectID, out System.String NewID) { // ensure that we can find the container where new child should be. DvMediaContainer parent = this.GetContainer(containerID); if (parent == null) { throw new Error_NoSuchContainer("("+containerID+")"); } // ensure we can find item that is being referenced DvMediaItem refItem = this.GetItem(objectID); if (refItem == null) { throw new Error_NoSuchObject("("+objectID+")"); } // create a new object that points to the referenced item refItem.LockReferenceList(); IDvItem newItem = refItem.CreateReference(); refItem.UnlockReferenceList(); // request application logic for approval in adding this new item. // Application logic should throw an exception, preferably a UPnPCustomException, // to indicate a rejection IDvMedia[] branch = new IDvMedia[1]; branch[0] = newItem; if (this.OnRequestAddBranch != null) { this.OnRequestAddBranch(this, parent, ref branch); } else { throw new Error_InvalidServerConfiguration("CreateReference() cannot be supported until the vendor configures the server correctly."); } // set the value for new ID NewID = newItem.ID; this.m_Stats.CreateReference++; this.FireStatsChange(); }
/// <summary> /// Changes the target media object's parent to a different parent. /// </summary> /// <param name="target">target object</param> /// <param name="np">new parent</param> /// <exception cref="InvalidCastException"> /// Thrown when the target's current parent is not a <see cref="DvMediaContainer"/>. /// </exception> internal static void ChangeParent2(IDvMedia target, DvMediaContainer np) { Exception error = null; IUPnPMedia errorDuplicate = null; IDvMedia[] removeThese = new IDvMedia[1]; removeThese[0] = target; DvMediaContainer dvp = (DvMediaContainer)target.Parent; // fire about to remove event if (dvp.OnChildrenToRemove != null) { dvp.OnChildrenToRemove(dvp, removeThese); } // acquire locks dvp.m_LockListing.AcquireWriterLock(-1); np.m_LockListing.AcquireWriterLock(-1); try { // remove target from current parent int i = dvp.HashingMethod.Get(dvp.m_Listing, target); dvp.m_Listing.RemoveAt(i); target.Parent = null; if (dvp.m_Listing.Count == 0) { dvp.m_Listing = null; } // add target to new parent if (np.m_Listing == null) { np.m_Listing = new ArrayList(); } try { np.HashingMethod.Set(np.m_Listing, target, true); target.Parent = np; } catch (KeyCollisionException) { errorDuplicate = target; } } catch (Exception e) { error = e; } // release locks np.m_LockListing.ReleaseWriterLock(); dvp.m_LockListing.ReleaseWriterLock(); // throw exceptions if appropriate if (error != null) { throw new Exception("Unexpected rrror in DvMediaContainer.ChangeParent2", error); } if (errorDuplicate != null) { throw new Error_DuplicateIdException(errorDuplicate); } // fire children removed event if (dvp.OnChildrenRemoved != null) { dvp.OnChildrenRemoved(dvp, removeThese); } // notify upnp network that two containers changed. dvp.NotifyRootOfChange(); np.NotifyRootOfChange(); }
private void ValidateBranch(DvMediaContainer branchFrom, IDvMedia branch, bool allowNewLocalResources) { DvMediaContainer parent = branchFrom; string basePath = ""; if (allowNewLocalResources) { /// The parent container's TAG field will have one of two things: /// 1) string representation of the local path that it maps to.. /// 2) InnerMediaDirectory structure describing the local path that it maps to. /// 3) Empty string value or null if no mapping to a local file. /// if (parent.Tag == null) { } else if (parent.Tag.GetType() == new InnerMediaDirectory().GetType()) { InnerMediaDirectory imd = (InnerMediaDirectory) parent.Tag; basePath = imd.directory.FullName + "\\"; } else if (parent.Tag.GetType() == typeof(string)) { basePath = parent.Tag + "\\"; } /// The new branch must be a storage container in order to /// map to a local file of sort. May consider making /// the class more restricted to object.container.storageFolder. /// if (branch.IsContainer) { DvMediaContainer container = (DvMediaContainer) branch; if (container.Class.ToString().StartsWith("object.container.storage")) //if (true) { container.Tag = basePath + container.Title; } else { container.Tag = null; } } } if (branch.IsContainer) { DvMediaContainer container = (DvMediaContainer) branch; foreach (IDvMedia child in container.CompleteList) { ValidateBranch(container, child, allowNewLocalResources); } } else if (branch.IsItem) { } else { throw new Exception("Error: Could not validate branch. Branch must be a container, reference, or item."); } if (branch.IsReference == false) { /// Each of these resources should be actual children of the item. /// They should not be resources obtained through a reference. /// This impelementation does not allow references to declare /// their own resources. /// IList resources = branch.Resources; if (resources != null) { if (resources.Count > 0) { foreach (DvMediaResource res in resources) { if (res.ContentUri.StartsWith(DvMediaResource.AUTOMAPFILE)) { if (allowNewLocalResources) { string filePath = res.ContentUri.Substring(DvMediaResource.AUTOMAPFILE.Length); if (File.Exists(filePath)) { if (Directory.Exists(filePath)) { throw new UPnPCustomException(810, "The specified local file-uri is a directory. (" +res.ContentUri+")"); } FileInfo fi = new FileInfo(filePath); string protocol = "http-get"; string network = "*"; string mime; string classType; MimeTypes.ExtensionToMimeType(fi.Extension, out mime, out classType); string info = "*"; StringBuilder sb = new StringBuilder(100); sb.AppendFormat("{0}:{1}:{2}:{3}", protocol, network, mime, info); ProtocolInfoString protInfo = new ProtocolInfoString(sb.ToString()); res.SetProtocolInfo(protInfo); } else { throw new UPnPCustomException(811, "The specified local file-uri does not exist. (" +res.ContentUri+")"); } } else { throw new Error_BadMetadata("Cannot create local http-get resources that are descendents from the specified container."); } } else if ((res.ContentUri == "") && (basePath != "")) { // Get a unique filename where we can save the binary when it gets imported // or sent. string filePath = res.GenerateLocalFilePath(basePath); /// Set the content uri to map to the determined local path... /// although it's understood that the file does not exist yet. /// res.SetContentUri(filePath); } else if (res.ContentUri != "") { string importUri = res.ImportUri; //int x = 3; } else { throw new Error_RestrictedObject("The container specified does not allow creation of storage containers or resources."); } } } } } }
private void ModifyLocalFileSystem(DvMediaContainer branchFrom, IDvMedia branch) { DvMediaContainer parent = (DvMediaContainer) branchFrom; IList resources = branch.Resources; if (branch.IsContainer) { DvMediaContainer container = (DvMediaContainer) branch; if (container.Class.ToString().StartsWith("object.container.storage")) { // This container is a storage container of some sort // so it should map to local directory. // string newDirPath = ""; if (container.Tag != null) { newDirPath = container.Tag.ToString(); } // Make a directory representing this container // and set the container's tag to point to the // media dir info. // DirectoryInfo newDirInfo = Directory.CreateDirectory(newDirPath); InnerMediaDirectory mediadir = new InnerMediaDirectory(); mediadir.directory = newDirInfo; mediadir.directoryname = newDirInfo.FullName; container.Tag = mediadir; } else if (container.Class.ToString().StartsWith("object.container")) { // This is a container, but it doesn't nececessarily map // to a local directory. The container subtree may have // items and references, so persist them virtually // in the hierarchy but not on disk. // container.Tag = null; } foreach (IDvMedia child in container.CompleteList) { ModifyLocalFileSystem(container, child); } } }
private void Handle_OnRequestRemoveBranch(MediaServerDevice sender, DvMediaContainer parentContainer, IDvMedia removeThisBranch) { Exception error = null; this.m_LockRoot.WaitOne(); try { parentContainer.RemoveBranch(removeThisBranch); } catch (Exception e) { error = e; } this.m_LockRoot.ReleaseMutex(); if (error != null) { throw new ApplicationException("Handle_OnRequestRemoveBranch()", error); } }
/// <summary> /// Adds an item, container, or a complete content subtree to this container. /// If the branch is a multiple-item subtree, then the programmer should /// take care to ensure that the proposed sub-tree is stable by itself. /// <para> /// This method is somewhat different than <see cref="DvMediaContainer.AddObject"/>() /// in that the added branch is assigned a new unique id from /// <see cref="MediaBuilder.GetUniqueId"/>(). Such a methodology absolves the /// application-logic from requiring <see cref="MediaBuilder.PrimeNextId"/>() /// to prevent object ID collisions. Programmers should be careful when mixing /// use of <see cref="DvMediaContainer.AddObject"/>() and /// AddBranch(), as improper use can still cause ID collisions. As a general rule, /// application logic that uses <see cref="DvMediaContainer.AddObject"/>() /// should always use <see cref="MediaBuilder.PrimeNextId"/>() to prime the /// media object counter and application logic that always uses AddBranch() /// need not do this. /// </para> /// </summary> /// <param name="branch">An item, container, or multiple-item subtree.</param> /// <exception cref="InvalidCastException"> /// Thrown if the branch is not a <see cref="IDvItem"/> or a <see cref="IDvContainer"/>. /// </exception> public virtual void AddBranch(IDvMedia branch) { branch.ID = MediaBuilder.GetUniqueId(); this.AddObject(branch, false); }
/// <summary> /// <para> /// Removes an item, container, or a complete content subtree from this container. /// If the branch is a multiple-item subtree, then the effect is /// recursive and immediately reflected in the advertised content hierarchy. /// </para> /// /// <para> /// Although the actual MediaResource objects are effectively removed /// from advertisment in the content hierarchy, the local binaries associated /// with those resources are automatically deleted by this routine. The /// ContentDirectory spec indicates that the deletion of binaries is an /// implementation specific issue, and so this implementation requires that /// control-points (or internal control logic) delete the binaries in a /// manner external to this function. /// </para> /// </summary> /// <param name="branch">An item, container, or multiple-item subtree.</param> public virtual void RemoveBranch(IDvMedia branch) { this.RemoveObject(branch); }
/// <summary> /// Method executes when a control point invokes the ContentDirectory.CreateObject action. /// The invoker is supposed to provide most of the valid DIDL-Lite XML for the new object. /// This method will construct a new media object, assign a unique object ID, /// assign appropriate importUri and contentUri values to empty resource attributes /// and then output the new object ID as well as the current DIDL-Lite representaiton /// of the object. /// <para> /// The method supports a vendor specific override, where multiple objects can be /// created in a single call, by means of using the same syntax as the normal, except /// container elements can have child item/container elements, representing /// an entire subtree. Can also have multiple item or container elements under DIDL-Lite /// tag to indicate multiple subtrees. /// </para> /// <para> /// The method supports a vendor specific override, where resource elements in the /// DIDL-Lite can specify the <see cref="MediaResource.AUTOMAPFILE"/> convention /// to automatically add locally mapped files. This does assume the control poitn /// has intimate knowledge about the file system used by this media server. /// </para> /// </summary> /// <param name="containerID">Create new object in this container.</param> /// <param name="Elements">Partial DIDL-Lite document to create the actual object(s).</param> /// <param name="objectID">Object ID for created media object.</param> /// <param name="Result">DIDL-Lite response for the created object(s).</param> private void SinkCd_CreateObject(System.String containerID, System.String Elements, out System.String objectID, out System.String Result) { try { DvMediaContainer parent = this.GetContainer(containerID); if (parent == null) { throw new Error_NoSuchObject("The container \""+containerID+"\" does not exist."); } // builds new subtrees under containerID using the XML in Elements. ArrayList newBranches = DvMediaBuilder.BuildMediaBranches(Elements); // At this point, newBranches contains a list of IDvMedia objects // that are proposed for addition to the content hierarchy. if (this.OnRequestAddBranch != null) { IDvMedia[] addTheseBranches = new IDvMedia[newBranches.Count]; for (int x=0; x < newBranches.Count; x++) { addTheseBranches[x] = (IDvMedia) newBranches[x]; } newBranches = null; this.OnRequestAddBranch (this, parent, ref addTheseBranches); // Reflect the new branches in the tree. // foreach (IDvMedia branch in addTheseBranches) { // Use AddObject() instead of AddBranch() // because the branch already has a unique ID // generated from MediaBuilder.GetUniqueId(). //parent.AddBranch(branch); parent.AddObject(branch, false); } // Initialize structures used to process output for this method. // StringBuilder newIds = new StringBuilder(9 * addTheseBranches.Length); StringBuilder sbXml = null; StringWriter sw = null; MemoryStream ms = null; XmlTextWriter xmlWriter = null; // Write the xml response for the operation // if (ENCODE_UTF8) { ms = new MemoryStream(XML_BUFFER_SIZE); xmlWriter = new XmlTextWriter(ms, System.Text.Encoding.UTF8); } else { sbXml = new StringBuilder(XML_BUFFER_SIZE); sw = new StringWriter(sbXml); xmlWriter = new XmlTextWriter(sw); } xmlWriter.Formatting = System.Xml.Formatting.Indented; xmlWriter.Namespaces = true; // recurse new subtrees and write DIDL-Lite stuff MediaObject.WriteResponseHeader(xmlWriter); RecurseNewBranches(addTheseBranches, newIds, xmlWriter); MediaObject.WriteResponseFooter(xmlWriter); xmlWriter.Flush(); objectID = newIds.ToString(); // properly recast string for UTF8 or UTF-16 if (ENCODE_UTF8) { int startPos = 3; int len = (int) ms.ToArray().Length - startPos; UTF8Encoding utf8e = new UTF8Encoding(false, true); Result = utf8e.GetString(ms.ToArray(), startPos, len); } else { Result = sbXml.ToString(); } xmlWriter.Close(); } else { throw new Error_InvalidServerConfiguration("CreateObject() cannot be supported until the vendor configures the server correctly."); } } catch (Exception e) { Exception ne = new Exception("MediaServerDevice.CreateObject()", e); throw ne; } this.m_Stats.CreateObject++; this.FireStatsChange(); }
/// <summary> /// Changes the target media object's parent to a different parent. /// </summary> /// <param name="target">target object</param> /// <param name="np">new parent</param> /// <exception cref="InvalidCastException"> /// Thrown when the target's current parent is not a <see cref="DvMediaContainer"/>. /// </exception> internal static void ChangeParent2(IDvMedia target, DvMediaContainer np) { Exception error = null; IUPnPMedia errorDuplicate = null; IDvMedia[] removeThese = new IDvMedia[1]; removeThese[0] = target; DvMediaContainer dvp = (DvMediaContainer) target.Parent; // fire about to remove event if (dvp.OnChildrenToRemove != null) { dvp.OnChildrenToRemove(dvp, removeThese); } // acquire locks dvp.m_LockListing.AcquireWriterLock(-1); np.m_LockListing.AcquireWriterLock(-1); try { // remove target from current parent int i = dvp.HashingMethod.Get(dvp.m_Listing, target); dvp.m_Listing.RemoveAt(i); target.Parent = null; if (dvp.m_Listing.Count == 0) { dvp.m_Listing = null; } // add target to new parent if (np.m_Listing == null) { np.m_Listing = new ArrayList(); } try { np.HashingMethod.Set(np.m_Listing, target, true); target.Parent = np; } catch (KeyCollisionException) { errorDuplicate = target; } } catch (Exception e) { error = e; } // release locks np.m_LockListing.ReleaseWriterLock(); dvp.m_LockListing.ReleaseWriterLock(); // throw exceptions if appropriate if (error != null) { throw new Exception("Unexpected rrror in DvMediaContainer.ChangeParent2", error); } if (errorDuplicate != null) { throw new Error_DuplicateIdException(errorDuplicate); } // fire children removed event if (dvp.OnChildrenRemoved != null) { dvp.OnChildrenRemoved (dvp, removeThese); } // notify upnp network that two containers changed. dvp.NotifyRootOfChange(); np.NotifyRootOfChange(); }
private void Handle_OnRequestAddBranch(MediaServerDevice sender, DvMediaContainer parentContainer, ref IDvMedia[] addTheseBranches) { Exception error = null; this.m_LockRoot.WaitOne(); try { if (parentContainer == this.mediaServer.Root) { throw new Error_RestrictedObject("Cannot create objects directly in the root container."); } else if (parentContainer.IsRestricted) { throw new Error_RestrictedObject("Cannot create objects in a restricted container."); } InnerMediaDirectory mediadir = (InnerMediaDirectory) parentContainer.Tag; bool allowNewLocalResources = true; if (mediadir != null) { allowNewLocalResources = Directory.Exists(mediadir.directory.FullName); } foreach (IDvMedia branch in addTheseBranches) { // Throw an exception if ANYTHING is bad. Add is always // atomic per request. // ValidateBranch(parentContainer, branch, allowNewLocalResources); } foreach (IDvMedia branch in addTheseBranches) { // No exceptions were thrown so go ahead and add new // files and directories to the local file system // for the new tree. // if (branch.IsReference == true) { parentContainer.AddBranch(branch); } else { ModifyLocalFileSystem(parentContainer, branch); } } } catch (Exception e) { error = e; } this.m_LockRoot.ReleaseMutex(); if (error != null) { throw new ApplicationException("Handle_OnRequestAddBranch()", error); } }
private void Handle_OnRequestChangeMetadata(MediaServerDevice sender, IDvMedia oldObject, IDvMedia newObject) { Exception error = null; this.m_LockRoot.WaitOne(); try { /// TODO: Add permissions logic /// /// The following metadata fields must be the same between the old and new objects. /// ID, /// refID /// Parent /// IsRestricted /// if (oldObject.IsRestricted) { //Allow everything for now //throw new Error_RestrictedObject("Cannot modify a restricted object."); } if (oldObject.ID != newObject.ID) { throw new Error_ReadOnlyTag("Cannot modify ID"); } if (oldObject.IsContainer != newObject.IsContainer) { throw new Error_BadMetadata("Cannot change containers into items."); } if (oldObject.IsItem != newObject.IsItem) { throw new Error_BadMetadata("Cannot change items into containers."); } if (oldObject.IsRestricted != newObject.IsRestricted) { throw new Error_ReadOnlyTag("Cannot change the \"restricted\" attribute."); } if ((oldObject.IsReference) || (newObject.IsReference)) { if (oldObject.IsReference == newObject.IsReference) { DvMediaItem oldItem = (DvMediaItem) oldObject; DvMediaItem newItem = (DvMediaItem) newObject; string refid1 = oldItem.RefID; string refid2 = newItem.RefID; if (string.Compare(refid1, refid2) != 0) { throw new Error_ReadOnlyTag("Cannot change the \"refID\" attribute."); } } else { throw new Error_BadMetadata("Cannot change a reference item into a non-reference."); } } } catch (Exception e) { error = e; } this.m_LockRoot.ReleaseMutex(); if (error != null) { throw new ApplicationException("Handle_OnRequestChangeMetadata()", error); } // TODO: Actually check validity for metadata of media objects and their resources. }
/// <summary> /// Throws an exception if the media object should not be added /// as a child. /// </summary> /// <param name="media"></param> /// <returns></returns> /// <exception cref="InvalidCastException"> /// Thrown if the "media" argument is not an <see cref="IDvItem"/> or an <see cref="IDvContainer"/> object. /// Also thrown if the underlying referenced item of "media" is not an <see cref="IDvItem"/>. /// </exception> /// <exception cref="Error_PendingDeleteException"> /// Thrown if the underlying referenced item of "media" is slated for deletion. /// </exception> private void ThrowExceptionIfBad(IDvMedia media) { if (media.IsItem) { // perform an explicit cast - throws InvalidCastException if error IDvItem dvItem = (IDvItem) media; if (media.IsReference) { IDvItem underlying = dvItem.RefItem as IDvItem; if (underlying == null) { throw new InvalidCastException("Cannot convert media.RefItem to IDvItem"); } else if (underlying.IsDeletePending) { throw new Error_PendingDeleteException(underlying); } } } else { // perform an explicit cast - throws InvalidCastException if error IDvContainer dvContainer = (IDvContainer) media; } }