Merge pull request #2735 from xmcclure/early-lookup-addr
[mono.git] / mcs / class / WindowsBase / System.IO.Packaging / Package.cs
index 83b69222c00c59fe3cc8a8bb5740469edd351a93..96ea9a5403b45bdae370068ca187d7763be6b3d8 100644 (file)
 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 //
 // Copyright (c) 2007 Novell, Inc. (http://www.novell.com)
+// Copyright (c) 2011 Xamarin Inc. (http://www.xamarin.com)
 //
 // Authors:
 //     Chris Toshok (toshok@ximian.com)
 //     Alan McGovern (amcgovern@novell.com)
+//     Rolf Bjarne Kvinge (rolf@xamarin.com)
 //
 
 using System;
@@ -35,88 +37,88 @@ namespace System.IO.Packaging {
                internal const string RelationshipContentType = "application/vnd.openxmlformats-package.relationships+xml";
                internal const string RelationshipNamespace = "http://schemas.openxmlformats.org/package/2006/relationships";
                internal static readonly Uri RelationshipUri = new Uri ("/_rels/.rels", UriKind.Relative);
-               
-               PackagePartCollection partsCollection = new PackagePartCollection ();
+
+               PackageProperties packageProperties;
+               PackagePartCollection partsCollection;
+               Dictionary<string, PackageRelationship> relationships;
                PackageRelationshipCollection relationshipsCollection = new PackageRelationshipCollection ();
+               Uri Uri = new Uri ("/", UriKind.Relative);
                
-               Dictionary<string, PackageRelationship> relationships;
+               bool Disposed {
+                       get; set;
+               }
+
+               public FileAccess FileOpenAccess {
+                       get; private set;
+               }
+
+               public PackageProperties PackageProperties {
+                       get {
+                               // PackageProperties are loaded when the relationships are loaded.
+                               // Therefore ensure we've already loaded the relationships.
+                               int count = Relationships.Count;
+                               
+                               if (packageProperties == null) {
+                                       packageProperties = new PackagePropertiesPart ();
+                                       packageProperties.Package = this;
+                               }
+                               return packageProperties;
+                       }
+               }
+
+               PackagePartCollection PartsCollection {
+                       get {
+                               if (partsCollection == null) {
+                                       partsCollection = new PackagePartCollection ();
+                                       partsCollection.Parts.AddRange (GetPartsCore ());
+                               }
+                               return partsCollection;
+                       }
+               }
+               
+               int RelationshipId {
+                       get; set;
+               }
                
                Dictionary<string, PackageRelationship> Relationships {
                        get {
                                if (relationships == null) {
-                                       relationships = new Dictionary<string, PackageRelationship> ();
-                                       
-                                       if (PartExists (RelationshipUri))
-                                               using (Stream stream = GetPart (RelationshipUri).GetStream ())
-                                                       LoadRelationships (relationships, stream);
+                                       LoadRelationships ();
                                }
                                return relationships;
                        }
                }
+
+               bool Streaming {
+                       get; set;
+               }
                
-               Uri Uri = new Uri ("/", UriKind.Relative);
                
-               protected Package (FileAccess fileOpenAccess)
-                       : this (fileOpenAccess, false)
+               protected Package (FileAccess openFileAccess)
+                       : this (openFileAccess, false)
                {
                        
                }
 
-               protected Package (FileAccess fileOpenAccess, bool streaming)
+               protected Package (FileAccess openFileAccess, bool streaming)
                {
-                       FileOpenAccess = fileOpenAccess;
+                       FileOpenAccess = openFileAccess;
                        Streaming = streaming;
                }
 
-               void IDisposable.Dispose ()
-               {
-                       Flush ();
-                       Dispose (true);
-               }
-
-               protected virtual void Dispose (bool disposing)
-               {
-                       // Nothing here needs to be disposed of
-               }
-
-               public FileAccess FileOpenAccess {
-                       get; private set;
-               }
-
-               public PackageProperties PackageProperties {
-                       get; private set;
-               }
-
-               private int RelationshipId {
-                       get; set;
-               }
-
-               private bool Streaming {
-                       get; set;
-               }
 
                internal void CheckIsReadOnly ()
                {
                        if (FileOpenAccess == FileAccess.Read)
                                throw new IOException ("Operation not valid when package is read-only");
                }
-               
+
                public void Close ()
                {
                        // FIXME: Ensure that Flush is actually called before dispose
-                       Flush ();
-                       Dispose (true);
-               }
-
-               public void Flush ()
-               {
-                       // I should have a dirty boolean
-                       if (FileOpenAccess != FileAccess.Read)
-                               FlushCore ();
+                       ((IDisposable) this).Dispose ();
                }
 
-               protected abstract void FlushCore ();
-
                public PackagePart CreatePart (Uri partUri, string contentType)
                {
                        return CreatePart (partUri, contentType, CompressionOption.NotCompressed);
@@ -132,34 +134,11 @@ namespace System.IO.Packaging {
                                throw new InvalidOperationException ("This partUri is already contained in the package");
                        
                        PackagePart part = CreatePartCore (partUri, contentType, compressionOption);
-                       partsCollection.Parts.Add (part);
+                       PartsCollection.Parts.Add (part);
                        return part;
                }
-
-               public void DeletePart (Uri partUri)
-               {
-                       CheckIsReadOnly ();
-                       Check.PartUri (partUri);
-
-                       PackagePart part = GetPart (partUri);
-                       if (part != null)
-                       {
-                               if (part.Package == null)
-                                       throw new InvalidOperationException ("This part has already been removed");
-                               
-                               // FIXME: MS.NET doesn't remove the relationship part
-                               // Instead it throws an exception if you try to use it
-                               if (PartExists (part.RelationshipsPartUri))
-                                       GetPart (part.RelationshipsPartUri).Package = null;
-
-                               part.Package = null;
-                               DeletePartCore (partUri);
-                               partsCollection.Parts.RemoveAll (p => p.Uri == partUri);
-                       }
-               }
-
-               protected abstract PackagePart CreatePartCore (Uri parentUri, string contentType, CompressionOption compressionOption);
-               protected abstract void DeletePartCore (Uri partUri);
+               
+               protected abstract PackagePart CreatePartCore (Uri partUri, string contentType, CompressionOption compressionOption);
 
                public PackageRelationship CreateRelationship (Uri targetUri, TargetMode targetMode, string relationshipType)
                {
@@ -173,8 +152,12 @@ namespace System.IO.Packaging {
 
                internal PackageRelationship CreateRelationship (Uri targetUri, TargetMode targetMode, string relationshipType, string id, bool loading)
                {
-                       CheckIsReadOnly ();
+                       if (!loading)
+                               CheckIsReadOnly ();
+                       
                        Check.TargetUri (targetUri);
+                       if (targetUri.IsAbsoluteUri && targetMode == TargetMode.Internal)
+                               throw new ArgumentException ("TargetUri cannot be absolute for an internal relationship");
                        
                        Check.RelationshipTypeIsValid (relationshipType);
                        Check.IdIsValid (id);
@@ -185,7 +168,7 @@ namespace System.IO.Packaging {
                        PackageRelationship r = new PackageRelationship (id, this, relationshipType, Uri, targetMode, targetUri);
 
                        if (!PartExists (RelationshipUri))
-                               CreatePart (RelationshipUri, RelationshipContentType).IsRelationship = true;
+                               CreatePartCore (RelationshipUri, RelationshipContentType, CompressionOption.NotCompressed).IsRelationship = true;
                        
                        Relationships.Add (r.Id, r);
                        relationshipsCollection.Relationships.Add (r);
@@ -198,6 +181,31 @@ namespace System.IO.Packaging {
                        return r;
                }
 
+               
+               public void DeletePart (Uri partUri)
+               {
+                       CheckIsReadOnly ();
+                       Check.PartUri (partUri);
+
+                       PackagePart part = GetPart (partUri);
+                       if (part != null)
+                       {
+                               if (part.Package == null)
+                                       throw new InvalidOperationException ("This part has already been removed");
+                               
+                               // FIXME: MS.NET doesn't remove the relationship part
+                               // Instead it throws an exception if you try to use it
+                               if (PartExists (part.RelationshipsPartUri))
+                                       GetPart (part.RelationshipsPartUri).Package = null;
+
+                               part.Package = null;
+                               DeletePartCore (partUri);
+                               PartsCollection.Parts.RemoveAll (p => p.Uri == partUri);
+                       }
+               }
+               
+               protected abstract void DeletePartCore (Uri partUri);
+
                public void DeleteRelationship (string id)
                {
                        Check.Id (id);
@@ -212,6 +220,41 @@ namespace System.IO.Packaging {
                        else
                                DeletePart (RelationshipUri);
                }
+               
+               void IDisposable.Dispose ()
+               {
+                       if (!Disposed) {
+                               Flush ();
+                               Dispose (true);
+                               Disposed = true;
+                       }
+               }
+
+               protected virtual void Dispose (bool disposing)
+               {
+                       // Nothing here needs to be disposed of
+               }
+
+               bool flushing = false;
+               public void Flush ()
+               {
+                       if (FileOpenAccess == FileAccess.Read || flushing)
+                               return;
+
+                       flushing = true;
+
+                       // Ensure we've loaded the relationships, parts and properties
+                       int count = Relationships.Count;
+                       
+                       if (packageProperties != null)
+                               packageProperties.Flush ();
+                       
+                       FlushCore ();
+
+                       flushing = false;
+               }
+
+               protected abstract void FlushCore ();
 
                public PackagePart GetPart (Uri partUri)
                {
@@ -223,19 +266,13 @@ namespace System.IO.Packaging {
 
                public PackagePartCollection GetParts ()
                {
-                       partsCollection.Parts.Clear ();
-                       partsCollection.Parts.AddRange (GetPartsCore());
-                       return partsCollection;
+                       PartsCollection.Parts.Clear ();
+                       PartsCollection.Parts.AddRange (GetPartsCore());
+                       return PartsCollection;
                }
 
                protected abstract PackagePart [] GetPartsCore ();
 
-
-               public virtual bool PartExists (Uri partUri)
-               {
-                       return GetPart (partUri) != null;
-               }
-
                public PackageRelationship GetRelationship (string id)
                {
                        return Relationships [id];
@@ -243,8 +280,10 @@ namespace System.IO.Packaging {
 
                public PackageRelationshipCollection GetRelationships ()
                {
+                       // Ensure the Relationships dict is instantiated first.
+                       ICollection <PackageRelationship> rels = Relationships.Values;
                        relationshipsCollection.Relationships.Clear ();
-                       relationshipsCollection.Relationships.AddRange (Relationships.Values);
+                       relationshipsCollection.Relationships.AddRange (rels);
                        return relationshipsCollection;
                }
 
@@ -258,45 +297,61 @@ namespace System.IO.Packaging {
                        return collection;
                }
 
-               public bool RelationshipExists (string id)
-               {
-                       return Relationships.ContainsKey (id);
-               }
-
-               void LoadRelationships (Dictionary<string, PackageRelationship> relationships, Stream stream)
+               void LoadRelationships ()
                {
-                       XmlDocument doc = new XmlDocument ();
-                       doc.Load (stream);
-                       XmlNamespaceManager manager = new XmlNamespaceManager (doc.NameTable);
-                       manager.AddNamespace ("rel", RelationshipNamespace);
+                       relationships = new Dictionary<string, PackageRelationship> ();
 
-                       foreach (XmlNode node in doc.SelectNodes ("/rel:Relationships/*", manager))
-                       {
-                               
-                               TargetMode mode = TargetMode.Internal;
-                               if (node.Attributes["TargetMode"] != null)
-                                       mode = (TargetMode) Enum.Parse (typeof(TargetMode), node.Attributes ["TargetMode"].Value);
+                       if (!PartExists (RelationshipUri))
+                               return;
+                       
+                       using (Stream stream = GetPart (RelationshipUri).GetStream ()) {
+                               XmlDocument doc = new XmlDocument ();
+                               doc.Load (stream);
+                               XmlNamespaceManager manager = new XmlNamespaceManager (doc.NameTable);
+                               manager.AddNamespace ("rel", RelationshipNamespace);
+
+                               foreach (XmlNode node in doc.SelectNodes ("/rel:Relationships/*", manager))
+                               {
+                                       TargetMode mode = TargetMode.Internal;
+                                       if (node.Attributes["TargetMode"] != null)
+                                               mode = (TargetMode) Enum.Parse (typeof(TargetMode), node.Attributes ["TargetMode"].Value);
+
+                                       Uri uri;
+                                       try {
+                                               uri = new Uri (node.Attributes ["Target"].Value.ToString(), UriKind.Relative);
+                                       } catch {
+                                               uri = new Uri (node.Attributes ["Target"].Value.ToString(), UriKind.Absolute);
+                                       }
+                                       CreateRelationship (uri,
+                                                           mode,
+                                                           node.Attributes["Type"].Value.ToString (),
+                                                           node.Attributes["Id"].Value.ToString (),
+                                                           true);
+                               }
                                
-                               CreateRelationship (new Uri (node.Attributes ["Target"].Value.ToString(), UriKind.Relative),
-                                                   mode,
-                                                   node.Attributes["Type"].Value.ToString (),
-                                                   node.Attributes["Id"].Value.ToString (),
-                                                   true);
+                               foreach (PackageRelationship r in relationships.Values) {
+                                       if (r.RelationshipType == System.IO.Packaging.PackageProperties.NSPackagePropertiesRelation) {
+                                               PackagePart part = GetPart (PackUriHelper.ResolvePartUri (Uri, r.TargetUri));
+                                               packageProperties = new PackagePropertiesPart ();
+                                               packageProperties.Package = this;
+                                               packageProperties.Part = part;
+                                               packageProperties.LoadFrom (part.GetStream ());
+                                       }
+                               }
                        }
                }
                
-               private string NextId ()
+               string NextId ()
                {
-                       while (true)
-                       {
-                               string s = RelationshipId.ToString ();
+                       while (true) {
+                               string s = "Re" + RelationshipId.ToString ();
                                if (!Relationships.ContainsKey (s))
                                        return s;
                                
                                RelationshipId++;
                        }
                }
-
+               
                public static Package Open (Stream stream)
                {
                        return Open (stream, FileMode.Open);
@@ -320,7 +375,12 @@ namespace System.IO.Packaging {
 
                public static Package Open (Stream stream, FileMode packageMode, FileAccess packageAccess)
                {
-                       return OpenCore (stream, packageMode, packageAccess);
+                       return Open (stream, packageMode, packageAccess, false);
+               }
+               
+               static Package Open (Stream stream, FileMode packageMode, FileAccess packageAccess, bool ownsStream)
+               {
+                       return OpenCore (stream, packageMode, packageAccess, ownsStream);
                }
 
                public static Package Open (string path, FileMode packageMode, FileAccess packageAccess)
@@ -344,10 +404,10 @@ namespace System.IO.Packaging {
                                throw new FileFormatException ("Stream length cannot be zero with FileMode.Open");
 
                        Stream s = File.Open (path, packageMode, packageAccess, packageShare);
-                       return Open (s, packageMode, packageAccess);
+                       return Open (s, packageMode, packageAccess, true);
                }
 
-               private static Package OpenCore (Stream stream, FileMode packageMode, FileAccess packageAccess)
+               static Package OpenCore (Stream stream, FileMode packageMode, FileAccess packageAccess, bool ownsStream)
                {
                        if ((packageAccess & FileAccess.Read) == FileAccess.Read && !stream.CanRead)
                                throw new IOException ("Stream does not support reading");
@@ -382,19 +442,17 @@ namespace System.IO.Packaging {
                                }
                        }
                        
-                       // FIXME: MS docs say that a ZipPackage is returned by default.
-                       // It looks like if you create a custom package, you cannot use Package.Open.
-
-                       // FIXME: This is a horrible hack. If a package is opened in readonly mode, i
-                       // need to pretend i have read/write mode so i can load the PackageParts
-                       // and PackageRelations. I think the 'streaming' boolean might be used for this.
-                       ZipPackage package = new ZipPackage (packageAccess);
-                       package.PackageStream = stream;
-                       package.FileOpenAccess = FileAccess.ReadWrite;
-                       package.GetParts ();
-                       package.GetRelationships();
-                       package.FileOpenAccess = packageAccess;
-                       return package;
+                       return new ZipPackage (packageAccess, ownsStream, stream);
+               }
+               
+               public virtual bool PartExists (Uri partUri)
+               {
+                       return GetPart (partUri) != null;
+               }
+               
+               public bool RelationshipExists (string id)
+               {
+                       return Relationships.ContainsKey (id);
                }
 
                internal static void WriteRelationships (Dictionary <string, PackageRelationship> relationships, Stream stream)
@@ -419,7 +477,12 @@ namespace System.IO.Packaging {
                                XmlAttribute targetAtt = doc.CreateAttribute ("Target");
                                targetAtt.Value = relationship.TargetUri.ToString ();
                                node.Attributes.Append(targetAtt);
-                               
+
+                               if (relationship.TargetMode != TargetMode.Internal) {
+                                       XmlAttribute modeAtt = doc.CreateAttribute ("TargetMode");
+                                       modeAtt.Value = relationship.TargetMode.ToString ();
+                                       node.Attributes.Append (modeAtt);
+                               }
                                XmlAttribute typeAtt = doc.CreateAttribute ("Type");
                                typeAtt.Value = relationship.RelationshipType;
                                node.Attributes.Append(typeAtt);