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.
31
using System.Collections;
32
using System.Collections.Generic;
35
using System.Reflection;
36
using System.Collections.Specialized;
38
using System.ComponentModel;
40
using Mono.Addins.Description;
42
namespace Mono.Addins.Database
44
class AddinScanner: MarshalByRefObject
46
AddinDatabase database;
47
AddinFileSystemExtension fs;
48
Dictionary<IAssemblyReflector,object> coreAssemblies = new Dictionary<IAssemblyReflector, object> ();
50
public AddinScanner (AddinDatabase database, AddinScanResult scanResult, IProgressStatus monitor)
52
this.database = database;
53
fs = database.FileSystem;
56
public void ScanFolder (IProgressStatus monitor, string path, string domain, AddinScanResult scanResult)
58
path = Path.GetFullPath (path);
60
// Avoid folders including each other
61
if (!scanResult.VisitFolder (path))
64
AddinScanFolderInfo folderInfo;
65
if (!database.GetFolderInfoForPath (monitor, path, out folderInfo)) {
66
// folderInfo file was corrupt.
67
// Just in case, we are going to regenerate all relation data.
68
if (!fs.DirectoryExists (path))
69
scanResult.RegenerateRelationData = true;
71
// Directory is included but it doesn't exist. Ignore it.
72
if (folderInfo == null && !fs.DirectoryExists (path))
76
// if domain is null it means that a new domain has to be created.
78
bool sharedFolder = domain == AddinDatabase.GlobalDomain;
79
bool isNewFolder = folderInfo == null;
82
// No folder info. It is the first time this folder is scanned.
83
// There is no need to store this object if the folder does not
85
folderInfo = new AddinScanFolderInfo (path);
88
if (!sharedFolder && (folderInfo.SharedFolder || folderInfo.Domain != domain)) {
89
// If the folder already has a domain, reuse it
90
if (domain == null && folderInfo.RootsDomain != null && folderInfo.RootsDomain != AddinDatabase.GlobalDomain)
91
domain = folderInfo.RootsDomain;
92
else if (domain == null) {
93
folderInfo.Domain = domain = database.GetUniqueDomainId ();
94
scanResult.RegenerateRelationData = true;
97
folderInfo.Domain = domain;
99
// Domain has changed. Update the folder info and regenerate everything.
100
scanResult.RegenerateRelationData = true;
101
scanResult.RegisterModifiedFolderInfo (folderInfo);
105
else if (!folderInfo.SharedFolder && sharedFolder) {
106
scanResult.RegenerateRelationData = true;
109
folderInfo.SharedFolder = sharedFolder;
111
// If there is no domain assigned to the host, get one now
112
if (scanResult.Domain == AddinDatabase.UnknownDomain)
113
scanResult.Domain = domain;
115
// Discard folders not belonging to the required domain
116
if (scanResult.Domain != null && domain != scanResult.Domain && domain != AddinDatabase.GlobalDomain) {
120
if (monitor.LogLevel > 1 && !scanResult.LocateAssembliesOnly)
121
monitor.Log ("Checking: " + path);
123
if (fs.DirectoryExists (path))
125
IEnumerable<string> files = fs.GetFiles (path);
127
// First of all, look for .addin files. Addin files must be processed before
128
// assemblies, because they may add files to the ignore list (i.e., assemblies
129
// included in .addin files won't be scanned twice).
130
foreach (string file in files) {
131
if (file.EndsWith (".addin.xml") || file.EndsWith (".addin")) {
132
RegisterFileToScan (monitor, file, scanResult, folderInfo);
136
// Now scan assemblies. They can also add files to the ignore list.
138
foreach (string file in files) {
139
string ext = Path.GetExtension (file).ToLower ();
140
if (ext == ".dll" || ext == ".exe") {
141
RegisterFileToScan (monitor, file, scanResult, folderInfo);
142
scanResult.AddAssemblyLocation (file);
146
// Finally scan .addins files
148
foreach (string file in files) {
149
if (Path.GetExtension (file).EndsWith (".addins")) {
150
ScanAddinsFile (monitor, file, domain, scanResult);
154
else if (!scanResult.LocateAssembliesOnly) {
155
// The folder has been deleted. All add-ins defined in that folder should also be deleted.
156
scanResult.RegenerateRelationData = true;
157
scanResult.ChangesFound = true;
158
if (scanResult.CheckOnly)
160
database.DeleteFolderInfo (monitor, folderInfo);
163
if (scanResult.LocateAssembliesOnly)
166
// Look for deleted add-ins.
168
UpdateDeletedAddins (monitor, folderInfo, scanResult);
171
public void UpdateDeletedAddins (IProgressStatus monitor, AddinScanFolderInfo folderInfo, AddinScanResult scanResult)
173
ArrayList missing = folderInfo.GetMissingAddins (fs);
174
if (missing.Count > 0) {
175
if (fs.DirectoryExists (folderInfo.Folder))
176
scanResult.RegisterModifiedFolderInfo (folderInfo);
177
scanResult.ChangesFound = true;
178
if (scanResult.CheckOnly)
181
foreach (AddinFileInfo info in missing) {
182
database.UninstallAddin (monitor, info.Domain, info.AddinId, info.File, scanResult);
187
void RegisterFileToScan (IProgressStatus monitor, string file, AddinScanResult scanResult, AddinScanFolderInfo folderInfo)
189
if (scanResult.LocateAssembliesOnly)
192
AddinFileInfo finfo = folderInfo.GetAddinFileInfo (file);
195
if (finfo != null && (!finfo.IsAddin || finfo.Domain == folderInfo.GetDomain (finfo.IsRoot)) && fs.GetLastWriteTime (file) == finfo.LastScan && !scanResult.RegenerateAllData) {
196
if (finfo.ScanError) {
197
// Always schedule the file for scan if there was an error in a previous scan.
198
// However, don't set ChangesFound=true, in this way if there isn't any other
199
// change in the registry, the file won't be scanned again.
200
scanResult.AddFileToScan (file, folderInfo);
207
if (database.AddinDescriptionExists (finfo.Domain, finfo.AddinId)) {
208
// It is an add-in and it has not changed. Paths in the ignore list
209
// are still valid, so they can be used.
210
if (finfo.IgnorePaths != null)
211
scanResult.AddPathsToIgnore (finfo.IgnorePaths);
216
scanResult.ChangesFound = true;
218
if (!scanResult.CheckOnly && !added)
219
scanResult.AddFileToScan (file, folderInfo);
222
public void ScanFile (IProgressStatus monitor, string file, AddinScanFolderInfo folderInfo, AddinScanResult scanResult)
224
if (scanResult.IgnorePath (file)) {
225
// The file must be ignored. Maybe it caused a crash in a previous scan, or it
226
// might be included by a .addin file (in which case it will be scanned when processing
228
folderInfo.SetLastScanTime (file, null, false, fs.GetLastWriteTime (file), true);
232
if (monitor.LogLevel > 1)
233
monitor.Log ("Scanning file: " + file);
235
// Log the file to be scanned, so in case of a process crash the main process
236
// will know what crashed
237
monitor.Log ("plog:scan:" + file);
239
string scannedAddinId = null;
240
bool scannedIsRoot = false;
241
bool scanSuccessful = false;
242
AddinDescription config = null;
245
string ext = Path.GetExtension (file).ToLower ();
247
if (ext == ".dll" || ext == ".exe")
248
scanSuccessful = ScanAssembly (monitor, file, scanResult, out config);
250
scanSuccessful = ScanConfigAssemblies (monitor, file, scanResult, out config);
252
if (config != null) {
254
AddinFileInfo fi = folderInfo.GetAddinFileInfo (file);
256
// If version is not specified, make up one
257
if (config.Version.Length == 0) {
258
config.Version = "0.0.0.0";
261
if (config.LocalId.Length == 0) {
262
// Generate an internal id for this add-in
263
config.LocalId = database.GetUniqueAddinId (file, (fi != null ? fi.AddinId : null), config.Namespace, config.Version);
264
config.HasUserId = false;
267
// Check errors in the description
268
StringCollection errors = config.Verify (fs);
270
if (database.IsGlobalRegistry && config.AddinId.IndexOf ('.') == -1) {
271
errors.Add ("Add-ins registered in the global registry must have a namespace.");
274
if (errors.Count > 0) {
275
scanSuccessful = false;
276
monitor.ReportError ("Errors found in add-in '" + file + ":", null);
277
foreach (string err in errors)
278
monitor.ReportError (err, null);
281
// Make sure all extensions sets are initialized with the correct add-in id
283
config.SetExtensionsAddinId (config.AddinId);
285
scanResult.ChangesFound = true;
287
// If the add-in already existed, try to reuse the relation data it had.
288
// Also, the dependencies of the old add-in need to be re-analized
290
AddinDescription existingDescription = null;
291
bool res = database.GetAddinDescription (monitor, folderInfo.Domain, config.AddinId, config.AddinFile, out existingDescription);
293
// If we can't get information about the old assembly, just regenerate all relation data
295
scanResult.RegenerateRelationData = true;
297
string replaceFileName = null;
299
if (existingDescription != null) {
300
// Reuse old relation data
301
config.MergeExternalData (existingDescription);
302
Util.AddDependencies (existingDescription, scanResult);
303
replaceFileName = existingDescription.FileName;
306
// If the scanned file results in an add-in version different from the one obtained from
307
// previous scans, the old add-in needs to be uninstalled.
308
if (fi != null && fi.IsAddin && fi.AddinId != config.AddinId) {
309
database.UninstallAddin (monitor, folderInfo.Domain, fi.AddinId, fi.File, scanResult);
311
// If the add-in version has changed, regenerate everything again since old data can't be reused
312
if (Addin.GetIdName (fi.AddinId) == Addin.GetIdName (config.AddinId))
313
scanResult.RegenerateRelationData = true;
316
// If a description could be generated, save it now (if the scan was successful)
317
if (scanSuccessful) {
321
if (folderInfo.RootsDomain == null) {
322
if (scanResult.Domain != null && scanResult.Domain != AddinDatabase.UnknownDomain && scanResult.Domain != AddinDatabase.GlobalDomain)
323
folderInfo.RootsDomain = scanResult.Domain;
325
folderInfo.RootsDomain = database.GetUniqueDomainId ();
327
config.Domain = folderInfo.RootsDomain;
329
config.Domain = folderInfo.Domain;
331
if (config.IsRoot && scanResult.HostIndex != null) {
332
// If the add-in is a root, register its assemblies
333
foreach (string f in config.MainModule.Assemblies) {
334
string asmFile = Path.Combine (config.BasePath, f);
335
scanResult.HostIndex.RegisterAssembly (asmFile, config.AddinId, config.AddinFile, config.Domain);
341
if (database.SaveDescription (monitor, config, replaceFileName)) {
342
// The new dependencies also have to be updated
343
Util.AddDependencies (config, scanResult);
344
scanResult.AddAddinToUpdate (config.AddinId);
345
scannedAddinId = config.AddinId;
346
scannedIsRoot = config.IsRoot;
352
catch (Exception ex) {
353
monitor.ReportError ("Unexpected error while scanning file: " + file, ex);
356
AddinFileInfo ainfo = folderInfo.SetLastScanTime (file, scannedAddinId, scannedIsRoot, fs.GetLastWriteTime (file), !scanSuccessful);
358
if (scanSuccessful && config != null) {
359
// Update the ignore list in the folder info object. To be used in the next scan
360
foreach (string df in config.AllIgnorePaths) {
361
string path = Path.Combine (config.BasePath, df);
362
ainfo.AddPathToIgnore (Path.GetFullPath (path));
366
monitor.Log ("plog:endscan");
370
public AddinDescription ScanSingleFile (IProgressStatus monitor, string file, AddinScanResult scanResult)
372
AddinDescription config = null;
374
if (monitor.LogLevel > 1)
375
monitor.Log ("Scanning file: " + file);
377
monitor.Log ("plog:scan:" + file);
380
string ext = Path.GetExtension (file).ToLower ();
383
if (ext == ".dll" || ext == ".exe")
384
scanSuccessful = ScanAssembly (monitor, file, scanResult, out config);
386
scanSuccessful = ScanConfigAssemblies (monitor, file, scanResult, out config);
388
if (scanSuccessful && config != null) {
390
config.Domain = "global";
391
if (config.Version.Length == 0)
392
config.Version = "0.0.0.0";
394
if (config.LocalId.Length == 0) {
395
// Generate an internal id for this add-in
396
config.LocalId = database.GetUniqueAddinId (file, "", config.Namespace, config.Version);
400
catch (Exception ex) {
401
monitor.ReportError ("Unexpected error while scanning file: " + file, ex);
403
monitor.Log ("plog:endscan");
408
public void ScanAddinsFile (IProgressStatus monitor, string file, string domain, AddinScanResult scanResult)
410
XmlTextReader r = null;
411
ArrayList directories = new ArrayList ();
412
ArrayList directoriesWithSubdirs = new ArrayList ();
413
string basePath = Path.GetDirectoryName (file);
416
r = new XmlTextReader (fs.OpenTextFile (file));
418
if (r.IsEmptyElement)
420
r.ReadStartElement ();
422
while (r.NodeType != XmlNodeType.EndElement) {
423
if (r.NodeType == XmlNodeType.Element && r.LocalName == "Directory") {
424
string subs = r.GetAttribute ("include-subdirs");
426
string share = r.GetAttribute ("shared");
428
sdom = AddinDatabase.GlobalDomain;
429
else if (share == "false")
432
sdom = domain; // Inherit the domain
434
string path = r.ReadElementString ().Trim ();
435
if (path.Length > 0) {
436
path = Util.NormalizePath (path);
438
directoriesWithSubdirs.Add (new string[] {path, sdom});
440
directories.Add (new string[] {path, sdom});
443
else if (r.NodeType == XmlNodeType.Element && r.LocalName == "GacAssembly") {
444
string aname = r.ReadElementString ().Trim ();
445
if (aname.Length > 0) {
446
aname = Util.NormalizePath (aname);
447
aname = Util.GetGacPath (aname);
449
// Gac assemblies always use the global domain
450
directories.Add (new string[] {aname, AddinDatabase.GlobalDomain});
454
else if (r.NodeType == XmlNodeType.Element && r.LocalName == "Exclude") {
455
string path = r.ReadElementString ().Trim ();
456
if (path.Length > 0) {
457
path = Util.NormalizePath (path);
458
if (!Path.IsPathRooted (path))
459
path = Path.Combine (basePath, path);
460
scanResult.AddPathToIgnore (Path.GetFullPath (path));
467
} catch (Exception ex) {
468
monitor.ReportError ("Could not process addins file: " + file, ex);
475
foreach (string[] d in directories) {
477
if (!Path.IsPathRooted (dir))
478
dir = Path.Combine (basePath, dir);
479
ScanFolder (monitor, dir, d[1], scanResult);
481
foreach (string[] d in directoriesWithSubdirs) {
483
if (!Path.IsPathRooted (dir))
484
dir = Path.Combine (basePath, dir);
485
ScanFolderRec (monitor, dir, d[1], scanResult);
489
public void ScanFolderRec (IProgressStatus monitor, string dir, string domain, AddinScanResult scanResult)
491
ScanFolder (monitor, dir, domain, scanResult);
493
if (!fs.DirectoryExists (dir))
496
foreach (string sd in fs.GetDirectories (dir))
497
ScanFolderRec (monitor, sd, domain, scanResult);
500
bool ScanConfigAssemblies (IProgressStatus monitor, string filePath, AddinScanResult scanResult, out AddinDescription config)
505
IAssemblyReflector reflector = GetReflector (monitor, scanResult, filePath);
507
string basePath = Path.GetDirectoryName (filePath);
509
using (var s = fs.OpenFile (filePath)) {
510
config = AddinDescription.Read (s, basePath);
512
config.FileName = filePath;
513
config.SetBasePath (basePath);
514
config.AddinFile = filePath;
516
return ScanDescription (monitor, reflector, config, null, scanResult);
518
catch (Exception ex) {
519
// Something went wrong while scanning the assembly. We'll ignore it for now.
520
monitor.ReportError ("There was an error while scanning add-in: " + filePath, ex);
525
IAssemblyReflector GetReflector (IProgressStatus monitor, AddinScanResult scanResult, string filePath)
527
IAssemblyReflector reflector = fs.GetReflectorForFile (scanResult, filePath);
529
if (!coreAssemblies.TryGetValue (reflector, out coreAssembly)) {
530
if (monitor.LogLevel > 1)
531
monitor.Log ("Using assembly reflector: " + reflector.GetType ());
532
coreAssemblies [reflector] = coreAssembly = reflector.LoadAssembly (GetType().Assembly.Location);
537
bool ScanAssembly (IProgressStatus monitor, string filePath, AddinScanResult scanResult, out AddinDescription config)
542
IAssemblyReflector reflector = GetReflector (monitor, scanResult, filePath);
543
object asm = reflector.LoadAssembly (filePath);
545
throw new Exception ("Could not load assembly: " + filePath);
547
// Get the config file from the resources, if there is one
549
foreach (string res in reflector.GetResourceNames (asm)) {
550
if (res.EndsWith (".addin") || res.EndsWith (".addin.xml")) {
551
using (Stream s = reflector.GetResourceStream (asm, res)) {
552
AddinDescription ad = AddinDescription.Read (s, Path.GetDirectoryName (filePath));
553
if (config != null) {
554
if (!config.IsExtensionModel && !ad.IsExtensionModel) {
555
// There is more than one add-in definition
556
monitor.ReportError ("Duplicate add-in definition found in assembly: " + filePath, null);
559
config = AddinDescription.Merge (config, ad);
566
if (config == null) {
567
// In this case, only scan the assembly if it has the Addin attribute.
568
AddinAttribute att = (AddinAttribute) reflector.GetCustomAttribute (asm, typeof(AddinAttribute), false);
572
config = new AddinDescription ();
575
config.SetBasePath (Path.GetDirectoryName (filePath));
576
config.AddinFile = filePath;
578
string rasmFile = Path.GetFileName (filePath);
579
if (!config.MainModule.Assemblies.Contains (rasmFile))
580
config.MainModule.Assemblies.Add (rasmFile);
582
return ScanDescription (monitor, reflector, config, asm, scanResult);
584
catch (Exception ex) {
585
// Something went wrong while scanning the assembly. We'll ignore it for now.
586
monitor.ReportError ("There was an error while scanning assembly: " + filePath, ex);
591
bool ScanDescription (IProgressStatus monitor, IAssemblyReflector reflector, AddinDescription config, object rootAssembly, AddinScanResult scanResult)
593
// First of all scan the main module
595
ArrayList assemblies = new ArrayList ();
598
string rootAsmFile = null;
600
if (rootAssembly != null) {
601
ScanAssemblyAddinHeaders (reflector, config, rootAssembly, scanResult);
602
ScanAssemblyImports (reflector, config.MainModule, rootAssembly);
603
assemblies.Add (rootAssembly);
604
rootAsmFile = Path.GetFileName (config.AddinFile);
607
// The assembly list may be modified while scanning the headears, so
608
// we use a for loop instead of a foreach
609
for (int n=0; n<config.MainModule.Assemblies.Count; n++) {
610
string s = config.MainModule.Assemblies [n];
611
string asmFile = Path.GetFullPath (Path.Combine (config.BasePath, s));
612
scanResult.AddPathToIgnore (asmFile);
613
if (s == rootAsmFile || config.MainModule.IgnorePaths.Contains (s))
615
object asm = reflector.LoadAssembly (asmFile);
616
assemblies.Add (asm);
617
ScanAssemblyAddinHeaders (reflector, config, asm, scanResult);
618
ScanAssemblyImports (reflector, config.MainModule, asm);
621
// Add all data files to the ignore file list. It avoids scanning assemblies
622
// which are included as 'data' in an add-in.
623
foreach (string df in config.MainModule.DataFiles) {
624
string file = Path.Combine (config.BasePath, df);
625
scanResult.AddPathToIgnore (Path.GetFullPath (file));
627
foreach (string df in config.MainModule.IgnorePaths) {
628
string path = Path.Combine (config.BasePath, df);
629
scanResult.AddPathToIgnore (Path.GetFullPath (path));
632
// The add-in id and version must be already assigned at this point
634
// Clean host data from the index. New data will be added.
635
if (scanResult.HostIndex != null)
636
scanResult.HostIndex.RemoveHostData (config.AddinId, config.AddinFile);
638
foreach (object asm in assemblies)
639
ScanAssemblyContents (reflector, config, config.MainModule, asm, scanResult);
641
} catch (Exception ex) {
642
ReportReflectionException (monitor, ex, config, scanResult);
646
// Extension node types may have child nodes declared as attributes. Find them.
648
Hashtable internalNodeSets = new Hashtable ();
650
ArrayList setsCopy = new ArrayList ();
651
setsCopy.AddRange (config.ExtensionNodeSets);
652
foreach (ExtensionNodeSet eset in setsCopy)
653
ScanNodeSet (reflector, config, eset, assemblies, internalNodeSets);
655
foreach (ExtensionPoint ep in config.ExtensionPoints) {
656
ScanNodeSet (reflector, config, ep.NodeSet, assemblies, internalNodeSets);
659
// Now scan all modules
661
if (!config.IsRoot) {
662
foreach (ModuleDescription mod in config.OptionalModules) {
665
for (int n=0; n<mod.Assemblies.Count; n++) {
666
string s = mod.Assemblies [n];
667
if (mod.IgnorePaths.Contains (s))
669
string asmFile = Path.Combine (config.BasePath, s);
670
object asm = reflector.LoadAssembly (asmFile);
671
assemblies.Add (asm);
672
scanResult.AddPathToIgnore (Path.GetFullPath (asmFile));
673
ScanAssemblyImports (reflector, mod, asm);
675
// Add all data files to the ignore file list. It avoids scanning assemblies
676
// which are included as 'data' in an add-in.
677
foreach (string df in mod.DataFiles) {
678
string file = Path.Combine (config.BasePath, df);
679
scanResult.AddPathToIgnore (Path.GetFullPath (file));
681
foreach (string df in mod.IgnorePaths) {
682
string path = Path.Combine (config.BasePath, df);
683
scanResult.AddPathToIgnore (Path.GetFullPath (path));
686
foreach (object asm in assemblies)
687
ScanAssemblyContents (reflector, config, mod, asm, scanResult);
689
} catch (Exception ex) {
690
ReportReflectionException (monitor, ex, config, scanResult);
695
config.StoreFileInfo ();
699
void ReportReflectionException (IProgressStatus monitor, Exception ex, AddinDescription config, AddinScanResult scanResult)
701
scanResult.AddFileToWithFailure (config.AddinFile);
702
monitor.ReportWarning ("[" + config.AddinId + "] Could not load some add-in assemblies: " + ex.Message);
703
if (monitor.LogLevel <= 1)
706
ReflectionTypeLoadException rex = ex as ReflectionTypeLoadException;
708
foreach (Exception e in rex.LoaderExceptions)
709
monitor.Log ("Load exception: " + e);
713
void ScanAssemblyAddinHeaders (IAssemblyReflector reflector, AddinDescription config, object asm, AddinScanResult scanResult)
715
// Get basic add-in information
716
AddinAttribute att = (AddinAttribute) reflector.GetCustomAttribute (asm, typeof(AddinAttribute), false);
718
if (att.Id.Length > 0)
719
config.LocalId = att.Id;
720
if (att.Version.Length > 0)
721
config.Version = att.Version;
722
if (att.Namespace.Length > 0)
723
config.Namespace = att.Namespace;
724
if (att.Category.Length > 0)
725
config.Category = att.Category;
726
if (att.CompatVersion.Length > 0)
727
config.CompatVersion = att.CompatVersion;
728
if (att.Url.Length > 0)
729
config.Url = att.Url;
730
config.IsRoot = att is AddinRootAttribute;
731
config.EnabledByDefault = att.EnabledByDefault;
732
config.Flags = att.Flags;
737
object[] atts = reflector.GetCustomAttributes (asm, typeof(AddinAuthorAttribute), false);
738
foreach (AddinAuthorAttribute author in atts) {
739
if (config.Author.Length == 0)
740
config.Author = author.Name;
742
config.Author += ", " + author.Name;
747
atts = reflector.GetCustomAttributes (asm, typeof(AddinNameAttribute), false);
748
foreach (AddinNameAttribute at in atts) {
749
if (string.IsNullOrEmpty (at.Locale))
750
config.Name = at.Name;
752
config.Properties.SetPropertyValue ("Name", at.Name, at.Locale);
757
object catt = reflector.GetCustomAttribute (asm, typeof(AssemblyDescriptionAttribute), false);
758
if (catt != null && config.Description.Length == 0)
759
config.Description = ((AssemblyDescriptionAttribute)catt).Description;
761
atts = reflector.GetCustomAttributes (asm, typeof(AddinDescriptionAttribute), false);
762
foreach (AddinDescriptionAttribute at in atts) {
763
if (string.IsNullOrEmpty (at.Locale))
764
config.Description = at.Description;
766
config.Properties.SetPropertyValue ("Description", at.Description, at.Locale);
771
catt = reflector.GetCustomAttribute (asm, typeof(AssemblyCopyrightAttribute), false);
772
if (catt != null && config.Copyright.Length == 0)
773
config.Copyright = ((AssemblyCopyrightAttribute)catt).Copyright;
777
AddinLocalizerGettextAttribute locat = (AddinLocalizerGettextAttribute) reflector.GetCustomAttribute (asm, typeof(AddinLocalizerGettextAttribute), false);
779
ExtensionNodeDescription node = new ExtensionNodeDescription ();
780
if (!string.IsNullOrEmpty (locat.Catalog))
781
node.SetAttribute ("catalog", locat.Catalog);
782
if (!string.IsNullOrEmpty (locat.Location))
783
node.SetAttribute ("location", locat.Catalog);
784
config.Localizer = node;
789
atts = reflector.GetCustomAttributes (asm, typeof(AddinModuleAttribute), false);
790
foreach (AddinModuleAttribute mod in atts) {
791
if (mod.AssemblyFile.Length > 0) {
792
ModuleDescription module = new ModuleDescription ();
793
module.Assemblies.Add (mod.AssemblyFile);
794
config.OptionalModules.Add (module);
799
void ScanAssemblyImports (IAssemblyReflector reflector, ModuleDescription module, object asm)
801
object[] atts = reflector.GetCustomAttributes (asm, typeof(ImportAddinAssemblyAttribute), false);
802
foreach (ImportAddinAssemblyAttribute import in atts) {
803
if (!string.IsNullOrEmpty (import.FilePath)) {
804
module.Assemblies.Add (import.FilePath);
806
module.IgnorePaths.Add (import.FilePath);
809
atts = reflector.GetCustomAttributes (asm, typeof(ImportAddinFileAttribute), false);
810
foreach (ImportAddinFileAttribute import in atts) {
811
if (!string.IsNullOrEmpty (import.FilePath))
812
module.DataFiles.Add (import.FilePath);
816
void ScanAssemblyContents (IAssemblyReflector reflector, AddinDescription config, ModuleDescription module, object asm, AddinScanResult scanResult)
818
bool isMainModule = module == config.MainModule;
822
object[] deps = reflector.GetCustomAttributes (asm, typeof(AddinDependencyAttribute), false);
823
foreach (AddinDependencyAttribute dep in deps) {
824
AddinDependency adep = new AddinDependency ();
825
adep.AddinId = dep.Id;
826
adep.Version = dep.Version;
827
module.Dependencies.Add (adep);
834
object[] props = reflector.GetCustomAttributes (asm, typeof(AddinPropertyAttribute), false);
835
foreach (AddinPropertyAttribute prop in props)
836
config.Properties.SetPropertyValue (prop.Name, prop.Value, prop.Locale);
838
// Get extension points
840
object[] extPoints = reflector.GetCustomAttributes (asm, typeof(ExtensionPointAttribute), false);
841
foreach (ExtensionPointAttribute ext in extPoints) {
842
ExtensionPoint ep = config.AddExtensionPoint (ext.Path);
843
ep.Description = ext.Description;
845
ExtensionNodeType nt = ep.AddExtensionNode (ext.NodeName, ext.NodeTypeName);
846
nt.ExtensionAttributeTypeName = ext.ExtensionAttributeTypeName;
850
// Look for extension nodes declared using assembly attributes
852
foreach (CustomAttribute att in reflector.GetRawCustomAttributes (asm, typeof(CustomExtensionAttribute), true))
853
AddCustomAttributeExtension (module, att, "Type");
855
// Get extensions or extension points applied to types
857
foreach (object t in reflector.GetAssemblyTypes (asm)) {
859
string typeFullName = reflector.GetTypeFullName (t);
861
// Look for extensions
863
object[] extensionAtts = reflector.GetCustomAttributes (t, typeof(ExtensionAttribute), false);
864
if (extensionAtts.Length > 0) {
865
Dictionary<string,ExtensionNodeDescription> nodes = new Dictionary<string, ExtensionNodeDescription> ();
866
ExtensionNodeDescription uniqueNode = null;
867
foreach (ExtensionAttribute eatt in extensionAtts) {
869
string nodeName = eatt.NodeName;
871
if (eatt.TypeName.Length > 0) {
872
path = "$" + eatt.TypeName;
874
else if (eatt.Path.Length == 0) {
875
path = GetBaseTypeNameList (reflector, t);
877
// The type does not implement any interface and has no superclass.
878
// Will be reported later as an error.
879
path = "$" + typeFullName;
885
ExtensionNodeDescription elem = module.AddExtensionNode (path, nodeName);
889
if (eatt.Id.Length > 0) {
890
elem.SetAttribute ("id", eatt.Id);
891
elem.SetAttribute ("type", typeFullName);
893
elem.SetAttribute ("id", typeFullName);
895
if (eatt.InsertAfter.Length > 0)
896
elem.SetAttribute ("insertafter", eatt.InsertAfter);
897
if (eatt.InsertBefore.Length > 0)
898
elem.SetAttribute ("insertbefore", eatt.InsertBefore);
901
// Get the node attributes
903
foreach (ExtensionAttributeAttribute eat in reflector.GetCustomAttributes (t, typeof(ExtensionAttributeAttribute), false)) {
904
ExtensionNodeDescription node;
905
if (!string.IsNullOrEmpty (eat.Path))
906
nodes.TryGetValue (eat.Path, out node);
907
else if (eat.TypeName.Length > 0)
908
nodes.TryGetValue ("$" + eat.TypeName, out node);
911
throw new Exception ("Missing type or extension path value in ExtensionAttribute for type '" + typeFullName + "'.");
915
throw new Exception ("Invalid type or path value in ExtensionAttribute for type '" + typeFullName + "'.");
917
node.SetAttribute (eat.Name ?? string.Empty, eat.Value ?? string.Empty);
921
// Look for extension points
923
extensionAtts = reflector.GetCustomAttributes (t, typeof(TypeExtensionPointAttribute), false);
924
if (extensionAtts.Length > 0 && isMainModule) {
925
foreach (TypeExtensionPointAttribute epa in extensionAtts) {
928
ExtensionNodeType nt = new ExtensionNodeType ();
930
if (epa.Path.Length > 0) {
931
ep = config.AddExtensionPoint (epa.Path);
934
ep = config.AddExtensionPoint (GetDefaultTypeExtensionPath (config, typeFullName));
935
nt.ObjectTypeName = typeFullName;
937
nt.Id = epa.NodeName;
938
nt.TypeName = epa.NodeTypeName;
939
nt.ExtensionAttributeTypeName = epa.ExtensionAttributeTypeName;
940
ep.NodeSet.NodeTypes.Add (nt);
941
ep.Description = epa.Description;
943
ep.RootAddin = config.AddinId;
944
ep.SetExtensionsAddinId (config.AddinId);
948
// Look for custom extension attribtues
949
foreach (CustomAttribute att in reflector.GetRawCustomAttributes (t, typeof(CustomExtensionAttribute), false)) {
950
ExtensionNodeDescription elem = AddCustomAttributeExtension (module, att, "Type");
951
elem.SetAttribute ("type", typeFullName);
952
if (string.IsNullOrEmpty (elem.GetAttribute ("id")))
953
elem.SetAttribute ("id", typeFullName);
960
ExtensionNodeDescription AddCustomAttributeExtension (ModuleDescription module, CustomAttribute att, string nameName)
963
if (!att.TryGetValue (CustomExtensionAttribute.PathFieldKey, out path))
964
path = "%" + att.TypeName;
965
ExtensionNodeDescription elem = module.AddExtensionNode (path, nameName);
966
foreach (KeyValuePair<string,string> prop in att) {
967
if (prop.Key != CustomExtensionAttribute.PathFieldKey)
968
elem.SetAttribute (prop.Key, prop.Value);
973
void ScanNodeSet (IAssemblyReflector reflector, AddinDescription config, ExtensionNodeSet nset, ArrayList assemblies, Hashtable internalNodeSets)
975
foreach (ExtensionNodeType nt in nset.NodeTypes)
976
ScanNodeType (reflector, config, nt, assemblies, internalNodeSets);
979
void ScanNodeType (IAssemblyReflector reflector, AddinDescription config, ExtensionNodeType nt, ArrayList assemblies, Hashtable internalNodeSets)
981
if (nt.TypeName.Length == 0)
982
nt.TypeName = "Mono.Addins.TypeExtensionNode";
984
object ntype = FindAddinType (reflector, nt.TypeName, assemblies);
988
// Add type information declared with attributes in the code
989
ExtensionNodeAttribute nodeAtt = (ExtensionNodeAttribute) reflector.GetCustomAttribute (ntype, typeof(ExtensionNodeAttribute), true);
990
if (nodeAtt != null) {
991
if (nt.Id.Length == 0 && nodeAtt.NodeName.Length > 0)
992
nt.Id = nodeAtt.NodeName;
993
if (nt.Description.Length == 0 && nodeAtt.Description.Length > 0)
994
nt.Description = nodeAtt.Description;
995
if (nt.ExtensionAttributeTypeName.Length == 0 && nodeAtt.ExtensionAttributeTypeName.Length > 0)
996
nt.ExtensionAttributeTypeName = nodeAtt.ExtensionAttributeTypeName;
998
// Use the node type name as default name
999
if (nt.Id.Length == 0)
1000
nt.Id = reflector.GetTypeName (ntype);
1003
// Add information about attributes
1004
object[] fieldAtts = reflector.GetCustomAttributes (ntype, typeof(NodeAttributeAttribute), true);
1005
foreach (NodeAttributeAttribute fatt in fieldAtts) {
1006
NodeTypeAttribute natt = new NodeTypeAttribute ();
1007
natt.Name = fatt.Name;
1008
natt.Required = fatt.Required;
1009
if (fatt.TypeName != null)
1010
natt.Type = fatt.TypeName;
1011
if (fatt.Description.Length > 0)
1012
natt.Description = fatt.Description;
1013
nt.Attributes.Add (natt);
1016
// Check if the type has NodeAttribute attributes applied to fields.
1017
foreach (object field in reflector.GetFields (ntype)) {
1018
NodeAttributeAttribute fatt = (NodeAttributeAttribute) reflector.GetCustomAttribute (field, typeof(NodeAttributeAttribute), false);
1020
NodeTypeAttribute natt = new NodeTypeAttribute ();
1021
if (fatt.Name.Length > 0)
1022
natt.Name = fatt.Name;
1024
natt.Name = reflector.GetFieldName (field);
1025
if (fatt.Description.Length > 0)
1026
natt.Description = fatt.Description;
1027
natt.Type = reflector.GetFieldTypeFullName (field);
1028
natt.Required = fatt.Required;
1029
nt.Attributes.Add (natt);
1033
// Check if the extension type allows children by looking for [ExtensionNodeChild] attributes.
1034
// First of all, look in the internalNodeSets hashtable, which is being used as cache
1036
string childSet = (string) internalNodeSets [nt.TypeName];
1038
if (childSet == null) {
1039
object[] ats = reflector.GetCustomAttributes (ntype, typeof(ExtensionNodeChildAttribute), true);
1040
if (ats.Length > 0) {
1041
// Create a new node set for this type. It is necessary to create a new node set
1042
// instead of just adding child ExtensionNodeType objects to the this node type
1043
// because child types references can be recursive.
1044
ExtensionNodeSet internalSet = new ExtensionNodeSet ();
1045
internalSet.Id = reflector.GetTypeName (ntype) + "_" + Guid.NewGuid().ToString ();
1046
foreach (ExtensionNodeChildAttribute at in ats) {
1047
ExtensionNodeType internalType = new ExtensionNodeType ();
1048
internalType.Id = at.NodeName;
1049
internalType.TypeName = at.ExtensionNodeTypeName;
1050
internalSet.NodeTypes.Add (internalType);
1052
config.ExtensionNodeSets.Add (internalSet);
1053
nt.NodeSets.Add (internalSet.Id);
1055
// Register the new set in a hashtable, to allow recursive references to the
1056
// same internal set.
1057
internalNodeSets [nt.TypeName] = internalSet.Id;
1058
internalNodeSets [reflector.GetTypeAssemblyQualifiedName (ntype)] = internalSet.Id;
1059
ScanNodeSet (reflector, config, internalSet, assemblies, internalNodeSets);
1063
if (childSet.Length == 0) {
1064
// The extension type does not declare children.
1067
// The extension type can have children. The allowed children are
1068
// defined in this extension set.
1069
nt.NodeSets.Add (childSet);
1073
ScanNodeSet (reflector, config, nt, assemblies, internalNodeSets);
1076
string GetBaseTypeNameList (IAssemblyReflector reflector, object type)
1078
StringBuilder sb = new StringBuilder ("$");
1079
foreach (string tn in reflector.GetBaseTypeFullNameList (type))
1080
sb.Append (tn).Append (',');
1082
sb.Remove (sb.Length - 1, 1);
1083
return sb.ToString ();
1086
object FindAddinType (IAssemblyReflector reflector, string typeName, ArrayList assemblies)
1088
// Look in the current assembly
1089
object etype = reflector.GetType (coreAssemblies [reflector], typeName);
1093
// Look in referenced assemblies
1094
foreach (object asm in assemblies) {
1095
etype = reflector.GetType (asm, typeName);
1100
Hashtable visited = new Hashtable ();
1102
// Look in indirectly referenced assemblies
1103
foreach (object asm in assemblies) {
1104
foreach (object aref in reflector.GetAssemblyReferences (asm)) {
1105
if (visited.Contains (aref))
1107
visited.Add (aref, aref);
1108
object rasm = reflector.LoadAssemblyFromReference (aref);
1110
etype = reflector.GetType (rasm, typeName);
1119
void RegisterTypeNode (AddinDescription config, ExtensionAttribute eatt, string path, string nodeName, string typeFullName)
1121
ExtensionNodeDescription elem = config.MainModule.AddExtensionNode (path, nodeName);
1122
if (eatt.Id.Length > 0) {
1123
elem.SetAttribute ("id", eatt.Id);
1124
elem.SetAttribute ("type", typeFullName);
1126
elem.SetAttribute ("id", typeFullName);
1128
if (eatt.InsertAfter.Length > 0)
1129
elem.SetAttribute ("insertafter", eatt.InsertAfter);
1130
if (eatt.InsertBefore.Length > 0)
1131
elem.SetAttribute ("insertbefore", eatt.InsertBefore);
1134
internal string GetDefaultTypeExtensionPath (AddinDescription desc, string typeFullName)
1136
return "/" + Addin.GetIdName (desc.AddinId) + "/TypeExtensions/" + typeFullName;