7
// Copyright (C) 2007 Novell, Inc (http://www.novell.com)
9
// Permission is hereby granted, free of charge, to any person obtaining
10
// a copy of this software and associated documentation files (the
11
// "Software"), to deal in the Software without restriction, including
12
// without limitation the rights to use, copy, modify, merge, publish,
13
// distribute, sublicense, and/or sell copies of the Software, and to
14
// permit persons to whom the Software is furnished to do so, subject to
15
// the following conditions:
17
// The above copyright notice and this permission notice shall be
18
// included in all copies or substantial portions of the Software.
20
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30
using System.Collections;
33
using System.Xml.Serialization;
34
using System.Collections.Specialized;
35
using Mono.Addins.Serialization;
36
using Mono.Addins.Database;
38
namespace Mono.Addins.Description
40
// This class represent an add-in configuration file. It has properties for getting
41
// all information, and methods for loading and saving files.
42
public class AddinDescription: IBinaryXmlElement
44
XmlDocument configDoc;
46
AddinDatabase ownerDatabase;
59
string sourceAddinFile;
63
bool defaultEnabled = true;
66
ModuleDescription mainModule;
67
ModuleCollection optionalModules;
68
ExtensionNodeSetCollection nodeSets;
69
ConditionTypeDescriptionCollection conditionTypes;
70
ExtensionPointCollection extensionPoints;
71
ExtensionNodeDescription localizer;
74
internal static BinaryXmlTypeMap typeMap;
76
static AddinDescription ()
78
typeMap = new BinaryXmlTypeMap ();
79
typeMap.RegisterType (typeof(AddinDescription), "AddinDescription");
80
typeMap.RegisterType (typeof(Extension), "Extension");
81
typeMap.RegisterType (typeof(ExtensionNodeDescription), "Node");
82
typeMap.RegisterType (typeof(ExtensionNodeSet), "NodeSet");
83
typeMap.RegisterType (typeof(ExtensionNodeType), "NodeType");
84
typeMap.RegisterType (typeof(ExtensionPoint), "ExtensionPoint");
85
typeMap.RegisterType (typeof(ModuleDescription), "ModuleDescription");
86
typeMap.RegisterType (typeof(ConditionTypeDescription), "ConditionType");
87
typeMap.RegisterType (typeof(Condition), "Condition");
88
typeMap.RegisterType (typeof(AddinDependency), "AddinDependency");
89
typeMap.RegisterType (typeof(AssemblyDependency), "AssemblyDependency");
90
typeMap.RegisterType (typeof(NodeTypeAttribute), "NodeTypeAttribute");
91
typeMap.RegisterType (typeof(AddinFileInfo), "FileInfo");
94
internal AddinDatabase OwnerDatabase {
95
get { return ownerDatabase; }
96
set { ownerDatabase = value; }
99
public string AddinFile {
100
get { return sourceAddinFile; }
101
set { sourceAddinFile = value; }
104
public string AddinId {
105
get { return Addin.GetFullId (Namespace, LocalId, Version); }
108
public string LocalId {
109
get { return id != null ? id : string.Empty; }
110
set { id = value; hasUserId = true; }
113
public string Namespace {
114
get { return ns != null ? ns : string.Empty; }
120
if (name != null && name.Length > 0)
124
else if (sourceAddinFile != null)
125
return Path.GetFileNameWithoutExtension (sourceAddinFile);
129
set { name = value; }
132
public string Version {
133
get { return version != null ? version : string.Empty; }
134
set { version = value; }
137
public string CompatVersion {
138
get { return compatVersion != null ? compatVersion : string.Empty; }
139
set { compatVersion = value; }
142
public string Author {
143
get { return author != null ? author : string.Empty; }
144
set { author = value; }
148
get { return url != null ? url : string.Empty; }
152
public string Copyright {
153
get { return copyright != null ? copyright : string.Empty; }
154
set { copyright = value; }
157
public string Description {
158
get { return description != null ? description : string.Empty; }
159
set { description = value; }
162
public string Category {
163
get { return category != null ? category : string.Empty; }
164
set { category = value; }
167
internal string BasePath {
168
get { return basePath != null ? basePath : string.Empty; }
169
set { basePath = value; }
173
get { return isroot; }
174
set { isroot = value; }
177
public bool EnabledByDefault {
178
get { return defaultEnabled; }
179
set { defaultEnabled = value; }
182
internal bool HasUserId {
183
get { return hasUserId; }
184
set { hasUserId = value; }
187
internal bool SupportsVersion (string ver)
189
return Addin.CompareVersions (ver, Version) >= 0 &&
190
(CompatVersion.Length == 0 || Addin.CompareVersions (ver, CompatVersion) <= 0);
193
public StringCollection AllFiles {
195
StringCollection col = new StringCollection ();
196
foreach (string s in MainModule.AllFiles)
199
foreach (ModuleDescription mod in OptionalModules) {
200
foreach (string s in mod.AllFiles)
207
public ModuleDescription MainModule {
209
if (mainModule == null) {
210
if (RootElement == null)
211
mainModule = new ModuleDescription ();
213
mainModule = new ModuleDescription (RootElement);
214
mainModule.SetParent (this);
220
public ModuleCollection OptionalModules {
222
if (optionalModules == null) {
223
optionalModules = new ModuleCollection (this);
224
if (RootElement != null) {
225
foreach (XmlElement mod in RootElement.SelectNodes ("Module"))
226
optionalModules.Add (new ModuleDescription (mod));
229
return optionalModules;
233
public ModuleCollection AllModules {
235
ModuleCollection col = new ModuleCollection (this);
236
col.Add (MainModule);
237
foreach (ModuleDescription mod in OptionalModules)
243
public ExtensionNodeSetCollection ExtensionNodeSets {
245
if (nodeSets == null) {
246
nodeSets = new ExtensionNodeSetCollection (this);
247
if (RootElement != null) {
248
foreach (XmlElement elem in RootElement.SelectNodes ("ExtensionNodeSet"))
249
nodeSets.Add (new ExtensionNodeSet (elem));
256
public ExtensionPointCollection ExtensionPoints {
258
if (extensionPoints == null) {
259
extensionPoints = new ExtensionPointCollection (this);
260
if (RootElement != null) {
261
foreach (XmlElement elem in RootElement.SelectNodes ("ExtensionPoint"))
262
extensionPoints.Add (new ExtensionPoint (elem));
265
return extensionPoints;
269
public ConditionTypeDescriptionCollection ConditionTypes {
271
if (conditionTypes == null) {
272
conditionTypes = new ConditionTypeDescriptionCollection (this);
273
if (RootElement != null) {
274
foreach (XmlElement elem in RootElement.SelectNodes ("ConditionType"))
275
conditionTypes.Add (new ConditionTypeDescription (elem));
278
return conditionTypes;
282
public ExtensionNodeDescription Localizer {
283
get { return localizer; }
284
set { localizer = value; }
287
public ExtensionPoint AddExtensionPoint (string path)
289
ExtensionPoint ep = new ExtensionPoint ();
291
ExtensionPoints.Add (ep);
295
internal ExtensionNodeDescription FindExtensionNode (string path, bool lookInDeps)
297
// Look in the extensions of this add-in
299
foreach (Extension ext in MainModule.Extensions) {
300
if (path.StartsWith (ext.Path + "/")) {
301
string subp = path.Substring (ext.Path.Length).Trim ('/');
302
ExtensionNodeDescriptionCollection nodes = ext.ExtensionNodes;
303
ExtensionNodeDescription node = null;
304
foreach (string p in subp.Split ('/')) {
305
if (p.Length == 0) continue;
309
nodes = node.ChildNodes;
316
if (!lookInDeps || OwnerDatabase == null)
319
// Look in dependencies
321
foreach (Dependency dep in MainModule.Dependencies) {
322
AddinDependency adep = dep as AddinDependency;
323
if (adep == null) continue;
324
Addin ad = OwnerDatabase.GetInstalledAddin (Domain, adep.FullAddinId);
325
if (ad != null && ad.Description != null) {
326
ExtensionNodeDescription node = ad.Description.FindExtensionNode (path, false);
334
XmlElement RootElement {
336
if (configDoc != null)
337
return configDoc.DocumentElement;
343
public string FileName {
344
get { return configFile; }
345
set { configFile = value; }
348
internal string Domain {
349
get { return domain; }
350
set { domain = value; }
353
internal void StoreFileInfo ()
355
ArrayList list = new ArrayList ();
356
foreach (string f in AllFiles) {
357
string file = Path.Combine (this.BasePath, f);
358
AddinFileInfo fi = new AddinFileInfo ();
360
fi.Timestamp = File.GetLastWriteTime (file);
363
fileInfo = list.ToArray ();
366
internal bool FilesChanged ()
368
// Checks if the files of the add-in have changed.
369
if (fileInfo == null)
372
foreach (AddinFileInfo f in fileInfo) {
373
string file = Path.Combine (this.BasePath, f.FileName);
374
if (!File.Exists (file))
376
if (f.Timestamp != File.GetLastWriteTime (file))
383
public void Save (string fileName)
385
configFile = fileName;
391
if (configFile == null)
392
throw new InvalidOperationException ("File name not specified.");
396
using (StreamWriter sw = new StreamWriter (configFile)) {
397
XmlTextWriter tw = new XmlTextWriter (sw);
398
tw.Formatting = Formatting.Indented;
403
public XmlDocument SaveToXml ()
412
throw new InvalidOperationException ("Can't write incomplete description.");
416
if (configDoc == null) {
417
configDoc = new XmlDocument ();
418
configDoc.AppendChild (configDoc.CreateElement ("Addin"));
421
elem = configDoc.DocumentElement;
424
elem.SetAttribute ("id", id);
426
elem.RemoveAttribute ("id");
428
elem.SetAttribute ("version", version);
429
elem.SetAttribute ("namespace", ns);
432
elem.SetAttribute ("isroot", "true");
434
elem.RemoveAttribute ("isroot");
436
// Name will return the file name when HasUserId=false
438
elem.SetAttribute ("name", Name);
440
elem.RemoveAttribute ("name");
442
if (compatVersion != null && compatVersion.Length > 0)
443
elem.SetAttribute ("compatVersion", compatVersion);
445
elem.RemoveAttribute ("compatVersion");
448
elem.RemoveAttribute ("defaultEnabled");
450
elem.SetAttribute ("defaultEnabled", "false");
452
if (author != null && author.Length > 0)
453
elem.SetAttribute ("author", author);
455
elem.RemoveAttribute ("author");
457
if (url != null && url.Length > 0)
458
elem.SetAttribute ("url", url);
460
elem.RemoveAttribute ("url");
462
if (copyright != null && copyright.Length > 0)
463
elem.SetAttribute ("copyright", copyright);
465
elem.RemoveAttribute ("copyright");
467
if (description != null && description.Length > 0)
468
elem.SetAttribute ("description", description);
470
elem.RemoveAttribute ("description");
472
if (category != null && category.Length > 0)
473
elem.SetAttribute ("category", category);
475
elem.RemoveAttribute ("category");
477
if (localizer == null || localizer.Element == null) {
478
// Remove old element if it exists
479
XmlElement oldLoc = (XmlElement) elem.SelectSingleNode ("Localizer");
481
elem.RemoveChild (oldLoc);
483
if (localizer != null)
484
localizer.SaveXml (elem);
486
if (mainModule != null) {
487
mainModule.Element = elem;
488
mainModule.SaveXml (elem);
491
if (optionalModules != null)
492
optionalModules.SaveXml (elem);
494
if (nodeSets != null)
495
nodeSets.SaveXml (elem);
497
if (extensionPoints != null)
498
extensionPoints.SaveXml (elem);
502
public static AddinDescription Read (string configFile)
504
AddinDescription config;
505
using (Stream s = File.OpenRead (configFile)) {
506
config = Read (s, Path.GetDirectoryName (configFile));
508
config.configFile = configFile;
512
public static AddinDescription Read (Stream stream, string basePath)
514
AddinDescription config = new AddinDescription ();
517
config.configDoc = new XmlDocument ();
518
config.configDoc.Load (stream);
519
} catch (Exception ex) {
520
throw new InvalidOperationException ("The add-in configuration file is invalid: " + ex.Message, ex);
523
XmlElement elem = config.configDoc.DocumentElement;
524
config.id = elem.GetAttribute ("id");
525
config.ns = elem.GetAttribute ("namespace");
526
config.name = elem.GetAttribute ("name");
527
config.version = elem.GetAttribute ("version");
528
config.compatVersion = elem.GetAttribute ("compatVersion");
529
config.author = elem.GetAttribute ("author");
530
config.url = elem.GetAttribute ("url");
531
config.copyright = elem.GetAttribute ("copyright");
532
config.description = elem.GetAttribute ("description");
533
config.category = elem.GetAttribute ("category");
534
config.basePath = elem.GetAttribute ("basePath");
536
string s = elem.GetAttribute ("isRoot");
537
if (s.Length == 0) s = elem.GetAttribute ("isroot");
538
config.isroot = s == "true" || s == "yes";
540
s = elem.GetAttribute ("defaultEnabled");
541
config.defaultEnabled = s.Length == 0 || s == "true" || s == "yes";
543
XmlElement localizerElem = (XmlElement) elem.SelectSingleNode ("Localizer");
544
if (localizerElem != null)
545
config.localizer = new ExtensionNodeDescription (localizerElem);
547
if (config.id.Length > 0)
548
config.hasUserId = true;
553
internal static AddinDescription ReadBinary (FileDatabase fdb, string configFile)
555
AddinDescription description = (AddinDescription) fdb.ReadSharedObject (configFile, typeMap);
556
if (description != null) {
557
description.FileName = configFile;
558
description.canWrite = !fdb.IgnoreDescriptionData;
563
internal void SaveBinary (FileDatabase fdb, string file)
569
internal void SaveBinary (FileDatabase fdb)
572
throw new InvalidOperationException ("Can't write incomplete description.");
573
fdb.WriteSharedObject (AddinFile, FileName, typeMap, this);
574
// BinaryXmlReader.DumpFile (configFile);
577
public StringCollection Verify ()
579
StringCollection errors = new StringCollection ();
582
if (OptionalModules.Count > 0)
583
errors.Add ("Root add-in hosts can't have optional modules.");
586
if (AddinId.Length == 0 || Version.Length == 0) {
587
if (ExtensionPoints.Count > 0)
588
errors.Add ("Add-ins which define new extension points must have an Id and Version.");
591
MainModule.Verify ("", errors);
592
OptionalModules.Verify ("", errors);
593
ExtensionNodeSets.Verify ("", errors);
594
ExtensionPoints.Verify ("", errors);
595
ConditionTypes.Verify ("", errors);
597
foreach (ExtensionNodeSet nset in ExtensionNodeSets) {
598
if (nset.Id.Length == 0)
599
errors.Add ("Attribute 'id' can't be empty for global node sets.");
603
if (BasePath.Length > 0)
605
else if (sourceAddinFile != null && sourceAddinFile.Length > 0)
606
bp = Path.GetDirectoryName (AddinFile);
607
else if (configFile != null && configFile.Length > 0)
608
bp = Path.GetDirectoryName (configFile);
611
foreach (string file in AllFiles) {
612
string asmFile = Path.Combine (BasePath, file);
613
if (!File.Exists (asmFile))
614
errors.Add ("The file '" + file + "' referenced in the manifest could not be found.");
618
if (localizer != null && localizer.GetAttribute ("type").Length == 0) {
619
errors.Add ("The attribute 'type' in the Location element is required.");
625
internal void SetExtensionsAddinId (string addinId)
627
foreach (ExtensionPoint ep in ExtensionPoints)
628
ep.SetExtensionsAddinId (addinId);
630
foreach (ExtensionNodeSet ns in ExtensionNodeSets)
631
ns.SetExtensionsAddinId (addinId);
634
internal void UnmergeExternalData (Hashtable addins)
636
// Removes extension types and extension sets coming from other add-ins.
637
foreach (ExtensionPoint ep in ExtensionPoints)
638
ep.UnmergeExternalData (AddinId, addins);
640
foreach (ExtensionNodeSet ns in ExtensionNodeSets)
641
ns.UnmergeExternalData (AddinId, addins);
644
internal void MergeExternalData (AddinDescription other)
646
// Removes extension types and extension sets coming from other add-ins.
647
foreach (ExtensionPoint ep in other.ExtensionPoints) {
648
ExtensionPoint tep = ExtensionPoints [ep.Path];
650
tep.MergeWith (AddinId, ep);
653
foreach (ExtensionNodeSet ns in other.ExtensionNodeSets) {
654
ExtensionNodeSet tns = ExtensionNodeSets [ns.Id];
656
tns.MergeWith (AddinId, ns);
660
void IBinaryXmlElement.Write (BinaryXmlWriter writer)
662
writer.WriteValue ("id", id);
663
writer.WriteValue ("ns", ns);
664
writer.WriteValue ("isroot", isroot);
665
writer.WriteValue ("name", name);
666
writer.WriteValue ("version", version);
667
writer.WriteValue ("compatVersion", compatVersion);
668
writer.WriteValue ("hasUserId", hasUserId);
669
writer.WriteValue ("author", author);
670
writer.WriteValue ("url", url);
671
writer.WriteValue ("copyright", copyright);
672
writer.WriteValue ("description", description);
673
writer.WriteValue ("category", category);
674
writer.WriteValue ("basePath", basePath);
675
writer.WriteValue ("sourceAddinFile", sourceAddinFile);
676
writer.WriteValue ("defaultEnabled", defaultEnabled);
677
writer.WriteValue ("domain", domain);
678
writer.WriteValue ("MainModule", MainModule);
679
writer.WriteValue ("OptionalModules", OptionalModules);
680
writer.WriteValue ("NodeSets", ExtensionNodeSets);
681
writer.WriteValue ("ExtensionPoints", ExtensionPoints);
682
writer.WriteValue ("ConditionTypes", ConditionTypes);
683
writer.WriteValue ("FilesInfo", fileInfo);
684
writer.WriteValue ("Localizer", localizer);
687
void IBinaryXmlElement.Read (BinaryXmlReader reader)
689
id = reader.ReadStringValue ("id");
690
ns = reader.ReadStringValue ("ns");
691
isroot = reader.ReadBooleanValue ("isroot");
692
name = reader.ReadStringValue ("name");
693
version = reader.ReadStringValue ("version");
694
compatVersion = reader.ReadStringValue ("compatVersion");
695
hasUserId = reader.ReadBooleanValue ("hasUserId");
696
author = reader.ReadStringValue ("author");
697
url = reader.ReadStringValue ("url");
698
copyright = reader.ReadStringValue ("copyright");
699
description = reader.ReadStringValue ("description");
700
category = reader.ReadStringValue ("category");
701
basePath = reader.ReadStringValue ("basePath");
702
sourceAddinFile = reader.ReadStringValue ("sourceAddinFile");
703
defaultEnabled = reader.ReadBooleanValue ("defaultEnabled");
704
domain = reader.ReadStringValue ("domain");
705
mainModule = (ModuleDescription) reader.ReadValue ("MainModule");
706
optionalModules = (ModuleCollection) reader.ReadValue ("OptionalModules", new ModuleCollection (this));
707
nodeSets = (ExtensionNodeSetCollection) reader.ReadValue ("NodeSets", new ExtensionNodeSetCollection (this));
708
extensionPoints = (ExtensionPointCollection) reader.ReadValue ("ExtensionPoints", new ExtensionPointCollection (this));
709
conditionTypes = (ConditionTypeDescriptionCollection) reader.ReadValue ("ConditionTypes", new ConditionTypeDescriptionCollection (this));
710
fileInfo = (object[]) reader.ReadValue ("FilesInfo", null);
711
localizer = (ExtensionNodeDescription) reader.ReadValue ("Localizer");
713
if (mainModule != null)
714
mainModule.SetParent (this);
718
class AddinFileInfo: IBinaryXmlElement
723
public string FileName {
732
public System.DateTime Timestamp {
741
public void Read (BinaryXmlReader reader)
743
fileName = reader.ReadStringValue ("fileName");
744
timestamp = reader.ReadDateTimeValue ("timestamp");
747
public void Write (BinaryXmlWriter writer)
749
writer.WriteValue ("fileName", fileName);
750
writer.WriteValue ("timestamp", timestamp);