Code:
/ Net / Net / 3.5.50727.3053 / DEVDIV / depot / DevDiv / releases / Orcas / SP / wpf / src / Base / System / IO / Packaging / Package.cs / 1 / Package.cs
//------------------------------------------------------------------------------ // //// Copyright (C) Microsoft Corporation. All rights reserved. // // // Description: // This is a base abstract class for Package. This is a part of the // Packaging Layer. // // History: // 01/03/2004: SarjanaS: Initial creation. [Stubs only] // 03/01/2004: SarjanaS: Implemented the functionality for all the members. // 03/17/2004: BruceMac: Initial implementation or PackageRelationship methods // //----------------------------------------------------------------------------- using System; using System.IO; using System.Collections; using System.Collections.Generic; // For SortedList<> using System.Windows; // For Exception strings - SRID using System.Diagnostics; // For Debug.Assert using MS.Internal.IO.Packaging; using MS.Internal.WindowsBase; // for [FriendAccessAllowed] using MS.Internal; // For Invariant.Assert using MS.Utility; namespace System.IO.Packaging { ////// Abstract Base class for the Package. /// This is a part of the Packaging Layer APIs /// public abstract class Package : IDisposable { //----------------------------------------------------- // // Public Constructors // //----------------------------------------------------- #region Protected Constructor ////// Protected constructor for the abstract Base class. /// This is the current contract between the subclass and the base class /// If we decide some registration mechanism then this might change /// /// ///If FileAccess enumeration does not have one of the valid values protected Package(FileAccess openFileAccess) : this(openFileAccess, false /* not streaming by default */) { } ////// Protected constructor for the abstract Base class. /// This is the current contract between the subclass and the base class /// If we decide some registration mechanism then this might change /// /// /// Whether the package is being opened for streaming. ///If FileAccess enumeration does not have one of the valid values protected Package(FileAccess openFileAccess, bool streaming) { ThrowIfFileAccessInvalid(openFileAccess); _openFileAccess = openFileAccess; //PackUriHelper.ValidatedPartUri implements the IComparable interface. _partList = new SortedList(); // initial default is zero _partCollection = null; _disposed = false; _inStreamingCreation = (openFileAccess == FileAccess.Write && streaming); } #endregion Protected Constructor //------------------------------------------------------ // // Public Properties // //----------------------------------------------------- #region Public Properties /// /// Gets the FileAccess with which the package was opened. This is a read only property. /// This property gets set when the package is opened. /// ///FileAccess ///If this Package object has been disposed public FileAccess FileOpenAccess { get { ThrowIfObjectDisposed(); return _openFileAccess; } } ////// The package properties are a subset of the standard OLE property sets /// SummaryInformation and DocumentSummaryInformation, and include such properties /// as Title and Subject. /// ///If this Package object has been disposed public PackageProperties PackageProperties { get { ThrowIfObjectDisposed(); if (_packageProperties == null) _packageProperties = new PartBasedPackageProperties(this); return _packageProperties; } } #endregion Public Properties //------------------------------------------------------ // // Public Methods // //------------------------------------------------------ #region Public Methods #region OpenOnFileMethods ////// Opens a package at the specified Path. This method calls the overload which accepts all the parameters /// with the following defaults - /// FileMode - FileMode.OpenOrCreate, /// FileAccess - FileAccess.ReadWrite /// FileShare - FileShare.None /// /// Path to the package ///Package ///If path parameter is null public static Package Open(string path) { return Open(path, _defaultFileMode, _defaultFileAccess, _defaultFileShare); } ////// Opens a package at the specified Path in the given mode. This method calls the overload which /// accepts all the parameters with the following defaults - /// FileAccess - FileAccess.ReadWrite /// FileShare - FileShare.None /// /// Path to the package /// FileMode in which the package should be opened ///Package ///If path parameter is null ///If FileMode enumeration [packageMode] does not have one of the valid values public static Package Open(string path, FileMode packageMode) { return Open(path, packageMode, _defaultFileAccess, _defaultFileShare); } ////// Opens a package at the specified Path in the given mode with the specified access. This method calls /// the overload which accepts all the parameters with the following defaults - /// FileShare - FileShare.None /// /// Path to the package /// FileMode in which the package should be opened /// FileAccess with which the package should be opened ///Package ///If path parameter is null ///If FileMode enumeration [packageMode] does not have one of the valid values ///If FileAccess enumeration [packageAccess] does not have one of the valid values public static Package Open(string path, FileMode packageMode, FileAccess packageAccess) { return Open(path, packageMode, packageAccess, _defaultFileShare); } ////// Opens the package with the specified parameters. /// /// Note:- /// Since currently there is no plan to implement a generic registration mechanism, this method /// has some hard coded knowledge about the sub classes. This might change later if we come up /// with a registration mechanism. /// There is no caching mechanism in case the FileShare is specified to ReadWrite/Write. /// We do not have any refresh mechanism, to make sure that the cached parts reflect the actual parts, /// in the case where there might be more than one processes writing to the same underlying package. /// /// Path to the package /// FileMode in which the package should be opened /// FileAccess with which the package should be opened /// FileShare with which the package is opened. ///Package ///InvalidArgumentException - If the combination of the FileMode, /// FileAccess and FileShare parameters is not meaningful. ///If path parameter is null ///If FileMode enumeration [packageMode] does not have one of the valid values ///If FileAccess enumeration [packageAccess] does not have one of the valid values public static Package Open(string path, FileMode packageMode, FileAccess packageAccess, FileShare packageShare) { return Open(path, packageMode, packageAccess, packageShare, false /* not in streaming mode */); } #endregion OpenOnFileMethods #region OpenOnStreamMethods ////// Open a package on this stream. This method calls the overload which accepts all the parameters /// with the following defaults - /// FileMode - FileMode.Open /// FileAccess - FileAccess.Read /// /// Stream on which the package is to be opened ///Package ///If stream parameter is null ///If package to be created should have readwrite/read access and underlying stream is write only ///If package to be created should have readwrite/write access and underlying stream is read only public static Package Open(Stream stream) { return Open(stream, _defaultStreamMode, _defaultStreamAccess); } ////// Open a package on this stream. This method calls the overload which accepts all the parameters /// with the following defaults - /// FileAccess - FileAccess.ReadWrite /// /// Stream on which the package is to be opened /// FileMode in which the package should be opened. ///Package ///If stream parameter is null ///If FileMode enumeration [packageMode] does not have one of the valid values ///If package to be created should have readwrite/read access and underlying stream is write only ///If package to be created should have readwrite/write access and underlying stream is read only public static Package Open(Stream stream, FileMode packageMode) { //If the user is providing a FileMode, in all the modes, except FileMode.Open, //its most likely that the user intends to write to the stream. return Open(stream, packageMode, _defaultFileAccess); } ////// Opens a package on this stream. The package is opened in the specified mode and with the access /// specified. /// /// Stream on which the package is created /// FileMode in which the package is to be opened /// FileAccess on the package that is opened ///Package ///If stream parameter is null ///If FileMode enumeration [packageMode] does not have one of the valid values ///If FileAccess enumeration [packageAccess] does not have one of the valid values ///If package to be created should have readwrite/read access and underlying stream is write only ///If package to be created should have readwrite/write access and underlying stream is read only public static Package Open(Stream stream, FileMode packageMode, FileAccess packageAccess) { return Open(stream, packageMode, packageAccess, false /* not in streaming mode */); } #endregion OpenOnStreamMethods #region PackagePart Methods ////// Creates a new part in the package. An empty stream corresponding to this part will be created in the /// package. If a part with the specified uri already exists then we throw an exception. /// This methods will call the CreatePartCore method which will create the actual PackagePart in the package. /// /// Uri of the PackagePart that is to be added /// ContentType of the stream to be added ////// If this Package object has been disposed ///If the package is readonly, it cannot be modified ///If partUri parameter is null ///If contentType parameter is null ///If partUri parameter does not conform to the valid partUri syntax ///If a PackagePart with the given partUri already exists in the Package public PackagePart CreatePart(Uri partUri, string contentType) { return CreatePart(partUri, contentType, CompressionOption.NotCompressed); } ////// Creates a new part in the package. An empty stream corresponding to this part will be created in the /// package. If a part with the specified uri already exists then we throw an exception. /// This methods will call the CreatePartCore method which will create the actual PackagePart in the package. /// /// Uri of the PackagePart that is to be added /// ContentType of the stream to be added /// CompressionOption describing compression configuration /// for the new part. This compression apply only to the part, it doesn't affect relationship parts or related parts. /// This parameter is optional. ////// If this Package object has been disposed ///If the package is readonly, it cannot be modified ///If partUri parameter is null ///If contentType parameter is null ///If partUri parameter does not conform to the valid partUri syntax ///If CompressionOption enumeration [compressionOption] does not have one of the valid values ///If a PackagePart with the given partUri already exists in the Package public PackagePart CreatePart(Uri partUri, string contentType, CompressionOption compressionOption) { ThrowIfObjectDisposed(); ThrowIfReadOnly(); if (partUri == null) throw new ArgumentNullException("partUri"); if (contentType == null) throw new ArgumentNullException("contentType"); ThrowIfCompressionOptionInvalid(compressionOption); PackUriHelper.ValidatedPartUri validatedPartUri = PackUriHelper.ValidatePartUri(partUri); if (_partList.ContainsKey(validatedPartUri)) throw new InvalidOperationException(SR.Get(SRID.PartAlreadyExists)); // Add the part to the _partList if there is no prefix collision // Note: This is the only place where we pass a null to this method for the part and if the // methods returns successfully then we replace the null with an actual part. AddIfNoPrefixCollisionDetected(validatedPartUri, null /* since we don't have a part yet */); PackagePart addedPart = CreatePartCore(validatedPartUri, contentType, compressionOption); //Set the entry for this Uri with the actual part _partList[validatedPartUri] = addedPart; return addedPart; } ////// Returns a part that already exists in the package. If the part /// Corresponding to the URI does not exist in the package then an exception is /// thrown. The method calls the GetPartCore method which actually fetches the part. /// /// ////// If this Package object has been disposed ///If the package is write only, information cannot be retrieved from it ///If partUri parameter is null ///If partUri parameter does not conform to the valid partUri syntax ///If the requested part does not exists in the Package public PackagePart GetPart(Uri partUri) { PackagePart returnedPart = GetPartHelper(partUri); if (returnedPart == null) throw new InvalidOperationException(SR.Get(SRID.PartDoesNotExist)); else return returnedPart; } ////// This is a convenient method to check whether a given part exists in the /// package. This will have a default implementation that will try to retrieve /// the part and then if successful, it will return true. /// If the custom file format has an easier way to do this, they can override this method /// to get this information in a more efficient way. /// /// ////// If this Package object has been disposed ///If the package is write only, information cannot be retrieved from it ///If partUri parameter is null ///If partUri parameter does not conform to the valid partUri syntax public virtual bool PartExists(Uri partUri) { return (GetPartHelper(partUri) != null); } ////// This method will do all the house keeping required when a part is deleted /// Then the DeletePartCore method will be called which will have the actual logic to /// do the work specific to the underlying file format and will actually delete the /// stream corresponding to this part. This method does not throw if the specified /// part does not exist. This is in conformance with the FileInfo.Delete call. /// /// ///If this Package object has been disposed ///If the package is readonly, it cannot be modified ///If partUri parameter is null ///If partUri parameter does not conform to the valid partUri syntax public void DeletePart(Uri partUri) { ThrowIfObjectDisposed(); ThrowIfReadOnly(); ThrowIfInStreamingCreation("DeletePart"); if (partUri == null) throw new ArgumentNullException("partUri"); PackUriHelper.ValidatedPartUri validatedPartUri = (PackUriHelper.ValidatedPartUri)PackUriHelper.ValidatePartUri(partUri); if (_partList.ContainsKey(validatedPartUri)) { //This will get the actual casing of the part that //is stored in the partList which is equivalent to the //partUri provided by the user validatedPartUri = (PackUriHelper.ValidatedPartUri)_partList[validatedPartUri].Uri; _partList[validatedPartUri].IsDeleted = true; _partList[validatedPartUri].Close(); //Call the Subclass to delete the part //!!Important Note: The order of this call is important as one of the //sub-classes - ZipPackage relies upon the abstract layer to be //able to provide the ZipPackagePart in order to do the proper //clean up and delete operation. //The dependency is in ZipPackagePart.DeletePartCore method. //Ideally we would have liked to avoid this kind of a restriction //but due to the current class interfaces and data structure ownerships //between these objects, it tough to re-design at this point. DeletePartCore(validatedPartUri); //Finally remove it from the list of parts in the cache _partList.Remove(validatedPartUri); } else //If the part is not in memory we still call the underlying layer //to delete the part if it exists DeletePartCore(validatedPartUri); if (PackUriHelper.IsRelationshipPartUri(validatedPartUri)) { //We clear the in-memory data structure corresponding to that relationship part //This will ensure that the intention of the user to delete the part, is respected. //And thus we will not try to recreate it just in case there was some data in the //memory structure. Uri owningPartUri = PackUriHelper.GetSourcePartUriFromRelationshipPartUri(validatedPartUri); //Package-level relationships in /_rels/.rels if (Uri.Compare(owningPartUri, PackUriHelper.PackageRootUri, UriComponents.SerializationInfoString, UriFormat.UriEscaped, StringComparison.Ordinal)==0) { //Clear any data in memory this.ClearRelationships(); } else { //Clear any data in memory if (this.PartExists(owningPartUri)) { PackagePart owningPart = this.GetPart(owningPartUri); owningPart.ClearRelationships(); } } } else { // remove any relationship part DeletePart(PackUriHelper.GetRelationshipPartUri(validatedPartUri)); } } ////// This returns a collection of all the Parts within the package. /// ////// If this Package object has been disposed ///If the package is writeonly, no information can be retrieved from it public PackagePartCollection GetParts() { ThrowIfObjectDisposed(); ThrowIfWriteOnly(); //Ideally we should decide whether we should query the underlying layer for parts based on the //FileShare enum. But since we do not have that information, currently the design is to just //query the underlying layer once. //Note: //Currently the incremental behavior for GetPart method is not consistent with the GetParts method //which just queries the underlying layer once. if (_partCollection == null) { PackagePart[] parts = GetPartsCore(); //making sure that we get a valid array Debug.Assert((parts != null), "Subclass is expected to return an array [an empty one if there are no parts] as a result of GetPartsCore method call. "); PackUriHelper.ValidatedPartUri partUri; //We need this dictionary to detect any collisions that might be present in the //list of parts that was given to us from the underlying physical layer, as more than one //partnames can be mapped to the same normalized part. //Note: We cannot use the _partList member variable, as that gets updated incrementally and so its //not possible to find the collisions using that list. //PackUriHelper.ValidatedPartUri implements the IComparable interface. DictionaryseenPartUris = new Dictionary (parts.Length); for (int i = 0; i < parts.Length; i++) { partUri = (PackUriHelper.ValidatedPartUri)parts[i].Uri; if (seenPartUris.ContainsKey(partUri)) throw new FileFormatException(SR.Get(SRID.BadPackageFormat)); else { // Add the part to the list of URIs that we have already seen seenPartUris.Add(partUri, parts[i]); if (!_partList.ContainsKey(partUri)) { // Add the part to the _partList if there is no prefix collision AddIfNoPrefixCollisionDetected(partUri, parts[i]); } } } _partCollection = new PackagePartCollection(_partList); } return _partCollection; } #endregion PackagePart Methods #region IDisposable Methods /// /// Member of the IDisposable interface. This method will clean up all the resources. /// It calls the Flush method to make sure that all the changes made get persisted. /// Note - subclasses should only override Dispose(bool) if they have resources to release. /// See the Design Guidelines for the Dispose() pattern. /// void IDisposable.Dispose() { if (!_disposed) { try { // put our house in order before involving the subclass // close core properties // This method will write out the core properties to the stream // In non-streaming mode - These will get flushed to the disk as a part of the DoFlush operation if (_packageProperties != null) _packageProperties.Close(); // flush relationships if (InStreamingCreation) ClosePackageRelationships(); else FlushRelationships(); //Write out the Relationship XML for the parts //These streams will get flushed in the DoClose operation. DoOperationOnEachPart(DoCloseRelationshipsXml); // Close all the parts that are currently open DoOperationOnEachPart(DoClose); // start the dispose chain Dispose(true); } finally { // do this no matter what (handles case of poorly behaving subclass that doesn't call back into Dispose(bool) _disposed = true; } //Since all the resources we care about are freed at this point. GC.SuppressFinalize(this); } } #endregion IDisposable Methods #region Other Methods ////// Closes the package and all the underlying parts and relationships. /// Calls the Dispose Method, since they have the same semantics /// public void Close() { ((IDisposable)this).Dispose(); } ////// Flushes the contents of the parts and the relationships to the package. /// This method will call the FlushCore method which will do the actual flushing of contents. /// ///If this Package object has been disposed ///If the package is readonly, it cannot be modified public void Flush() { ThrowIfObjectDisposed(); ThrowIfReadOnly(); // Flush core properties (in streaming production, has to be done before parts get flushed). // Write core properties (in streaming production, has to be done before parts get flushed). // This call will write out the xml for the core properties to the stream // In non-streaming mode - These properties will get flushed to disk as a part of the DoFlush operation if (_packageProperties != null) _packageProperties.Flush(); // Write package relationships XML to the relationship part stream. // These will get flushed to disk as a part of the DoFlush operation if (InStreamingCreation) FlushPackageRelationships(); // Create a piece. else FlushRelationships(); // Flush into .rels part. //Write out the Relationship XML for the parts //These streams will get flushed in the DoFlush operation. DoOperationOnEachPart(DoWriteRelationshipsXml); // Flush all the parts that are currently open. // This will flush part relationships. DoOperationOnEachPart(DoFlush); FlushCore(); } #endregion Other Methods #region PackageRelationship Methods ////// Creates a relationship at the Package level with the Target PackagePart specified as the Uri /// /// Target's URI /// Enumeration indicating the base uri for the target uri /// PackageRelationship type, having uri like syntax that is used to /// uniquely identify the role of the relationship ////// If this Package object has been disposed ///If the package is readonly, it cannot be modified ///If parameter "targetUri" is null ///If parameter "relationshipType" is null ///If parameter "targetMode" enumeration does not have a valid value ///If TargetMode is TargetMode.Internal and the targetUri is an absolute Uri ///If relationship is being targeted to a relationship part public PackageRelationship CreateRelationship(Uri targetUri, TargetMode targetMode, string relationshipType) { return CreateRelationship(targetUri, targetMode, relationshipType, null); } ////// Creates a relationship at the Package level with the Target PackagePart specified as the Uri /// /// Target's URI /// Enumeration indicating the base uri for the target uri /// PackageRelationship type, having uri like syntax that is used to /// uniquely identify the role of the relationship /// String that conforms to the xsd:ID datatype. Unique across the source's /// relationships. Null is OK (ID will be generated). An empty string is an invalid XML ID. ////// If this Package object has been disposed ///If the package is readonly, it cannot be modified ///If parameter "targetUri" is null ///If parameter "relationshipType" is null ///If parameter "targetMode" enumeration does not have a valid value ///If TargetMode is TargetMode.Internal and the targetUri is an absolute Uri ///If relationship is being targeted to a relationship part ///If parameter "id" is not a valid Xsd Id ///If an id is provided in the method, and its not unique public PackageRelationship CreateRelationship(Uri targetUri, TargetMode targetMode, string relationshipType, String id) { ThrowIfObjectDisposed(); ThrowIfReadOnly(); EnsureRelationships(); //All parameter validation is done in the following call return _relationships.Add(targetUri, targetMode, relationshipType, id); } ////// Deletes a relationship from the Package. This is done based on the /// relationship's ID. The target PackagePart is not affected by this operation. /// /// The ID of the relationship to delete. An invalid ID will not /// throw an exception, but nothing will be deleted. ///If this Package object has been disposed ///If the package is readonly, it cannot be modified ///If parameter "id" is null ///If parameter "id" is not a valid Xsd Id public void DeleteRelationship(String id) { ThrowIfObjectDisposed(); ThrowIfReadOnly(); ThrowIfInStreamingCreation("DeleteRelationship"); if (id == null) throw new ArgumentNullException("id"); InternalRelationshipCollection.ThrowIfInvalidXsdId(id); EnsureRelationships(); _relationships.Delete(id); } ////// Returns a collection of all the Relationships that are /// owned by the package /// ////// If this Package object has been disposed ///If the package is write only, no information can be retrieved from it public PackageRelationshipCollection GetRelationships() { //All the validations for dispose and file access are done in the //GetRelationshipsHelper method. return GetRelationshipsHelper(null); } ////// Returns a collection of filtered Relationships that are /// owned by the package /// The filter string is compared with the type of the relationships /// in a case sensitive and culture ignorant manner. /// ////// If this Package object has been disposed ///If the package is write only, no information can be retrieved from it ///If parameter "relationshipType" is null ///If parameter "relationshipType" is an empty string public PackageRelationshipCollection GetRelationshipsByType(string relationshipType) { //These checks are made in the GetRelationshipsHelper as well, but we make them //here as we need to perform parameter validation ThrowIfObjectDisposed(); ThrowIfWriteOnly(); if (relationshipType == null) throw new ArgumentNullException("relationshipType"); InternalRelationshipCollection.ThrowIfInvalidRelationshipType(relationshipType); return GetRelationshipsHelper(relationshipType); } ////// Retrieve a relationship per ID. /// /// The relationship ID. ///The relationship with ID 'id' or throw an exception if not found. ///If this Package object has been disposed ///If the package is write only, no information can be retrieved from it ///If parameter "id" is null ///If parameter "id" is not a valid Xsd Id ///If the requested relationship does not exist in the Package public PackageRelationship GetRelationship(string id) { //All the validations for dispose and file access are done in the //GetRelationshipHelper method. PackageRelationship returnedRelationship = GetRelationshipHelper(id); if (returnedRelationship == null) throw new InvalidOperationException(SR.Get(SRID.PackageRelationshipDoesNotExist)); else return returnedRelationship; } ////// Returns whether there is a relationship with the specified ID. /// /// The relationship ID. ///true iff a relationship with ID 'id' is defined on this source. ///If this Package object has been disposed ///If the package is write only, no information can be retrieved from it ///If parameter "id" is null ///If parameter "id" is not a valid Xsd Id public bool RelationshipExists(string id) { //All the validations for dispose and file access are done in the //GetRelationshipHelper method. return (GetRelationshipHelper(id) != null); } #endregion PackageRelationship Methods #endregion Public Methods #region Protected Abstract Methods ////// This method is for custom implementation corresponding to the underlying file format. /// This method will actually add a new part to the package. An empty part should be /// created as a result of this call. /// /// /// /// ///protected abstract PackagePart CreatePartCore(Uri partUri, string contentType, CompressionOption compressionOption); /// /// This method is for custom implementation corresponding to the underlying file format. /// This method will actually return the part after reading the actual physical bits. /// If the PackagePart does not exists in the underlying package then this method should return a null. /// This method must not throw an exception if a part does not exist. /// /// ///protected abstract PackagePart GetPartCore(Uri partUri); /// /// This method is for custom implementation corresponding to the underlying file format. /// This method will actually delete the part from the underlying package. /// This method should not throw if the specified part does not exist. /// This is in conformance with the FileInfo.Delete call. /// /// protected abstract void DeletePartCore(Uri partUri); ////// This method is for custom implementation corresponding to the underlying file format. /// This is the method that knows how to get the actual parts. If there are no parts, /// this method should return an empty array. /// ///protected abstract PackagePart[] GetPartsCore(); /// /// This method is for custom implementation corresponding to the underlying file format. /// This method should be used to dispose the resources that are specific to the file format. /// Also everything should be flushed to the disc before closing the package. /// ///Subclasses that manage non-memory resources should override this method and free these resources. /// Any override should be careful to always call base.Dispose(disposing) to ensure orderly cleanup. protected virtual void Dispose(bool disposing) { if (!_disposed && disposing) { _partList.Clear(); if (_packageProperties != null) { _packageProperties.Dispose(); _packageProperties = null; } //release objects _partList = null; _partCollection = null; _relationships = null; _disposed = true; } } ////// This method is for custom implementation corresponding to the underlying file format. /// This method flushes the contents of the package to the disc. /// protected abstract void FlushCore(); #endregion Protected Abstract Methods //----------------------------------------------------- // // Internal Constructors // //------------------------------------------------------ // None //----------------------------------------------------- //----------------------------------------------------- // // Internal Properties // //----------------------------------------------------- #region Internal Properties ////// true iff the package was opened for streaming. /// internal bool InStreamingCreation { get { return _inStreamingCreation; } } #endregion Internal Properties //------------------------------------------------------ // // Internal Methods // //----------------------------------------------------- #region Internal Methods // Some operations are not supported while producing a package in streaming mode. internal void ThrowIfInStreamingCreation(string methodName) { if (_inStreamingCreation) throw new IOException(SR.Get(SRID.OperationIsNotSupportedInStreamingProduction, methodName)); } // Some operations are supported only while producing a package in streaming mode. internal void ThrowIfNotInStreamingCreation(string methodName) { if (!InStreamingCreation) throw new IOException(SR.Get(SRID.MethodAvailableOnlyInStreamingCreation, methodName)); } //If the container is readonly then we cannot add/delete to it internal void ThrowIfReadOnly() { if (_openFileAccess == FileAccess.Read) throw new IOException(SR.Get(SRID.CannotModifyReadOnlyContainer)); } // If the container is writeonly, parts cannot be retrieved from it internal void ThrowIfWriteOnly() { if (_openFileAccess == FileAccess.Write) throw new IOException(SR.Get(SRID.CannotRetrievePartsOfWriteOnlyContainer)); } // return true to continue internal delegate bool PartOperation(PackagePart p); internal static void ThrowIfFileModeInvalid(FileMode mode) { //We do the enum check as suggested by the following condition for performance reasons. if (mode < FileMode.CreateNew || mode > FileMode.Append) throw new ArgumentOutOfRangeException("mode"); } internal static void ThrowIfFileAccessInvalid(FileAccess access) { //We do the enum check as suggested by the following condition for performance reasons. if (access < FileAccess.Read || access > FileAccess.ReadWrite) throw new ArgumentOutOfRangeException("access"); } internal static void ThrowIfCompressionOptionInvalid(CompressionOption compressionOption) { //We do the enum check as suggested by the following condition for performance reasons. if (compressionOption < CompressionOption.NotCompressed || compressionOption > CompressionOption.SuperFast) throw new ArgumentOutOfRangeException("compressionOption"); } #region Write-time streaming API ////// This method gives the possibility of opening a package in streaming mode. /// When 'streaming' is true, the only allowed file modes are Create and CreateNew, /// the only allowed file access is Write and the only allowed FileShare values are /// Null and Read. /// /// Path to the package. /// FileMode in which the package should be opened. /// FileAccess with which the package should be opened. /// FileShare with which the package is opened. /// Whether to allow the creation of part pieces while enforcing write-once access. ///Package ///If path parameter is null ///If FileAccess enumeration [packageAccess] does not have one of the valid values ///If FileMode enumeration [packageMode] does not have one of the valid values internal static Package Open( string path, FileMode packageMode, FileAccess packageAccess, FileShare packageShare, bool streaming) { EventTrace.NormalTraceEvent(EventTraceGuidId.DRXOPENPACKAGEGUID, EventType.StartEvent); if (path == null) throw new ArgumentNullException("path"); ThrowIfFileModeInvalid(packageMode); ThrowIfFileAccessInvalid(packageAccess); ValidateStreamingAccess(packageMode, packageAccess, packageShare, streaming); //Note: FileShare enum is not being verfied at this stage, as we do not interpret the flag in this //code at all and just pass it on to the next layer, where the necessary validation can be //performed. Also, there is no meaningful way to check this parameter at this layer, as the //FileShare enumeration is a set of flags and flags/Bit-fields can be combined using a //bitwise OR operation to create different values, and validity of these values is specific to //the actual physical implementation. //Verify if this is valid for filenames FileInfo packageFileInfo = new FileInfo(path); Package package = new ZipPackage(packageFileInfo.FullName, packageMode, packageAccess, packageShare, streaming); if (!package._inStreamingCreation) // No read operation in streaming production. { //We need to get all the parts if any exists from the underlying file //so that we have the names in the Normalized form in our in-memory //data structures. //Note: If ever this call is removed, each individual call to GetPartCore, //may result in undefined behavior as the underlying ZipArchive, maintains the //files list as being case-sensitive. if (package.FileOpenAccess == FileAccess.ReadWrite || package.FileOpenAccess == FileAccess.Read) package.GetParts(); } EventTrace.NormalTraceEvent(EventTraceGuidId.DRXOPENPACKAGEGUID, EventType.EndEvent); return package; } ////// This method gives the possibility of opening a package in streaming mode. /// When 'streaming' is true, the only allowed file modes are Create and CreateNew, /// and the only allowed file access is Write. /// /// Stream on which the package is created /// FileMode in which the package is to be opened /// FileAccess on the package that is opened /// Whether to allow the creation of part pieces while enforcing write-once access. ///Package ///If stream parameter is null ///If FileMode enumeration [packageMode] does not have one of the valid values ///If FileAccess enumeration [packageAccess] does not have one of the valid values ///If package to be created should have readwrite/read access and underlying stream is write only ///If package to be created should have readwrite/write access and underlying stream is read only [FriendAccessAllowed] internal static Package Open(Stream stream, FileMode packageMode, FileAccess packageAccess, bool streaming) { EventTrace.NormalTraceEvent(EventTraceGuidId.DRXOPENPACKAGEGUID, EventType.StartEvent); if (stream == null) throw new ArgumentNullException("stream"); ValidateStreamingAccess(packageMode, packageAccess, null /* no FileShare info */, streaming); //FileMode and FileAccess Enums are validated in the following call Stream ensuredStream = ValidateModeAndAccess(stream, packageMode, packageAccess); Package package; // Today the Open(Stream) method is purely used for streams of Zip file format as // that is the default underlying file format mapper implemented. package = new ZipPackage(ensuredStream, packageMode, packageAccess, streaming); if (!package._inStreamingCreation) // No read operation in streaming production. { //We need to get all the parts if any exists from the underlying file //so that we have the names in the Normalized form in our in-memory //data structures. //Note: If ever this call is removed, each individual call to GetPartCore, //may result in undefined behavior as the underlying ZipArchive, maintains the //files list as being case-sensitive. if(package.FileOpenAccess == FileAccess.ReadWrite || package.FileOpenAccess == FileAccess.Read) package.GetParts(); } EventTrace.NormalTraceEvent(EventTraceGuidId.DRXOPENPACKAGEGUID, EventType.EndEvent); return package; } ////// Write a nonterminal piece for /_rels/.rels. /// internal void FlushPackageRelationships() { ThrowIfNotInStreamingCreation("FlushPackageRelationships"); if (_relationships == null) return; // nothing to flush _relationships.Flush(); } ////// Write a terminal piece for /_rels/.rels. /// internal void ClosePackageRelationships() { ThrowIfNotInStreamingCreation("ClosePackageRelationships"); if (_relationships == null) return; // no relationship part _relationships.CloseInStreamingCreationMode(); } #endregion Write-time streaming API #endregion Internal Methods //------------------------------------------------------ // // Internal Events // //------------------------------------------------------ // None //----------------------------------------------------- // // Private Methods // //------------------------------------------------------ #region Private Methods // This method is only when new part is added to the Package object. // This method will throw an exception if the name of the part being added is a // prefix of the name of an existing part. // Example - Say the following parts exist in the package // 1. /abc.xaml // 2. /xyz/pqr/a.jpg // As an example - Adding any of the following parts will throw an exception - // 1. /abc.xaml/new.xaml // 2. /xyz/pqr private void AddIfNoPrefixCollisionDetected(PackUriHelper.ValidatedPartUri partUri, PackagePart part) { //Add the Normalized Uri to the sorted _partList tentatively to see where it will get inserted _partList.Add(partUri, part); //Get the index of the entry at which this part was added int index = _partList.IndexOfKey(partUri); Invariant.Assert(index >= 0, "Given uri must be present in the dictionary"); string normalizedPartName = partUri.NormalizedPartUriString; string precedingPartName = null; string followingPartName = null; if (index > 0) { precedingPartName = _partList.Keys[index - 1].NormalizedPartUriString; } if (index < _partList.Count - 1) { followingPartName = _partList.Keys[index + 1].NormalizedPartUriString; } if ((precedingPartName != null && normalizedPartName.StartsWith(precedingPartName, StringComparison.Ordinal) && normalizedPartName.Length > precedingPartName.Length && normalizedPartName[precedingPartName.Length] == PackUriHelper.ForwardSlashChar) || (followingPartName != null && followingPartName.StartsWith(normalizedPartName, StringComparison.Ordinal) && followingPartName.Length > normalizedPartName.Length && followingPartName[normalizedPartName.Length] == PackUriHelper.ForwardSlashChar)) { //Removing the invalid entry from the _partList. _partList.Remove(partUri); throw new InvalidOperationException(SR.Get(SRID.PartNamePrefixExists)); } } // Test consistency of file opening parameters with the value of 'streaming', and record // whether the streaming mode is for consumption or production. // private static void ValidateStreamingAccess( FileMode packageMode, FileAccess packageAccess, NullablepackageShare, bool streaming) { if (streaming) { if (packageMode == FileMode.Create || packageMode == FileMode.CreateNew) { if (packageAccess != FileAccess.Write) throw new IOException(SR.Get(SRID.StreamingPackageProductionImpliesWriteOnlyAccess)); if ( packageShare != null && packageShare != FileShare.Read && packageShare != FileShare.None) throw new IOException(SR.Get(SRID.StreamingPackageProductionRequiresSingleWriter)); } else { // Blanket exception pending design of streaming consumption. throw new NotSupportedException(SR.Get(SRID.StreamingModeNotSupportedForConsumption)); } } } //Checking if the mode and access parameters are compatible with the provided stream. private static Stream ValidateModeAndAccess(Stream s, FileMode mode, FileAccess access) { ThrowIfFileModeInvalid(mode); ThrowIfFileAccessInvalid(access); //asking for more permissions than the underlying stream. // Stream cannot write, but package to be created should have write access if (!s.CanWrite && (access == FileAccess.ReadWrite || access == FileAccess.Write)) throw new IOException(SR.Get(SRID.IncompatibleModeOrAccess)); //asking for more permissions than the underlying stream. // Stream cannot read, but the package to be created should have read access if (!s.CanRead && (access == FileAccess.ReadWrite || access == FileAccess.Read)) throw new IOException(SR.Get(SRID.IncompatibleModeOrAccess)); //asking for less restricted access to the underlying stream //stream is ReadWrite but the package is either readonly, or writeonly if ((s.CanRead && s.CanWrite) && (access == FileAccess.Read || access == FileAccess.Write)) { return new RestrictedStream(s, access); } else return s; } //Throw if the object is in a disposed state private void ThrowIfObjectDisposed() { if (_disposed == true) throw new ObjectDisposedException(null, SR.Get(SRID.ObjectDisposed)); } private void EnsureRelationships() { // once per package if (_relationships == null) { _relationships = new InternalRelationshipCollection(this); } } //Delete All Package-level Relationships private void ClearRelationships() { if(_relationships!=null) _relationships.Clear(); } //Flush the relationships at package level private void FlushRelationships() { // flush relationships if (_relationships != null && _openFileAccess != FileAccess.Read) { _relationships.Flush(); } } //We do the close or the flush operation per part private void DoOperationOnEachPart(PartOperation operation) { //foreach (PackagePart p in _partList.Values) // p.Close(); - this throws // Make local copy of part names to prevent exception during enumeration when // a new relationship part gets created (flushing relationships can cause part creation). // This code throws in such a case: // // foreach (PackagePart p in _partList.Values) // p.Flush(); // if (_partList.Count > 0) { int partCount = 0; PackUriHelper.ValidatedPartUri[] partKeys = new PackUriHelper.ValidatedPartUri[_partList.Keys.Count]; foreach (PackUriHelper.ValidatedPartUri uri in _partList.Keys) { partKeys[partCount++] = uri; } // this throws an exception in certain cases (when a part has been deleted) // // _partList.Keys.CopyTo(keys, 0); for (int i = 0; i < _partList.Keys.Count; i++) { // Some of these may disappear during above close because the list contains "relationship parts" // and these are removed if their parts' relationship collection is empty // This fails: // _partList[keys[i]].Flush(); PackagePart p; if (_partList.TryGetValue(partKeys[i], out p)) { if (!operation(p)) break; } } } } //We needed to separate the rels parts from the other parts //because if a rels part for a part occured earlier than the part itself in the array, //the rels part would be closed and then when close the part and try to persist the relationships //for the particular part, it would throw an exception private bool DoClose(PackagePart p) { if (!p.IsClosed) { if (PackUriHelper.IsRelationshipPartUri(p.Uri) && PackUriHelper.ComparePartUri(p.Uri, PackageRelationship.ContainerRelationshipPartName) != 0) { //First we close the source part. //Note - we can safely do this as DoClose is being called on all parts. So ultimately we will end up //closing the source part as well. //This logic only takes care of out of order parts. PackUriHelper.ValidatedPartUri owningPartUri = (PackUriHelper.ValidatedPartUri)PackUriHelper.GetSourcePartUriFromRelationshipPartUri(p.Uri); //If the source part for this rels part exists then we close it. PackagePart sourcePart; if (_partList.TryGetValue(owningPartUri, out sourcePart)) sourcePart.Close(); } p.Close(); } return true; } private bool DoFlush(PackagePart p) { p.Flush(); return true; } private bool DoWriteRelationshipsXml(PackagePart p) { if (!p.IsRelationshipPart) { p.FlushRelationships(); } return true; } private bool DoCloseRelationshipsXml(PackagePart p) { if (!p.IsRelationshipPart) { p.CloseRelationships(); } return true; } private PackagePart GetPartHelper(Uri partUri) { ThrowIfObjectDisposed(); ThrowIfWriteOnly(); if (partUri == null) throw new ArgumentNullException("partUri"); PackUriHelper.ValidatedPartUri validatePartUri = PackUriHelper.ValidatePartUri(partUri); if (_partList.ContainsKey(validatePartUri)) return _partList[validatePartUri]; else { //Ideally we should decide whether we should query the underlying layer for the part based on the //FileShare enum. But since we do not have that information, currently the design is to always //ask the underlying layer, this allows for incremental access to the package. //Note: //Currently this incremental behavior for GetPart is not consistent with the GetParts method //which just queries the underlying layer once. PackagePart returnedPart = GetPartCore(validatePartUri); if (returnedPart != null) { // Add the part to the _partList if there is no prefix collision AddIfNoPrefixCollisionDetected(validatePartUri, returnedPart); } return returnedPart; } } /// /// Retrieve a relationship per ID. /// /// The relationship ID. ///The relationship with ID 'id' or null if not found. private PackageRelationship GetRelationshipHelper(string id) { ThrowIfObjectDisposed(); ThrowIfWriteOnly(); if (id == null) throw new ArgumentNullException("id"); InternalRelationshipCollection.ThrowIfInvalidXsdId(id); EnsureRelationships(); return _relationships.GetRelationship(id); } ////// Returns a collection of all the Relationships that are /// owned by the package based on the filter string. /// ///private PackageRelationshipCollection GetRelationshipsHelper(string filterString) { ThrowIfObjectDisposed(); ThrowIfWriteOnly(); EnsureRelationships(); //Internally null is used to indicate that no filter string was specified and //and all the relationships should be returned. return new PackageRelationshipCollection(_relationships, filterString); } #endregion Private Methods //----------------------------------------------------- // // Private Fields // //----------------------------------------------------- #region Private Members // Default values for the Package.Open method overloads private static readonly FileMode _defaultFileMode = FileMode.OpenOrCreate; private static readonly FileAccess _defaultFileAccess = FileAccess.ReadWrite; private static readonly FileShare _defaultFileShare = FileShare.None; private static readonly FileMode _defaultStreamMode = FileMode.Open; private static readonly FileAccess _defaultStreamAccess = FileAccess.Read; private bool _inStreamingCreation; // false by default private FileAccess _openFileAccess; private bool _disposed; private SortedList _partList; private PackagePartCollection _partCollection; private InternalRelationshipCollection _relationships; private PartBasedPackageProperties _packageProperties; #endregion Private Members //----------------------------------------------------- // // Private Class // //------------------------------------------------------ #region Private Class: Restricted Stream /// /// This implementation of Stream class is a simple wrapper to restrict the /// read or write access to the underlying stream, as per user request. /// No validation for the stream method calls is done in this wrapper, the calls /// are passed onto the underlying stream object, which should do the /// validation as required. /// private sealed class RestrictedStream : Stream { #region Constructor ////// Constructor /// /// /// internal RestrictedStream(Stream stream, FileAccess access) { if (stream == null) throw new ArgumentNullException("stream"); //Verifying if the FileAccess enum is valid //This constructor will never be called with FileAccess.ReadWrite Debug.Assert(access==FileAccess.Read || access == FileAccess.Write, "The constructor of this private class is expected to be called with FileAccess.Read or FileAccess.Write"); _stream = stream; if (access == FileAccess.Read) { _canRead = true; _canWrite = false; } else if (access == FileAccess.Write) { _canRead = false; _canWrite = true; } } #endregion Constructor #region Properties ////// Member of the abstract Stream class /// ///Bool, true if the stream can be read from, else false public override bool CanRead { get { if(!_disposed) return _canRead; else return false; } } ////// Member of the abstract Stream class /// ///Bool, true if the stream can be seeked, else false public override bool CanSeek { get { if(!_disposed) return _stream.CanSeek; else return false; } } ////// Member of the abstract Stream class /// ///Bool, true if the stream can be written to, else false public override bool CanWrite { get { if(!_disposed) return _canWrite; else return false; } } ////// Member of the abstract Stream class /// ///Long value indicating the length of the stream public override long Length { get { ThrowIfStreamDisposed(); return _stream.Length; } } ////// Member of the abstract Stream class /// ///Long value indicating the current position in the stream public override long Position { get { ThrowIfStreamDisposed(); return _stream.Position; } set { ThrowIfStreamDisposed(); _stream.Position = value; } } #endregion Properties #region Methods ////// Member of the abstract Stream class /// /// only zero is supported /// only SeekOrigin.Begin is supported ///zero public override long Seek(long offset, SeekOrigin origin) { ThrowIfStreamDisposed(); return _stream.Seek(offset, origin); } ////// Member of the abstract Stream class /// /// public override void SetLength(long newLength) { ThrowIfStreamDisposed(); if (_canWrite) _stream.SetLength(newLength); else throw new NotSupportedException(SR.Get(SRID.ReadOnlyStream)); } ////// Member of the abstract Stream class /// /// /// /// ////// /// The standard Stream.Read semantics, and in particular the restoration of the current /// position in case of an exception, is implemented by the underlying stream. /// public override int Read(byte[] buffer, int offset, int count) { ThrowIfStreamDisposed(); if (_canRead) return _stream.Read(buffer, offset, count); else throw new NotSupportedException(SR.Get(SRID.WriteOnlyStream)); } ////// Member of the abstract Stream class /// /// /// /// public override void Write(byte[] buf, int offset, int count) { ThrowIfStreamDisposed(); if (_canWrite) _stream.Write(buf, offset, count); else throw new NotSupportedException(SR.Get(SRID.ReadOnlyStream)); } ////// Member of the abstract Stream class /// public override void Flush() { ThrowIfStreamDisposed(); if (_canWrite) _stream.Flush(); } #endregion Methods //----------------------------------------------------- // // Protected Methods // //------------------------------------------------------ ////// Dispose(bool) /// /// protected override void Dispose(bool disposing) { try { if (disposing) { if (!_disposed) { _stream.Close(); } } } finally { _disposed = true; base.Dispose(disposing); } } #region Private Methods private void ThrowIfStreamDisposed() { if (_disposed) throw new ObjectDisposedException(null, SR.Get(SRID.StreamObjectDisposed)); } #endregion Private Methods #region Private Variables private Stream _stream; private bool _canRead; private bool _canWrite; private bool _disposed; #endregion Private Variables } #endregion Private Class: Restricted Stream } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved. //------------------------------------------------------------------------------ // //// Copyright (C) Microsoft Corporation. All rights reserved. // // // Description: // This is a base abstract class for Package. This is a part of the // Packaging Layer. // // History: // 01/03/2004: SarjanaS: Initial creation. [Stubs only] // 03/01/2004: SarjanaS: Implemented the functionality for all the members. // 03/17/2004: BruceMac: Initial implementation or PackageRelationship methods // //----------------------------------------------------------------------------- using System; using System.IO; using System.Collections; using System.Collections.Generic; // For SortedList<> using System.Windows; // For Exception strings - SRID using System.Diagnostics; // For Debug.Assert using MS.Internal.IO.Packaging; using MS.Internal.WindowsBase; // for [FriendAccessAllowed] using MS.Internal; // For Invariant.Assert using MS.Utility; namespace System.IO.Packaging { ////// Abstract Base class for the Package. /// This is a part of the Packaging Layer APIs /// public abstract class Package : IDisposable { //----------------------------------------------------- // // Public Constructors // //----------------------------------------------------- #region Protected Constructor ////// Protected constructor for the abstract Base class. /// This is the current contract between the subclass and the base class /// If we decide some registration mechanism then this might change /// /// ///If FileAccess enumeration does not have one of the valid values protected Package(FileAccess openFileAccess) : this(openFileAccess, false /* not streaming by default */) { } ////// Protected constructor for the abstract Base class. /// This is the current contract between the subclass and the base class /// If we decide some registration mechanism then this might change /// /// /// Whether the package is being opened for streaming. ///If FileAccess enumeration does not have one of the valid values protected Package(FileAccess openFileAccess, bool streaming) { ThrowIfFileAccessInvalid(openFileAccess); _openFileAccess = openFileAccess; //PackUriHelper.ValidatedPartUri implements the IComparable interface. _partList = new SortedList(); // initial default is zero _partCollection = null; _disposed = false; _inStreamingCreation = (openFileAccess == FileAccess.Write && streaming); } #endregion Protected Constructor //------------------------------------------------------ // // Public Properties // //----------------------------------------------------- #region Public Properties /// /// Gets the FileAccess with which the package was opened. This is a read only property. /// This property gets set when the package is opened. /// ///FileAccess ///If this Package object has been disposed public FileAccess FileOpenAccess { get { ThrowIfObjectDisposed(); return _openFileAccess; } } ////// The package properties are a subset of the standard OLE property sets /// SummaryInformation and DocumentSummaryInformation, and include such properties /// as Title and Subject. /// ///If this Package object has been disposed public PackageProperties PackageProperties { get { ThrowIfObjectDisposed(); if (_packageProperties == null) _packageProperties = new PartBasedPackageProperties(this); return _packageProperties; } } #endregion Public Properties //------------------------------------------------------ // // Public Methods // //------------------------------------------------------ #region Public Methods #region OpenOnFileMethods ////// Opens a package at the specified Path. This method calls the overload which accepts all the parameters /// with the following defaults - /// FileMode - FileMode.OpenOrCreate, /// FileAccess - FileAccess.ReadWrite /// FileShare - FileShare.None /// /// Path to the package ///Package ///If path parameter is null public static Package Open(string path) { return Open(path, _defaultFileMode, _defaultFileAccess, _defaultFileShare); } ////// Opens a package at the specified Path in the given mode. This method calls the overload which /// accepts all the parameters with the following defaults - /// FileAccess - FileAccess.ReadWrite /// FileShare - FileShare.None /// /// Path to the package /// FileMode in which the package should be opened ///Package ///If path parameter is null ///If FileMode enumeration [packageMode] does not have one of the valid values public static Package Open(string path, FileMode packageMode) { return Open(path, packageMode, _defaultFileAccess, _defaultFileShare); } ////// Opens a package at the specified Path in the given mode with the specified access. This method calls /// the overload which accepts all the parameters with the following defaults - /// FileShare - FileShare.None /// /// Path to the package /// FileMode in which the package should be opened /// FileAccess with which the package should be opened ///Package ///If path parameter is null ///If FileMode enumeration [packageMode] does not have one of the valid values ///If FileAccess enumeration [packageAccess] does not have one of the valid values public static Package Open(string path, FileMode packageMode, FileAccess packageAccess) { return Open(path, packageMode, packageAccess, _defaultFileShare); } ////// Opens the package with the specified parameters. /// /// Note:- /// Since currently there is no plan to implement a generic registration mechanism, this method /// has some hard coded knowledge about the sub classes. This might change later if we come up /// with a registration mechanism. /// There is no caching mechanism in case the FileShare is specified to ReadWrite/Write. /// We do not have any refresh mechanism, to make sure that the cached parts reflect the actual parts, /// in the case where there might be more than one processes writing to the same underlying package. /// /// Path to the package /// FileMode in which the package should be opened /// FileAccess with which the package should be opened /// FileShare with which the package is opened. ///Package ///InvalidArgumentException - If the combination of the FileMode, /// FileAccess and FileShare parameters is not meaningful. ///If path parameter is null ///If FileMode enumeration [packageMode] does not have one of the valid values ///If FileAccess enumeration [packageAccess] does not have one of the valid values public static Package Open(string path, FileMode packageMode, FileAccess packageAccess, FileShare packageShare) { return Open(path, packageMode, packageAccess, packageShare, false /* not in streaming mode */); } #endregion OpenOnFileMethods #region OpenOnStreamMethods ////// Open a package on this stream. This method calls the overload which accepts all the parameters /// with the following defaults - /// FileMode - FileMode.Open /// FileAccess - FileAccess.Read /// /// Stream on which the package is to be opened ///Package ///If stream parameter is null ///If package to be created should have readwrite/read access and underlying stream is write only ///If package to be created should have readwrite/write access and underlying stream is read only public static Package Open(Stream stream) { return Open(stream, _defaultStreamMode, _defaultStreamAccess); } ////// Open a package on this stream. This method calls the overload which accepts all the parameters /// with the following defaults - /// FileAccess - FileAccess.ReadWrite /// /// Stream on which the package is to be opened /// FileMode in which the package should be opened. ///Package ///If stream parameter is null ///If FileMode enumeration [packageMode] does not have one of the valid values ///If package to be created should have readwrite/read access and underlying stream is write only ///If package to be created should have readwrite/write access and underlying stream is read only public static Package Open(Stream stream, FileMode packageMode) { //If the user is providing a FileMode, in all the modes, except FileMode.Open, //its most likely that the user intends to write to the stream. return Open(stream, packageMode, _defaultFileAccess); } ////// Opens a package on this stream. The package is opened in the specified mode and with the access /// specified. /// /// Stream on which the package is created /// FileMode in which the package is to be opened /// FileAccess on the package that is opened ///Package ///If stream parameter is null ///If FileMode enumeration [packageMode] does not have one of the valid values ///If FileAccess enumeration [packageAccess] does not have one of the valid values ///If package to be created should have readwrite/read access and underlying stream is write only ///If package to be created should have readwrite/write access and underlying stream is read only public static Package Open(Stream stream, FileMode packageMode, FileAccess packageAccess) { return Open(stream, packageMode, packageAccess, false /* not in streaming mode */); } #endregion OpenOnStreamMethods #region PackagePart Methods ////// Creates a new part in the package. An empty stream corresponding to this part will be created in the /// package. If a part with the specified uri already exists then we throw an exception. /// This methods will call the CreatePartCore method which will create the actual PackagePart in the package. /// /// Uri of the PackagePart that is to be added /// ContentType of the stream to be added ////// If this Package object has been disposed ///If the package is readonly, it cannot be modified ///If partUri parameter is null ///If contentType parameter is null ///If partUri parameter does not conform to the valid partUri syntax ///If a PackagePart with the given partUri already exists in the Package public PackagePart CreatePart(Uri partUri, string contentType) { return CreatePart(partUri, contentType, CompressionOption.NotCompressed); } ////// Creates a new part in the package. An empty stream corresponding to this part will be created in the /// package. If a part with the specified uri already exists then we throw an exception. /// This methods will call the CreatePartCore method which will create the actual PackagePart in the package. /// /// Uri of the PackagePart that is to be added /// ContentType of the stream to be added /// CompressionOption describing compression configuration /// for the new part. This compression apply only to the part, it doesn't affect relationship parts or related parts. /// This parameter is optional. ////// If this Package object has been disposed ///If the package is readonly, it cannot be modified ///If partUri parameter is null ///If contentType parameter is null ///If partUri parameter does not conform to the valid partUri syntax ///If CompressionOption enumeration [compressionOption] does not have one of the valid values ///If a PackagePart with the given partUri already exists in the Package public PackagePart CreatePart(Uri partUri, string contentType, CompressionOption compressionOption) { ThrowIfObjectDisposed(); ThrowIfReadOnly(); if (partUri == null) throw new ArgumentNullException("partUri"); if (contentType == null) throw new ArgumentNullException("contentType"); ThrowIfCompressionOptionInvalid(compressionOption); PackUriHelper.ValidatedPartUri validatedPartUri = PackUriHelper.ValidatePartUri(partUri); if (_partList.ContainsKey(validatedPartUri)) throw new InvalidOperationException(SR.Get(SRID.PartAlreadyExists)); // Add the part to the _partList if there is no prefix collision // Note: This is the only place where we pass a null to this method for the part and if the // methods returns successfully then we replace the null with an actual part. AddIfNoPrefixCollisionDetected(validatedPartUri, null /* since we don't have a part yet */); PackagePart addedPart = CreatePartCore(validatedPartUri, contentType, compressionOption); //Set the entry for this Uri with the actual part _partList[validatedPartUri] = addedPart; return addedPart; } ////// Returns a part that already exists in the package. If the part /// Corresponding to the URI does not exist in the package then an exception is /// thrown. The method calls the GetPartCore method which actually fetches the part. /// /// ////// If this Package object has been disposed ///If the package is write only, information cannot be retrieved from it ///If partUri parameter is null ///If partUri parameter does not conform to the valid partUri syntax ///If the requested part does not exists in the Package public PackagePart GetPart(Uri partUri) { PackagePart returnedPart = GetPartHelper(partUri); if (returnedPart == null) throw new InvalidOperationException(SR.Get(SRID.PartDoesNotExist)); else return returnedPart; } ////// This is a convenient method to check whether a given part exists in the /// package. This will have a default implementation that will try to retrieve /// the part and then if successful, it will return true. /// If the custom file format has an easier way to do this, they can override this method /// to get this information in a more efficient way. /// /// ////// If this Package object has been disposed ///If the package is write only, information cannot be retrieved from it ///If partUri parameter is null ///If partUri parameter does not conform to the valid partUri syntax public virtual bool PartExists(Uri partUri) { return (GetPartHelper(partUri) != null); } ////// This method will do all the house keeping required when a part is deleted /// Then the DeletePartCore method will be called which will have the actual logic to /// do the work specific to the underlying file format and will actually delete the /// stream corresponding to this part. This method does not throw if the specified /// part does not exist. This is in conformance with the FileInfo.Delete call. /// /// ///If this Package object has been disposed ///If the package is readonly, it cannot be modified ///If partUri parameter is null ///If partUri parameter does not conform to the valid partUri syntax public void DeletePart(Uri partUri) { ThrowIfObjectDisposed(); ThrowIfReadOnly(); ThrowIfInStreamingCreation("DeletePart"); if (partUri == null) throw new ArgumentNullException("partUri"); PackUriHelper.ValidatedPartUri validatedPartUri = (PackUriHelper.ValidatedPartUri)PackUriHelper.ValidatePartUri(partUri); if (_partList.ContainsKey(validatedPartUri)) { //This will get the actual casing of the part that //is stored in the partList which is equivalent to the //partUri provided by the user validatedPartUri = (PackUriHelper.ValidatedPartUri)_partList[validatedPartUri].Uri; _partList[validatedPartUri].IsDeleted = true; _partList[validatedPartUri].Close(); //Call the Subclass to delete the part //!!Important Note: The order of this call is important as one of the //sub-classes - ZipPackage relies upon the abstract layer to be //able to provide the ZipPackagePart in order to do the proper //clean up and delete operation. //The dependency is in ZipPackagePart.DeletePartCore method. //Ideally we would have liked to avoid this kind of a restriction //but due to the current class interfaces and data structure ownerships //between these objects, it tough to re-design at this point. DeletePartCore(validatedPartUri); //Finally remove it from the list of parts in the cache _partList.Remove(validatedPartUri); } else //If the part is not in memory we still call the underlying layer //to delete the part if it exists DeletePartCore(validatedPartUri); if (PackUriHelper.IsRelationshipPartUri(validatedPartUri)) { //We clear the in-memory data structure corresponding to that relationship part //This will ensure that the intention of the user to delete the part, is respected. //And thus we will not try to recreate it just in case there was some data in the //memory structure. Uri owningPartUri = PackUriHelper.GetSourcePartUriFromRelationshipPartUri(validatedPartUri); //Package-level relationships in /_rels/.rels if (Uri.Compare(owningPartUri, PackUriHelper.PackageRootUri, UriComponents.SerializationInfoString, UriFormat.UriEscaped, StringComparison.Ordinal)==0) { //Clear any data in memory this.ClearRelationships(); } else { //Clear any data in memory if (this.PartExists(owningPartUri)) { PackagePart owningPart = this.GetPart(owningPartUri); owningPart.ClearRelationships(); } } } else { // remove any relationship part DeletePart(PackUriHelper.GetRelationshipPartUri(validatedPartUri)); } } ////// This returns a collection of all the Parts within the package. /// ////// If this Package object has been disposed ///If the package is writeonly, no information can be retrieved from it public PackagePartCollection GetParts() { ThrowIfObjectDisposed(); ThrowIfWriteOnly(); //Ideally we should decide whether we should query the underlying layer for parts based on the //FileShare enum. But since we do not have that information, currently the design is to just //query the underlying layer once. //Note: //Currently the incremental behavior for GetPart method is not consistent with the GetParts method //which just queries the underlying layer once. if (_partCollection == null) { PackagePart[] parts = GetPartsCore(); //making sure that we get a valid array Debug.Assert((parts != null), "Subclass is expected to return an array [an empty one if there are no parts] as a result of GetPartsCore method call. "); PackUriHelper.ValidatedPartUri partUri; //We need this dictionary to detect any collisions that might be present in the //list of parts that was given to us from the underlying physical layer, as more than one //partnames can be mapped to the same normalized part. //Note: We cannot use the _partList member variable, as that gets updated incrementally and so its //not possible to find the collisions using that list. //PackUriHelper.ValidatedPartUri implements the IComparable interface. DictionaryseenPartUris = new Dictionary (parts.Length); for (int i = 0; i < parts.Length; i++) { partUri = (PackUriHelper.ValidatedPartUri)parts[i].Uri; if (seenPartUris.ContainsKey(partUri)) throw new FileFormatException(SR.Get(SRID.BadPackageFormat)); else { // Add the part to the list of URIs that we have already seen seenPartUris.Add(partUri, parts[i]); if (!_partList.ContainsKey(partUri)) { // Add the part to the _partList if there is no prefix collision AddIfNoPrefixCollisionDetected(partUri, parts[i]); } } } _partCollection = new PackagePartCollection(_partList); } return _partCollection; } #endregion PackagePart Methods #region IDisposable Methods /// /// Member of the IDisposable interface. This method will clean up all the resources. /// It calls the Flush method to make sure that all the changes made get persisted. /// Note - subclasses should only override Dispose(bool) if they have resources to release. /// See the Design Guidelines for the Dispose() pattern. /// void IDisposable.Dispose() { if (!_disposed) { try { // put our house in order before involving the subclass // close core properties // This method will write out the core properties to the stream // In non-streaming mode - These will get flushed to the disk as a part of the DoFlush operation if (_packageProperties != null) _packageProperties.Close(); // flush relationships if (InStreamingCreation) ClosePackageRelationships(); else FlushRelationships(); //Write out the Relationship XML for the parts //These streams will get flushed in the DoClose operation. DoOperationOnEachPart(DoCloseRelationshipsXml); // Close all the parts that are currently open DoOperationOnEachPart(DoClose); // start the dispose chain Dispose(true); } finally { // do this no matter what (handles case of poorly behaving subclass that doesn't call back into Dispose(bool) _disposed = true; } //Since all the resources we care about are freed at this point. GC.SuppressFinalize(this); } } #endregion IDisposable Methods #region Other Methods ////// Closes the package and all the underlying parts and relationships. /// Calls the Dispose Method, since they have the same semantics /// public void Close() { ((IDisposable)this).Dispose(); } ////// Flushes the contents of the parts and the relationships to the package. /// This method will call the FlushCore method which will do the actual flushing of contents. /// ///If this Package object has been disposed ///If the package is readonly, it cannot be modified public void Flush() { ThrowIfObjectDisposed(); ThrowIfReadOnly(); // Flush core properties (in streaming production, has to be done before parts get flushed). // Write core properties (in streaming production, has to be done before parts get flushed). // This call will write out the xml for the core properties to the stream // In non-streaming mode - These properties will get flushed to disk as a part of the DoFlush operation if (_packageProperties != null) _packageProperties.Flush(); // Write package relationships XML to the relationship part stream. // These will get flushed to disk as a part of the DoFlush operation if (InStreamingCreation) FlushPackageRelationships(); // Create a piece. else FlushRelationships(); // Flush into .rels part. //Write out the Relationship XML for the parts //These streams will get flushed in the DoFlush operation. DoOperationOnEachPart(DoWriteRelationshipsXml); // Flush all the parts that are currently open. // This will flush part relationships. DoOperationOnEachPart(DoFlush); FlushCore(); } #endregion Other Methods #region PackageRelationship Methods ////// Creates a relationship at the Package level with the Target PackagePart specified as the Uri /// /// Target's URI /// Enumeration indicating the base uri for the target uri /// PackageRelationship type, having uri like syntax that is used to /// uniquely identify the role of the relationship ////// If this Package object has been disposed ///If the package is readonly, it cannot be modified ///If parameter "targetUri" is null ///If parameter "relationshipType" is null ///If parameter "targetMode" enumeration does not have a valid value ///If TargetMode is TargetMode.Internal and the targetUri is an absolute Uri ///If relationship is being targeted to a relationship part public PackageRelationship CreateRelationship(Uri targetUri, TargetMode targetMode, string relationshipType) { return CreateRelationship(targetUri, targetMode, relationshipType, null); } ////// Creates a relationship at the Package level with the Target PackagePart specified as the Uri /// /// Target's URI /// Enumeration indicating the base uri for the target uri /// PackageRelationship type, having uri like syntax that is used to /// uniquely identify the role of the relationship /// String that conforms to the xsd:ID datatype. Unique across the source's /// relationships. Null is OK (ID will be generated). An empty string is an invalid XML ID. ////// If this Package object has been disposed ///If the package is readonly, it cannot be modified ///If parameter "targetUri" is null ///If parameter "relationshipType" is null ///If parameter "targetMode" enumeration does not have a valid value ///If TargetMode is TargetMode.Internal and the targetUri is an absolute Uri ///If relationship is being targeted to a relationship part ///If parameter "id" is not a valid Xsd Id ///If an id is provided in the method, and its not unique public PackageRelationship CreateRelationship(Uri targetUri, TargetMode targetMode, string relationshipType, String id) { ThrowIfObjectDisposed(); ThrowIfReadOnly(); EnsureRelationships(); //All parameter validation is done in the following call return _relationships.Add(targetUri, targetMode, relationshipType, id); } ////// Deletes a relationship from the Package. This is done based on the /// relationship's ID. The target PackagePart is not affected by this operation. /// /// The ID of the relationship to delete. An invalid ID will not /// throw an exception, but nothing will be deleted. ///If this Package object has been disposed ///If the package is readonly, it cannot be modified ///If parameter "id" is null ///If parameter "id" is not a valid Xsd Id public void DeleteRelationship(String id) { ThrowIfObjectDisposed(); ThrowIfReadOnly(); ThrowIfInStreamingCreation("DeleteRelationship"); if (id == null) throw new ArgumentNullException("id"); InternalRelationshipCollection.ThrowIfInvalidXsdId(id); EnsureRelationships(); _relationships.Delete(id); } ////// Returns a collection of all the Relationships that are /// owned by the package /// ////// If this Package object has been disposed ///If the package is write only, no information can be retrieved from it public PackageRelationshipCollection GetRelationships() { //All the validations for dispose and file access are done in the //GetRelationshipsHelper method. return GetRelationshipsHelper(null); } ////// Returns a collection of filtered Relationships that are /// owned by the package /// The filter string is compared with the type of the relationships /// in a case sensitive and culture ignorant manner. /// ////// If this Package object has been disposed ///If the package is write only, no information can be retrieved from it ///If parameter "relationshipType" is null ///If parameter "relationshipType" is an empty string public PackageRelationshipCollection GetRelationshipsByType(string relationshipType) { //These checks are made in the GetRelationshipsHelper as well, but we make them //here as we need to perform parameter validation ThrowIfObjectDisposed(); ThrowIfWriteOnly(); if (relationshipType == null) throw new ArgumentNullException("relationshipType"); InternalRelationshipCollection.ThrowIfInvalidRelationshipType(relationshipType); return GetRelationshipsHelper(relationshipType); } ////// Retrieve a relationship per ID. /// /// The relationship ID. ///The relationship with ID 'id' or throw an exception if not found. ///If this Package object has been disposed ///If the package is write only, no information can be retrieved from it ///If parameter "id" is null ///If parameter "id" is not a valid Xsd Id ///If the requested relationship does not exist in the Package public PackageRelationship GetRelationship(string id) { //All the validations for dispose and file access are done in the //GetRelationshipHelper method. PackageRelationship returnedRelationship = GetRelationshipHelper(id); if (returnedRelationship == null) throw new InvalidOperationException(SR.Get(SRID.PackageRelationshipDoesNotExist)); else return returnedRelationship; } ////// Returns whether there is a relationship with the specified ID. /// /// The relationship ID. ///true iff a relationship with ID 'id' is defined on this source. ///If this Package object has been disposed ///If the package is write only, no information can be retrieved from it ///If parameter "id" is null ///If parameter "id" is not a valid Xsd Id public bool RelationshipExists(string id) { //All the validations for dispose and file access are done in the //GetRelationshipHelper method. return (GetRelationshipHelper(id) != null); } #endregion PackageRelationship Methods #endregion Public Methods #region Protected Abstract Methods ////// This method is for custom implementation corresponding to the underlying file format. /// This method will actually add a new part to the package. An empty part should be /// created as a result of this call. /// /// /// /// ///protected abstract PackagePart CreatePartCore(Uri partUri, string contentType, CompressionOption compressionOption); /// /// This method is for custom implementation corresponding to the underlying file format. /// This method will actually return the part after reading the actual physical bits. /// If the PackagePart does not exists in the underlying package then this method should return a null. /// This method must not throw an exception if a part does not exist. /// /// ///protected abstract PackagePart GetPartCore(Uri partUri); /// /// This method is for custom implementation corresponding to the underlying file format. /// This method will actually delete the part from the underlying package. /// This method should not throw if the specified part does not exist. /// This is in conformance with the FileInfo.Delete call. /// /// protected abstract void DeletePartCore(Uri partUri); ////// This method is for custom implementation corresponding to the underlying file format. /// This is the method that knows how to get the actual parts. If there are no parts, /// this method should return an empty array. /// ///protected abstract PackagePart[] GetPartsCore(); /// /// This method is for custom implementation corresponding to the underlying file format. /// This method should be used to dispose the resources that are specific to the file format. /// Also everything should be flushed to the disc before closing the package. /// ///Subclasses that manage non-memory resources should override this method and free these resources. /// Any override should be careful to always call base.Dispose(disposing) to ensure orderly cleanup. protected virtual void Dispose(bool disposing) { if (!_disposed && disposing) { _partList.Clear(); if (_packageProperties != null) { _packageProperties.Dispose(); _packageProperties = null; } //release objects _partList = null; _partCollection = null; _relationships = null; _disposed = true; } } ////// This method is for custom implementation corresponding to the underlying file format. /// This method flushes the contents of the package to the disc. /// protected abstract void FlushCore(); #endregion Protected Abstract Methods //----------------------------------------------------- // // Internal Constructors // //------------------------------------------------------ // None //----------------------------------------------------- //----------------------------------------------------- // // Internal Properties // //----------------------------------------------------- #region Internal Properties ////// true iff the package was opened for streaming. /// internal bool InStreamingCreation { get { return _inStreamingCreation; } } #endregion Internal Properties //------------------------------------------------------ // // Internal Methods // //----------------------------------------------------- #region Internal Methods // Some operations are not supported while producing a package in streaming mode. internal void ThrowIfInStreamingCreation(string methodName) { if (_inStreamingCreation) throw new IOException(SR.Get(SRID.OperationIsNotSupportedInStreamingProduction, methodName)); } // Some operations are supported only while producing a package in streaming mode. internal void ThrowIfNotInStreamingCreation(string methodName) { if (!InStreamingCreation) throw new IOException(SR.Get(SRID.MethodAvailableOnlyInStreamingCreation, methodName)); } //If the container is readonly then we cannot add/delete to it internal void ThrowIfReadOnly() { if (_openFileAccess == FileAccess.Read) throw new IOException(SR.Get(SRID.CannotModifyReadOnlyContainer)); } // If the container is writeonly, parts cannot be retrieved from it internal void ThrowIfWriteOnly() { if (_openFileAccess == FileAccess.Write) throw new IOException(SR.Get(SRID.CannotRetrievePartsOfWriteOnlyContainer)); } // return true to continue internal delegate bool PartOperation(PackagePart p); internal static void ThrowIfFileModeInvalid(FileMode mode) { //We do the enum check as suggested by the following condition for performance reasons. if (mode < FileMode.CreateNew || mode > FileMode.Append) throw new ArgumentOutOfRangeException("mode"); } internal static void ThrowIfFileAccessInvalid(FileAccess access) { //We do the enum check as suggested by the following condition for performance reasons. if (access < FileAccess.Read || access > FileAccess.ReadWrite) throw new ArgumentOutOfRangeException("access"); } internal static void ThrowIfCompressionOptionInvalid(CompressionOption compressionOption) { //We do the enum check as suggested by the following condition for performance reasons. if (compressionOption < CompressionOption.NotCompressed || compressionOption > CompressionOption.SuperFast) throw new ArgumentOutOfRangeException("compressionOption"); } #region Write-time streaming API ////// This method gives the possibility of opening a package in streaming mode. /// When 'streaming' is true, the only allowed file modes are Create and CreateNew, /// the only allowed file access is Write and the only allowed FileShare values are /// Null and Read. /// /// Path to the package. /// FileMode in which the package should be opened. /// FileAccess with which the package should be opened. /// FileShare with which the package is opened. /// Whether to allow the creation of part pieces while enforcing write-once access. ///Package ///If path parameter is null ///If FileAccess enumeration [packageAccess] does not have one of the valid values ///If FileMode enumeration [packageMode] does not have one of the valid values internal static Package Open( string path, FileMode packageMode, FileAccess packageAccess, FileShare packageShare, bool streaming) { EventTrace.NormalTraceEvent(EventTraceGuidId.DRXOPENPACKAGEGUID, EventType.StartEvent); if (path == null) throw new ArgumentNullException("path"); ThrowIfFileModeInvalid(packageMode); ThrowIfFileAccessInvalid(packageAccess); ValidateStreamingAccess(packageMode, packageAccess, packageShare, streaming); //Note: FileShare enum is not being verfied at this stage, as we do not interpret the flag in this //code at all and just pass it on to the next layer, where the necessary validation can be //performed. Also, there is no meaningful way to check this parameter at this layer, as the //FileShare enumeration is a set of flags and flags/Bit-fields can be combined using a //bitwise OR operation to create different values, and validity of these values is specific to //the actual physical implementation. //Verify if this is valid for filenames FileInfo packageFileInfo = new FileInfo(path); Package package = new ZipPackage(packageFileInfo.FullName, packageMode, packageAccess, packageShare, streaming); if (!package._inStreamingCreation) // No read operation in streaming production. { //We need to get all the parts if any exists from the underlying file //so that we have the names in the Normalized form in our in-memory //data structures. //Note: If ever this call is removed, each individual call to GetPartCore, //may result in undefined behavior as the underlying ZipArchive, maintains the //files list as being case-sensitive. if (package.FileOpenAccess == FileAccess.ReadWrite || package.FileOpenAccess == FileAccess.Read) package.GetParts(); } EventTrace.NormalTraceEvent(EventTraceGuidId.DRXOPENPACKAGEGUID, EventType.EndEvent); return package; } ////// This method gives the possibility of opening a package in streaming mode. /// When 'streaming' is true, the only allowed file modes are Create and CreateNew, /// and the only allowed file access is Write. /// /// Stream on which the package is created /// FileMode in which the package is to be opened /// FileAccess on the package that is opened /// Whether to allow the creation of part pieces while enforcing write-once access. ///Package ///If stream parameter is null ///If FileMode enumeration [packageMode] does not have one of the valid values ///If FileAccess enumeration [packageAccess] does not have one of the valid values ///If package to be created should have readwrite/read access and underlying stream is write only ///If package to be created should have readwrite/write access and underlying stream is read only [FriendAccessAllowed] internal static Package Open(Stream stream, FileMode packageMode, FileAccess packageAccess, bool streaming) { EventTrace.NormalTraceEvent(EventTraceGuidId.DRXOPENPACKAGEGUID, EventType.StartEvent); if (stream == null) throw new ArgumentNullException("stream"); ValidateStreamingAccess(packageMode, packageAccess, null /* no FileShare info */, streaming); //FileMode and FileAccess Enums are validated in the following call Stream ensuredStream = ValidateModeAndAccess(stream, packageMode, packageAccess); Package package; // Today the Open(Stream) method is purely used for streams of Zip file format as // that is the default underlying file format mapper implemented. package = new ZipPackage(ensuredStream, packageMode, packageAccess, streaming); if (!package._inStreamingCreation) // No read operation in streaming production. { //We need to get all the parts if any exists from the underlying file //so that we have the names in the Normalized form in our in-memory //data structures. //Note: If ever this call is removed, each individual call to GetPartCore, //may result in undefined behavior as the underlying ZipArchive, maintains the //files list as being case-sensitive. if(package.FileOpenAccess == FileAccess.ReadWrite || package.FileOpenAccess == FileAccess.Read) package.GetParts(); } EventTrace.NormalTraceEvent(EventTraceGuidId.DRXOPENPACKAGEGUID, EventType.EndEvent); return package; } ////// Write a nonterminal piece for /_rels/.rels. /// internal void FlushPackageRelationships() { ThrowIfNotInStreamingCreation("FlushPackageRelationships"); if (_relationships == null) return; // nothing to flush _relationships.Flush(); } ////// Write a terminal piece for /_rels/.rels. /// internal void ClosePackageRelationships() { ThrowIfNotInStreamingCreation("ClosePackageRelationships"); if (_relationships == null) return; // no relationship part _relationships.CloseInStreamingCreationMode(); } #endregion Write-time streaming API #endregion Internal Methods //------------------------------------------------------ // // Internal Events // //------------------------------------------------------ // None //----------------------------------------------------- // // Private Methods // //------------------------------------------------------ #region Private Methods // This method is only when new part is added to the Package object. // This method will throw an exception if the name of the part being added is a // prefix of the name of an existing part. // Example - Say the following parts exist in the package // 1. /abc.xaml // 2. /xyz/pqr/a.jpg // As an example - Adding any of the following parts will throw an exception - // 1. /abc.xaml/new.xaml // 2. /xyz/pqr private void AddIfNoPrefixCollisionDetected(PackUriHelper.ValidatedPartUri partUri, PackagePart part) { //Add the Normalized Uri to the sorted _partList tentatively to see where it will get inserted _partList.Add(partUri, part); //Get the index of the entry at which this part was added int index = _partList.IndexOfKey(partUri); Invariant.Assert(index >= 0, "Given uri must be present in the dictionary"); string normalizedPartName = partUri.NormalizedPartUriString; string precedingPartName = null; string followingPartName = null; if (index > 0) { precedingPartName = _partList.Keys[index - 1].NormalizedPartUriString; } if (index < _partList.Count - 1) { followingPartName = _partList.Keys[index + 1].NormalizedPartUriString; } if ((precedingPartName != null && normalizedPartName.StartsWith(precedingPartName, StringComparison.Ordinal) && normalizedPartName.Length > precedingPartName.Length && normalizedPartName[precedingPartName.Length] == PackUriHelper.ForwardSlashChar) || (followingPartName != null && followingPartName.StartsWith(normalizedPartName, StringComparison.Ordinal) && followingPartName.Length > normalizedPartName.Length && followingPartName[normalizedPartName.Length] == PackUriHelper.ForwardSlashChar)) { //Removing the invalid entry from the _partList. _partList.Remove(partUri); throw new InvalidOperationException(SR.Get(SRID.PartNamePrefixExists)); } } // Test consistency of file opening parameters with the value of 'streaming', and record // whether the streaming mode is for consumption or production. // private static void ValidateStreamingAccess( FileMode packageMode, FileAccess packageAccess, NullablepackageShare, bool streaming) { if (streaming) { if (packageMode == FileMode.Create || packageMode == FileMode.CreateNew) { if (packageAccess != FileAccess.Write) throw new IOException(SR.Get(SRID.StreamingPackageProductionImpliesWriteOnlyAccess)); if ( packageShare != null && packageShare != FileShare.Read && packageShare != FileShare.None) throw new IOException(SR.Get(SRID.StreamingPackageProductionRequiresSingleWriter)); } else { // Blanket exception pending design of streaming consumption. throw new NotSupportedException(SR.Get(SRID.StreamingModeNotSupportedForConsumption)); } } } //Checking if the mode and access parameters are compatible with the provided stream. private static Stream ValidateModeAndAccess(Stream s, FileMode mode, FileAccess access) { ThrowIfFileModeInvalid(mode); ThrowIfFileAccessInvalid(access); //asking for more permissions than the underlying stream. // Stream cannot write, but package to be created should have write access if (!s.CanWrite && (access == FileAccess.ReadWrite || access == FileAccess.Write)) throw new IOException(SR.Get(SRID.IncompatibleModeOrAccess)); //asking for more permissions than the underlying stream. // Stream cannot read, but the package to be created should have read access if (!s.CanRead && (access == FileAccess.ReadWrite || access == FileAccess.Read)) throw new IOException(SR.Get(SRID.IncompatibleModeOrAccess)); //asking for less restricted access to the underlying stream //stream is ReadWrite but the package is either readonly, or writeonly if ((s.CanRead && s.CanWrite) && (access == FileAccess.Read || access == FileAccess.Write)) { return new RestrictedStream(s, access); } else return s; } //Throw if the object is in a disposed state private void ThrowIfObjectDisposed() { if (_disposed == true) throw new ObjectDisposedException(null, SR.Get(SRID.ObjectDisposed)); } private void EnsureRelationships() { // once per package if (_relationships == null) { _relationships = new InternalRelationshipCollection(this); } } //Delete All Package-level Relationships private void ClearRelationships() { if(_relationships!=null) _relationships.Clear(); } //Flush the relationships at package level private void FlushRelationships() { // flush relationships if (_relationships != null && _openFileAccess != FileAccess.Read) { _relationships.Flush(); } } //We do the close or the flush operation per part private void DoOperationOnEachPart(PartOperation operation) { //foreach (PackagePart p in _partList.Values) // p.Close(); - this throws // Make local copy of part names to prevent exception during enumeration when // a new relationship part gets created (flushing relationships can cause part creation). // This code throws in such a case: // // foreach (PackagePart p in _partList.Values) // p.Flush(); // if (_partList.Count > 0) { int partCount = 0; PackUriHelper.ValidatedPartUri[] partKeys = new PackUriHelper.ValidatedPartUri[_partList.Keys.Count]; foreach (PackUriHelper.ValidatedPartUri uri in _partList.Keys) { partKeys[partCount++] = uri; } // this throws an exception in certain cases (when a part has been deleted) // // _partList.Keys.CopyTo(keys, 0); for (int i = 0; i < _partList.Keys.Count; i++) { // Some of these may disappear during above close because the list contains "relationship parts" // and these are removed if their parts' relationship collection is empty // This fails: // _partList[keys[i]].Flush(); PackagePart p; if (_partList.TryGetValue(partKeys[i], out p)) { if (!operation(p)) break; } } } } //We needed to separate the rels parts from the other parts //because if a rels part for a part occured earlier than the part itself in the array, //the rels part would be closed and then when close the part and try to persist the relationships //for the particular part, it would throw an exception private bool DoClose(PackagePart p) { if (!p.IsClosed) { if (PackUriHelper.IsRelationshipPartUri(p.Uri) && PackUriHelper.ComparePartUri(p.Uri, PackageRelationship.ContainerRelationshipPartName) != 0) { //First we close the source part. //Note - we can safely do this as DoClose is being called on all parts. So ultimately we will end up //closing the source part as well. //This logic only takes care of out of order parts. PackUriHelper.ValidatedPartUri owningPartUri = (PackUriHelper.ValidatedPartUri)PackUriHelper.GetSourcePartUriFromRelationshipPartUri(p.Uri); //If the source part for this rels part exists then we close it. PackagePart sourcePart; if (_partList.TryGetValue(owningPartUri, out sourcePart)) sourcePart.Close(); } p.Close(); } return true; } private bool DoFlush(PackagePart p) { p.Flush(); return true; } private bool DoWriteRelationshipsXml(PackagePart p) { if (!p.IsRelationshipPart) { p.FlushRelationships(); } return true; } private bool DoCloseRelationshipsXml(PackagePart p) { if (!p.IsRelationshipPart) { p.CloseRelationships(); } return true; } private PackagePart GetPartHelper(Uri partUri) { ThrowIfObjectDisposed(); ThrowIfWriteOnly(); if (partUri == null) throw new ArgumentNullException("partUri"); PackUriHelper.ValidatedPartUri validatePartUri = PackUriHelper.ValidatePartUri(partUri); if (_partList.ContainsKey(validatePartUri)) return _partList[validatePartUri]; else { //Ideally we should decide whether we should query the underlying layer for the part based on the //FileShare enum. But since we do not have that information, currently the design is to always //ask the underlying layer, this allows for incremental access to the package. //Note: //Currently this incremental behavior for GetPart is not consistent with the GetParts method //which just queries the underlying layer once. PackagePart returnedPart = GetPartCore(validatePartUri); if (returnedPart != null) { // Add the part to the _partList if there is no prefix collision AddIfNoPrefixCollisionDetected(validatePartUri, returnedPart); } return returnedPart; } } /// /// Retrieve a relationship per ID. /// /// The relationship ID. ///The relationship with ID 'id' or null if not found. private PackageRelationship GetRelationshipHelper(string id) { ThrowIfObjectDisposed(); ThrowIfWriteOnly(); if (id == null) throw new ArgumentNullException("id"); InternalRelationshipCollection.ThrowIfInvalidXsdId(id); EnsureRelationships(); return _relationships.GetRelationship(id); } ////// Returns a collection of all the Relationships that are /// owned by the package based on the filter string. /// ///private PackageRelationshipCollection GetRelationshipsHelper(string filterString) { ThrowIfObjectDisposed(); ThrowIfWriteOnly(); EnsureRelationships(); //Internally null is used to indicate that no filter string was specified and //and all the relationships should be returned. return new PackageRelationshipCollection(_relationships, filterString); } #endregion Private Methods //----------------------------------------------------- // // Private Fields // //----------------------------------------------------- #region Private Members // Default values for the Package.Open method overloads private static readonly FileMode _defaultFileMode = FileMode.OpenOrCreate; private static readonly FileAccess _defaultFileAccess = FileAccess.ReadWrite; private static readonly FileShare _defaultFileShare = FileShare.None; private static readonly FileMode _defaultStreamMode = FileMode.Open; private static readonly FileAccess _defaultStreamAccess = FileAccess.Read; private bool _inStreamingCreation; // false by default private FileAccess _openFileAccess; private bool _disposed; private SortedList _partList; private PackagePartCollection _partCollection; private InternalRelationshipCollection _relationships; private PartBasedPackageProperties _packageProperties; #endregion Private Members //----------------------------------------------------- // // Private Class // //------------------------------------------------------ #region Private Class: Restricted Stream /// /// This implementation of Stream class is a simple wrapper to restrict the /// read or write access to the underlying stream, as per user request. /// No validation for the stream method calls is done in this wrapper, the calls /// are passed onto the underlying stream object, which should do the /// validation as required. /// private sealed class RestrictedStream : Stream { #region Constructor ////// Constructor /// /// /// internal RestrictedStream(Stream stream, FileAccess access) { if (stream == null) throw new ArgumentNullException("stream"); //Verifying if the FileAccess enum is valid //This constructor will never be called with FileAccess.ReadWrite Debug.Assert(access==FileAccess.Read || access == FileAccess.Write, "The constructor of this private class is expected to be called with FileAccess.Read or FileAccess.Write"); _stream = stream; if (access == FileAccess.Read) { _canRead = true; _canWrite = false; } else if (access == FileAccess.Write) { _canRead = false; _canWrite = true; } } #endregion Constructor #region Properties ////// Member of the abstract Stream class /// ///Bool, true if the stream can be read from, else false public override bool CanRead { get { if(!_disposed) return _canRead; else return false; } } ////// Member of the abstract Stream class /// ///Bool, true if the stream can be seeked, else false public override bool CanSeek { get { if(!_disposed) return _stream.CanSeek; else return false; } } ////// Member of the abstract Stream class /// ///Bool, true if the stream can be written to, else false public override bool CanWrite { get { if(!_disposed) return _canWrite; else return false; } } ////// Member of the abstract Stream class /// ///Long value indicating the length of the stream public override long Length { get { ThrowIfStreamDisposed(); return _stream.Length; } } ////// Member of the abstract Stream class /// ///Long value indicating the current position in the stream public override long Position { get { ThrowIfStreamDisposed(); return _stream.Position; } set { ThrowIfStreamDisposed(); _stream.Position = value; } } #endregion Properties #region Methods ////// Member of the abstract Stream class /// /// only zero is supported /// only SeekOrigin.Begin is supported ///zero public override long Seek(long offset, SeekOrigin origin) { ThrowIfStreamDisposed(); return _stream.Seek(offset, origin); } ////// Member of the abstract Stream class /// /// public override void SetLength(long newLength) { ThrowIfStreamDisposed(); if (_canWrite) _stream.SetLength(newLength); else throw new NotSupportedException(SR.Get(SRID.ReadOnlyStream)); } ////// Member of the abstract Stream class /// /// /// /// ////// /// The standard Stream.Read semantics, and in particular the restoration of the current /// position in case of an exception, is implemented by the underlying stream. /// public override int Read(byte[] buffer, int offset, int count) { ThrowIfStreamDisposed(); if (_canRead) return _stream.Read(buffer, offset, count); else throw new NotSupportedException(SR.Get(SRID.WriteOnlyStream)); } ////// Member of the abstract Stream class /// /// /// /// public override void Write(byte[] buf, int offset, int count) { ThrowIfStreamDisposed(); if (_canWrite) _stream.Write(buf, offset, count); else throw new NotSupportedException(SR.Get(SRID.ReadOnlyStream)); } ////// Member of the abstract Stream class /// public override void Flush() { ThrowIfStreamDisposed(); if (_canWrite) _stream.Flush(); } #endregion Methods //----------------------------------------------------- // // Protected Methods // //------------------------------------------------------ ////// Dispose(bool) /// /// protected override void Dispose(bool disposing) { try { if (disposing) { if (!_disposed) { _stream.Close(); } } } finally { _disposed = true; base.Dispose(disposing); } } #region Private Methods private void ThrowIfStreamDisposed() { if (_disposed) throw new ObjectDisposedException(null, SR.Get(SRID.StreamObjectDisposed)); } #endregion Private Methods #region Private Variables private Stream _stream; private bool _canRead; private bool _canWrite; private bool _disposed; #endregion Private Variables } #endregion Private Class: Restricted Stream } } // File provided for Reference Use Only by Microsoft Corporation (c) 2007. // Copyright (c) Microsoft Corporation. All rights reserved.
Link Menu
This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- TranslateTransform3D.cs
- SecurityVerifiedMessage.cs
- PeerContact.cs
- StylusPointCollection.cs
- RSAPKCS1KeyExchangeDeformatter.cs
- BuildResultCache.cs
- serverconfig.cs
- DataGridViewCellEventArgs.cs
- BaseServiceProvider.cs
- RemoteDebugger.cs
- InstanceOwner.cs
- WinOEToolBoxItem.cs
- ProfileSettingsCollection.cs
- ReadOnlyTernaryTree.cs
- BookmarkScope.cs
- CaseStatementProjectedSlot.cs
- PageResolution.cs
- PromptEventArgs.cs
- ImportContext.cs
- SortedDictionary.cs
- XomlSerializationHelpers.cs
- AutomationElementCollection.cs
- AdornerLayer.cs
- IDQuery.cs
- DateTimeOffsetConverter.cs
- DocumentReference.cs
- LinearKeyFrames.cs
- SoapAttributes.cs
- JapaneseLunisolarCalendar.cs
- WsdlExporter.cs
- CheckBoxDesigner.cs
- PolyQuadraticBezierSegmentFigureLogic.cs
- XPathNodeIterator.cs
- SmiRecordBuffer.cs
- CompositeFontParser.cs
- IdentityHolder.cs
- StringComparer.cs
- ManifestResourceInfo.cs
- Utils.cs
- HelpEvent.cs
- DataServiceOperationContext.cs
- MemberMaps.cs
- EventListener.cs
- Pen.cs
- AssemblySettingAttributes.cs
- FormViewRow.cs
- DataGridItemCollection.cs
- CheckBox.cs
- GroupedContextMenuStrip.cs
- DataGridViewButtonColumn.cs
- MultipartContentParser.cs
- KeyFrames.cs
- XXXOnTypeBuilderInstantiation.cs
- NameValueConfigurationElement.cs
- ConfigurationElement.cs
- MobileUITypeEditor.cs
- HtmlPhoneCallAdapter.cs
- DataControlHelper.cs
- HashSet.cs
- CanonicalXml.cs
- TextDecoration.cs
- FunctionDetailsReader.cs
- DataGridViewColumnCollectionDialog.cs
- HtmlInputSubmit.cs
- SqlParameterizer.cs
- DocumentXmlWriter.cs
- Variable.cs
- ScopelessEnumAttribute.cs
- Module.cs
- EntityDataSourceViewSchema.cs
- ConfigurationPropertyCollection.cs
- FontDifferentiator.cs
- BaseParser.cs
- EntityContainerEmitter.cs
- RelatedImageListAttribute.cs
- QuinticEase.cs
- NavigationPropertyEmitter.cs
- PasswordBoxAutomationPeer.cs
- MobileCategoryAttribute.cs
- MissingManifestResourceException.cs
- GraphicsState.cs
- TransformFinalBlockRequest.cs
- CharUnicodeInfo.cs
- CatalogPartChrome.cs
- CodeThrowExceptionStatement.cs
- SqlNodeTypeOperators.cs
- DataGridViewRowPostPaintEventArgs.cs
- SecurityTokenSpecification.cs
- HeaderedContentControl.cs
- HostVisual.cs
- EmptyElement.cs
- Cursor.cs
- PaintValueEventArgs.cs
- XmlSchemaGroupRef.cs
- CodeTypeDeclarationCollection.cs
- PriorityRange.cs
- Documentation.cs
- WebPartExportVerb.cs
- WhitespaceRuleLookup.cs
- XmlnsCache.cs