[sgen] Refactor collection logging
[mono.git] / mcs / class / WindowsBase / System.IO.Packaging / Package.cs
1 // Permission is hereby granted, free of charge, to any person obtaining
2 // a copy of this software and associated documentation files (the
3 // "Software"), to deal in the Software without restriction, including
4 // without limitation the rights to use, copy, modify, merge, publish,
5 // distribute, sublicense, and/or sell copies of the Software, and to
6 // permit persons to whom the Software is furnished to do so, subject to
7 // the following conditions:
8 // 
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
11 // 
12 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19 //
20 // Copyright (c) 2007 Novell, Inc. (http://www.novell.com)
21 // Copyright (c) 2011 Xamarin Inc. (http://www.xamarin.com)
22 //
23 // Authors:
24 //      Chris Toshok (toshok@ximian.com)
25 //      Alan McGovern (amcgovern@novell.com)
26 //      Rolf Bjarne Kvinge (rolf@xamarin.com)
27 //
28
29 using System;
30 using System.Collections.Generic;
31 using System.Xml;
32
33 namespace System.IO.Packaging {
34
35         public abstract class Package : IDisposable
36         {
37                 internal const string RelationshipContentType = "application/vnd.openxmlformats-package.relationships+xml";
38                 internal const string RelationshipNamespace = "http://schemas.openxmlformats.org/package/2006/relationships";
39                 internal static readonly Uri RelationshipUri = new Uri ("/_rels/.rels", UriKind.Relative);
40
41                 PackageProperties packageProperties;
42                 PackagePartCollection partsCollection;
43                 Dictionary<string, PackageRelationship> relationships;
44                 PackageRelationshipCollection relationshipsCollection = new PackageRelationshipCollection ();
45                 Uri Uri = new Uri ("/", UriKind.Relative);
46                 
47                 bool Disposed {
48                         get; set;
49                 }
50
51                 public FileAccess FileOpenAccess {
52                         get; private set;
53                 }
54
55                 public PackageProperties PackageProperties {
56                         get {
57                                 // PackageProperties are loaded when the relationships are loaded.
58                                 // Therefore ensure we've already loaded the relationships.
59                                 int count = Relationships.Count;
60                                 
61                                 if (packageProperties == null) {
62                                         packageProperties = new PackagePropertiesPart ();
63                                         packageProperties.Package = this;
64                                 }
65                                 return packageProperties;
66                         }
67                 }
68
69                 PackagePartCollection PartsCollection {
70                         get {
71                                 if (partsCollection == null) {
72                                         partsCollection = new PackagePartCollection ();
73                                         partsCollection.Parts.AddRange (GetPartsCore ());
74                                 }
75                                 return partsCollection;
76                         }
77                 }
78                 
79                 int RelationshipId {
80                         get; set;
81                 }
82                 
83                 Dictionary<string, PackageRelationship> Relationships {
84                         get {
85                                 if (relationships == null) {
86                                         LoadRelationships ();
87                                 }
88                                 return relationships;
89                         }
90                 }
91
92                 bool Streaming {
93                         get; set;
94                 }
95                 
96                 
97                 protected Package (FileAccess openFileAccess)
98                         : this (openFileAccess, false)
99                 {
100                         
101                 }
102
103                 protected Package (FileAccess openFileAccess, bool streaming)
104                 {
105                         FileOpenAccess = openFileAccess;
106                         Streaming = streaming;
107                 }
108
109
110                 internal void CheckIsReadOnly ()
111                 {
112                         if (FileOpenAccess == FileAccess.Read)
113                                 throw new IOException ("Operation not valid when package is read-only");
114                 }
115
116                 public void Close ()
117                 {
118                         // FIXME: Ensure that Flush is actually called before dispose
119                         ((IDisposable) this).Dispose ();
120                 }
121
122                 public PackagePart CreatePart (Uri partUri, string contentType)
123                 {
124                         return CreatePart (partUri, contentType, CompressionOption.NotCompressed);
125                 }
126
127                 public PackagePart CreatePart (Uri partUri, string contentType, CompressionOption compressionOption)
128                 {
129                         CheckIsReadOnly ();
130                         Check.PartUri (partUri);
131                         Check.ContentTypeIsValid (contentType);
132
133                         if (PartExists (partUri))
134                                 throw new InvalidOperationException ("This partUri is already contained in the package");
135                         
136                         PackagePart part = CreatePartCore (partUri, contentType, compressionOption);
137                         PartsCollection.Parts.Add (part);
138                         return part;
139                 }
140                 
141                 protected abstract PackagePart CreatePartCore (Uri partUri, string contentType, CompressionOption compressionOption);
142
143                 public PackageRelationship CreateRelationship (Uri targetUri, TargetMode targetMode, string relationshipType)
144                 {
145                         return CreateRelationship (targetUri, targetMode, relationshipType, null);
146                 }
147
148                 public PackageRelationship CreateRelationship (Uri targetUri, TargetMode targetMode, string relationshipType, string id)
149                 {
150                         return CreateRelationship (targetUri, targetMode, relationshipType, id, false);
151                 }
152
153                 internal PackageRelationship CreateRelationship (Uri targetUri, TargetMode targetMode, string relationshipType, string id, bool loading)
154                 {
155                         if (!loading)
156                                 CheckIsReadOnly ();
157                         
158                         Check.TargetUri (targetUri);
159                         if (targetUri.IsAbsoluteUri && targetMode == TargetMode.Internal)
160                                 throw new ArgumentException ("TargetUri cannot be absolute for an internal relationship");
161                         
162                         Check.RelationshipTypeIsValid (relationshipType);
163                         Check.IdIsValid (id);
164
165                         if (id == null)
166                                 id = NextId ();
167
168                         PackageRelationship r = new PackageRelationship (id, this, relationshipType, Uri, targetMode, targetUri);
169
170                         if (!PartExists (RelationshipUri))
171                                 CreatePartCore (RelationshipUri, RelationshipContentType, CompressionOption.NotCompressed).IsRelationship = true;
172                         
173                         Relationships.Add (r.Id, r);
174                         relationshipsCollection.Relationships.Add (r);
175
176                         if (!loading) {
177                                 using (Stream s = GetPart (RelationshipUri).GetStream ())
178                                         WriteRelationships (relationships, s);
179                         }
180                         
181                         return r;
182                 }
183
184                 
185                 public void DeletePart (Uri partUri)
186                 {
187                         CheckIsReadOnly ();
188                         Check.PartUri (partUri);
189
190                         PackagePart part = GetPart (partUri);
191                         if (part != null)
192                         {
193                                 if (part.Package == null)
194                                         throw new InvalidOperationException ("This part has already been removed");
195                                 
196                                 // FIXME: MS.NET doesn't remove the relationship part
197                                 // Instead it throws an exception if you try to use it
198                                 if (PartExists (part.RelationshipsPartUri))
199                                         GetPart (part.RelationshipsPartUri).Package = null;
200
201                                 part.Package = null;
202                                 DeletePartCore (partUri);
203                                 PartsCollection.Parts.RemoveAll (p => p.Uri == partUri);
204                         }
205                 }
206                 
207                 protected abstract void DeletePartCore (Uri partUri);
208
209                 public void DeleteRelationship (string id)
210                 {
211                         Check.Id (id);
212                         CheckIsReadOnly ();
213                         
214                         Relationships.Remove (id);
215
216                         relationshipsCollection.Relationships.RemoveAll (r => r.Id == id);
217                         if (Relationships.Count > 0)
218                                 using (Stream s = GetPart (RelationshipUri).GetStream ())
219                                         WriteRelationships (relationships, s);
220                         else
221                                 DeletePart (RelationshipUri);
222                 }
223                 
224                 void IDisposable.Dispose ()
225                 {
226                         if (!Disposed) {
227                                 Flush ();
228                                 Dispose (true);
229                                 Disposed = true;
230                         }
231                 }
232
233                 protected virtual void Dispose (bool disposing)
234                 {
235                         // Nothing here needs to be disposed of
236                 }
237
238                 bool flushing = false;
239                 public void Flush ()
240                 {
241                         if (FileOpenAccess == FileAccess.Read || flushing)
242                                 return;
243
244                         flushing = true;
245
246                         // Ensure we've loaded the relationships, parts and properties
247                         int count = Relationships.Count;
248                         
249                         if (packageProperties != null)
250                                 packageProperties.Flush ();
251                         
252                         FlushCore ();
253
254                         flushing = false;
255                 }
256
257                 protected abstract void FlushCore ();
258
259                 public PackagePart GetPart (Uri partUri)
260                 {
261                         Check.PartUri (partUri);
262                         return GetPartCore (partUri);
263                 }
264
265                 protected abstract PackagePart GetPartCore (Uri partUri);
266
267                 public PackagePartCollection GetParts ()
268                 {
269                         PartsCollection.Parts.Clear ();
270                         PartsCollection.Parts.AddRange (GetPartsCore());
271                         return PartsCollection;
272                 }
273
274                 protected abstract PackagePart [] GetPartsCore ();
275
276                 public PackageRelationship GetRelationship (string id)
277                 {
278                         return Relationships [id];
279                 }
280
281                 public PackageRelationshipCollection GetRelationships ()
282                 {
283                         // Ensure the Relationships dict is instantiated first.
284                         ICollection <PackageRelationship> rels = Relationships.Values;
285                         relationshipsCollection.Relationships.Clear ();
286                         relationshipsCollection.Relationships.AddRange (rels);
287                         return relationshipsCollection;
288                 }
289
290                 public PackageRelationshipCollection GetRelationshipsByType (string relationshipType)
291                 {
292                         PackageRelationshipCollection collection = new PackageRelationshipCollection ();
293                         foreach (PackageRelationship r in Relationships.Values)
294                                 if (r.RelationshipType == relationshipType)
295                                         collection.Relationships.Add (r);
296                         
297                         return collection;
298                 }
299
300                 void LoadRelationships ()
301                 {
302                         relationships = new Dictionary<string, PackageRelationship> ();
303
304                         if (!PartExists (RelationshipUri))
305                                 return;
306                         
307                         using (Stream stream = GetPart (RelationshipUri).GetStream ()) {
308                                 XmlDocument doc = new XmlDocument ();
309                                 doc.Load (stream);
310                                 XmlNamespaceManager manager = new XmlNamespaceManager (doc.NameTable);
311                                 manager.AddNamespace ("rel", RelationshipNamespace);
312
313                                 foreach (XmlNode node in doc.SelectNodes ("/rel:Relationships/*", manager))
314                                 {
315                                         TargetMode mode = TargetMode.Internal;
316                                         if (node.Attributes["TargetMode"] != null)
317                                                 mode = (TargetMode) Enum.Parse (typeof(TargetMode), node.Attributes ["TargetMode"].Value);
318
319                                         Uri uri;
320                                         try {
321                                                 uri = new Uri (node.Attributes ["Target"].Value.ToString(), UriKind.Relative);
322                                         } catch {
323                                                 uri = new Uri (node.Attributes ["Target"].Value.ToString(), UriKind.Absolute);
324                                         }
325                                         CreateRelationship (uri,
326                                                             mode,
327                                                             node.Attributes["Type"].Value.ToString (),
328                                                             node.Attributes["Id"].Value.ToString (),
329                                                             true);
330                                 }
331                                 
332                                 foreach (PackageRelationship r in relationships.Values) {
333                                         if (r.RelationshipType == System.IO.Packaging.PackageProperties.NSPackagePropertiesRelation) {
334                                                 PackagePart part = GetPart (PackUriHelper.ResolvePartUri (Uri, r.TargetUri));
335                                                 packageProperties = new PackagePropertiesPart ();
336                                                 packageProperties.Package = this;
337                                                 packageProperties.Part = part;
338                                                 packageProperties.LoadFrom (part.GetStream ());
339                                         }
340                                 }
341                         }
342                 }
343                 
344                 string NextId ()
345                 {
346                         while (true) {
347                                 string s = "Re" + RelationshipId.ToString ();
348                                 if (!Relationships.ContainsKey (s))
349                                         return s;
350                                 
351                                 RelationshipId++;
352                         }
353                 }
354                 
355                 public static Package Open (Stream stream)
356                 {
357                         return Open (stream, FileMode.Open);
358                 }
359
360                 public static Package Open (string path)
361                 {
362                         return Open (path, FileMode.OpenOrCreate);
363                 }
364
365                 public static Package Open (Stream stream, FileMode packageMode)
366                 {
367                         FileAccess access = packageMode == FileMode.Open ? FileAccess.Read : FileAccess.ReadWrite;
368                         return Open (stream, packageMode, access);
369                 }
370
371                 public static Package Open (string path, FileMode packageMode)
372                 {
373                         return Open (path, packageMode, FileAccess.ReadWrite);
374                 }
375
376                 public static Package Open (Stream stream, FileMode packageMode, FileAccess packageAccess)
377                 {
378                         return Open (stream, packageMode, packageAccess, false);
379                 }
380                 
381                 static Package Open (Stream stream, FileMode packageMode, FileAccess packageAccess, bool ownsStream)
382                 {
383                         return OpenCore (stream, packageMode, packageAccess, ownsStream);
384                 }
385
386                 public static Package Open (string path, FileMode packageMode, FileAccess packageAccess)
387                 {
388                         return Open (path, packageMode, packageAccess, FileShare.None);
389                 }
390
391                 public static Package Open (string path, FileMode packageMode, FileAccess packageAccess, FileShare packageShare)
392                 {
393                         if (packageShare != FileShare.Read && packageShare != FileShare.None)
394                                 throw new NotSupportedException ("FileShare.Read and FileShare.None are the only supported options");
395
396                         FileInfo info = new FileInfo (path);
397                         
398                         // Bug - MS.NET appears to test for FileAccess.ReadWrite, not FileAccess.Write
399                         if (packageAccess != FileAccess.ReadWrite && !info.Exists)
400                                 throw new ArgumentException ("packageAccess", "Cannot create stream with FileAccess.Read");
401
402                         
403                         if (info.Exists && packageMode == FileMode.OpenOrCreate && info.Length == 0)
404                                 throw new FileFormatException ("Stream length cannot be zero with FileMode.Open");
405
406                         Stream s = File.Open (path, packageMode, packageAccess, packageShare);
407                         return Open (s, packageMode, packageAccess, true);
408                 }
409
410                 static Package OpenCore (Stream stream, FileMode packageMode, FileAccess packageAccess, bool ownsStream)
411                 {
412                         if ((packageAccess & FileAccess.Read) == FileAccess.Read && !stream.CanRead)
413                                 throw new IOException ("Stream does not support reading");
414
415                         if ((packageAccess & FileAccess.Write) == FileAccess.Write && !stream.CanWrite)
416                                 throw new IOException ("Stream does not support reading");
417                         
418                         if (!stream.CanSeek)
419                                 throw new ArgumentException ("stream", "Stream must support seeking");
420                         
421                         if (packageMode == FileMode.Open && stream.Length == 0)
422                                 throw new FileFormatException("Stream length cannot be zero with FileMode.Open");
423
424                         if (packageMode == FileMode.CreateNew && stream.Length > 0)
425                                 throw new IOException ("Cannot use CreateNew when stream contains data");
426
427                         if (packageMode == FileMode.Append || packageMode == FileMode.Truncate)
428                         {
429                                 if (stream.CanWrite)
430                                         throw new NotSupportedException (string.Format("PackageMode.{0} is not supported", packageMode));
431                                 else
432                                         throw new IOException (string.Format("PackageMode.{0} is not supported", packageMode));
433                         }
434
435                         // Test to see if archive is valid
436                         if (stream.Length > 0 && packageAccess == FileAccess.Read) {
437                                 try {
438                                         using (zipsharp.UnzipArchive a = new zipsharp.UnzipArchive (stream)) {
439                                         }
440                                 } catch {
441                                         throw new FileFormatException ("The specified archive is invalid.");
442                                 }
443                         }
444                         
445                         return new ZipPackage (packageAccess, ownsStream, stream);
446                 }
447                 
448                 public virtual bool PartExists (Uri partUri)
449                 {
450                         return GetPart (partUri) != null;
451                 }
452                 
453                 public bool RelationshipExists (string id)
454                 {
455                         return Relationships.ContainsKey (id);
456                 }
457
458                 internal static void WriteRelationships (Dictionary <string, PackageRelationship> relationships, Stream stream)
459                 {
460                         XmlDocument doc = new XmlDocument ();
461                         XmlNamespaceManager manager = new XmlNamespaceManager (doc.NameTable);
462                         manager.AddNamespace ("rel", RelationshipNamespace);
463
464                         doc.AppendChild (doc.CreateNode (XmlNodeType.XmlDeclaration, "", ""));
465                         
466                         XmlNode root = doc.CreateNode (XmlNodeType.Element, "Relationships", RelationshipNamespace);
467                         doc.AppendChild (root);
468                         
469                         foreach (PackageRelationship relationship in relationships.Values)
470                         {
471                                 XmlNode node = doc.CreateNode (XmlNodeType.Element, "Relationship", RelationshipNamespace);
472                                 
473                                 XmlAttribute idAtt = doc.CreateAttribute ("Id");
474                                 idAtt.Value = relationship.Id;
475                                 node.Attributes.Append(idAtt);
476                                 
477                                 XmlAttribute targetAtt = doc.CreateAttribute ("Target");
478                                 targetAtt.Value = relationship.TargetUri.ToString ();
479                                 node.Attributes.Append(targetAtt);
480
481                                 if (relationship.TargetMode != TargetMode.Internal) {
482                                         XmlAttribute modeAtt = doc.CreateAttribute ("TargetMode");
483                                         modeAtt.Value = relationship.TargetMode.ToString ();
484                                         node.Attributes.Append (modeAtt);
485                                 }
486                                 XmlAttribute typeAtt = doc.CreateAttribute ("Type");
487                                 typeAtt.Value = relationship.RelationshipType;
488                                 node.Attributes.Append(typeAtt);
489                                 
490                                 root.AppendChild (node);
491                         }
492
493                         using (XmlTextWriter writer = new XmlTextWriter (stream, System.Text.Encoding.UTF8))
494                                 doc.WriteTo (writer);
495                 }
496         }
497 }
498