~ubuntu-branches/ubuntu/karmic/mono-addins/karmic

« back to all changes in this revision

Viewing changes to Mono.Addins/Mono.Addins.Database/AddinScanner.cs

  • Committer: Bazaar Package Importer
  • Author(s): Mirco Bauer
  • Date: 2007-07-14 12:07:48 UTC
  • Revision ID: james.westby@ubuntu.com-20070714120748-2elczfsjlrdsrpms
Tags: upstream-0.2
ImportĀ upstreamĀ versionĀ 0.2

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
//
 
2
// AddinScanner.cs
 
3
//
 
4
// Author:
 
5
//   Lluis Sanchez Gual
 
6
//
 
7
// Copyright (C) 2007 Novell, Inc (http://www.novell.com)
 
8
//
 
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:
 
16
// 
 
17
// The above copyright notice and this permission notice shall be
 
18
// included in all copies or substantial portions of the Software.
 
19
// 
 
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.
 
27
//
 
28
 
 
29
 
 
30
using System;
 
31
using System.Collections;
 
32
using System.IO;
 
33
using System.Text;
 
34
using System.Reflection;
 
35
using System.Collections.Specialized;
 
36
using System.Xml;
 
37
using System.ComponentModel;
 
38
 
 
39
using Mono.Addins.Description;
 
40
 
 
41
namespace Mono.Addins.Database
 
42
{
 
43
        class AddinScanner: MarshalByRefObject
 
44
        {
 
45
                AddinDatabase database;
 
46
                
 
47
                public AddinScanner (AddinDatabase database)
 
48
                {
 
49
                        this.database = database;
 
50
                }
 
51
                
 
52
                public void ScanFolder (IProgressStatus monitor, string path, AddinScanResult scanResult)
 
53
                {
 
54
                        path = Util.GetFullPath (path);
 
55
                        
 
56
                        // Avoid folders including each other
 
57
                        if (!scanResult.VisitFolder (path))
 
58
                                return;
 
59
                        
 
60
                        if (monitor.LogLevel > 1 && !scanResult.LocateAssembliesOnly)
 
61
                                monitor.Log ("Checking: " + path);
 
62
                        
 
63
                        AddinScanFolderInfo folderInfo;
 
64
                        if (!database.GetFolderInfoForPath (monitor, path, out folderInfo)) {
 
65
                                // folderInfo file was corrupt.
 
66
                                // Just in case, we are going to regenerate all relation data.
 
67
                                if (!Directory.Exists (path))
 
68
                                        scanResult.RegenerateRelationData = true;
 
69
                        } else {
 
70
                                if (folderInfo == null && !Directory.Exists (path))
 
71
                                        return;
 
72
                        }
 
73
                        
 
74
                        if (folderInfo == null)
 
75
                                folderInfo = new AddinScanFolderInfo (path);
 
76
                        
 
77
                        if (Directory.Exists (path))
 
78
                        {
 
79
                                string[] files = Directory.GetFiles (path);
 
80
                                
 
81
                                // First of all, look for .addin files. Addin files must be processed before
 
82
                                // assemblies, because they may add files to the ignore list (i.e., assemblies
 
83
                                // included in .addin files won't be scanned twice).
 
84
                                foreach (string file in files) {
 
85
                                        if (file.EndsWith (".addin.xml") || file.EndsWith (".addin")) {
 
86
                                                RegisterFileToScan (monitor, file, scanResult, folderInfo);
 
87
                                        }
 
88
                                }
 
89
                                
 
90
                                foreach (string file in files) {
 
91
                                        switch (Path.GetExtension (file)) {
 
92
                                        case ".dll":
 
93
                                        case ".exe":
 
94
                                                RegisterFileToScan (monitor, file, scanResult, folderInfo);
 
95
                                                scanResult.AddAssemblyLocation (file);
 
96
                                                break;
 
97
                                        case ".addins":
 
98
                                                ScanAddinsFile (monitor, file, scanResult);
 
99
                                                break;
 
100
                                        }
 
101
                                }
 
102
                        }
 
103
                        else if (!scanResult.LocateAssembliesOnly) {
 
104
                                // The folder has been deleted. All add-ins defined in that folder should also be deleted.
 
105
                                scanResult.RegenerateRelationData = true;
 
106
                                scanResult.ChangesFound = true;
 
107
                                if (scanResult.CheckOnly)
 
108
                                        return;
 
109
                                database.DeleteFolderInfo (monitor, folderInfo);
 
110
                        }
 
111
                        
 
112
                        if (scanResult.LocateAssembliesOnly)
 
113
                                return;
 
114
                        
 
115
                        // Look for deleted add-ins.
 
116
                        
 
117
                        UpdateDeletedAddins (monitor, folderInfo, scanResult);
 
118
                }
 
119
                
 
120
                public void UpdateDeletedAddins (IProgressStatus monitor, AddinScanFolderInfo folderInfo, AddinScanResult scanResult)
 
121
                {
 
122
                        ArrayList missing = folderInfo.GetMissingAddins ();
 
123
                        if (missing.Count > 0) {
 
124
                                if (Directory.Exists (folderInfo.Folder))
 
125
                                        scanResult.ModifiedFolderInfos.Add (folderInfo);
 
126
                                scanResult.ChangesFound = true;
 
127
                                if (scanResult.CheckOnly)
 
128
                                        return;
 
129
                                        
 
130
                                foreach (AddinFileInfo info in missing) {
 
131
                                        if (info.IsRoot)
 
132
                                                database.UninstallRootAddin (monitor, info.AddinId, info.File, scanResult);
 
133
                                        else
 
134
                                                database.UninstallAddin (monitor, info.AddinId, scanResult);
 
135
                                }
 
136
                        }
 
137
                }
 
138
                
 
139
                void RegisterFileToScan (IProgressStatus monitor, string file, AddinScanResult scanResult, AddinScanFolderInfo folderInfo)
 
140
                {
 
141
                        if (scanResult.LocateAssembliesOnly)
 
142
                                return;
 
143
 
 
144
                        AddinFileInfo finfo = folderInfo.GetAddinFileInfo (file);
 
145
                        bool added = false;
 
146
                           
 
147
                        if (finfo != null && File.GetLastWriteTime (file) == finfo.LastScan && !scanResult.RegenerateAllData) {
 
148
                                if (finfo.ScanError) {
 
149
                                        // Always schedule the file for scan if there was an error in a previous scan.
 
150
                                        // However, don't set ChangesFound=true, in this way if there isn't any other
 
151
                                        // change in the registry, the file won't be scanned again.
 
152
                                        scanResult.AddFileToScan (file, folderInfo);
 
153
                                        added = true;
 
154
                                }
 
155
                        
 
156
                                if (finfo.AddinId == null || finfo.AddinId.Length == 0)
 
157
                                        return;
 
158
                                if (!finfo.IsRoot) {
 
159
                                        if (database.AddinDescriptionExists (finfo.AddinId))
 
160
                                                return;
 
161
                                } else {
 
162
                                        if (database.HostDescriptionExists (finfo.AddinId, file))
 
163
                                                return;
 
164
                                }
 
165
                        }
 
166
                        
 
167
                        scanResult.ChangesFound = true;
 
168
                        
 
169
                        if (!scanResult.CheckOnly && !added)
 
170
                                scanResult.AddFileToScan (file, folderInfo);
 
171
                }
 
172
                
 
173
                public void ScanFile (IProgressStatus monitor, string file, AddinScanFolderInfo folderInfo, AddinScanResult scanResult)
 
174
                {
 
175
                        if (scanResult.IgnoreFile (file)) {
 
176
                                // The file must be ignored. Maybe it caused a crash in a previous scan, or it
 
177
                                // might be included by a .addin file (in which case it will be scanned when processing
 
178
                                // the .addin file).
 
179
                                folderInfo.SetLastScanTime (file, null, false, File.GetLastWriteTime (file), true);
 
180
                                return;
 
181
                        }
 
182
                        
 
183
                        if (monitor.LogLevel > 1)
 
184
                                monitor.Log ("Scanning file: " + file);
 
185
                        
 
186
                        // Log the file to be scanned, so in case of a process crash the main process
 
187
                        // will know what crashed
 
188
                        monitor.Log ("plog:scan:" + file);
 
189
                        
 
190
                        string scannedAddinId = null;
 
191
                        bool scannedIsRoot = false;
 
192
                        bool scanSuccessful = false;
 
193
                        
 
194
                        try {
 
195
                                string ext = Path.GetExtension (file);
 
196
                                AddinDescription config = null;
 
197
                                
 
198
                                if (ext == ".dll" || ext == ".exe")
 
199
                                        scanSuccessful = ScanAssembly (monitor, file, scanResult, out config);
 
200
                                else
 
201
                                        scanSuccessful = ScanConfigAssemblies (monitor, file, scanResult, out config);
 
202
 
 
203
                                if (config != null) {
 
204
                                        
 
205
                                        AddinFileInfo fi = folderInfo.GetAddinFileInfo (file);
 
206
                                        
 
207
                                        // If version is not specified, make up one
 
208
                                        if (config.Version.Length == 0) {
 
209
                                                config.Version = "0.0.0.0";
 
210
                                        }
 
211
                                        
 
212
                                        if (config.LocalId.Length == 0) {
 
213
                                                // Generate an internal id for this add-in
 
214
                                                config.LocalId = database.GetUniqueAddinId (file, (fi != null ? fi.AddinId : null), config.Namespace, config.Version);
 
215
                                                config.HasUserId = false;
 
216
                                        }
 
217
                                        
 
218
                                        // Check errors in the description
 
219
                                        StringCollection errors = config.Verify ();
 
220
                                        
 
221
                                        if (database.IsGlobalRegistry && config.AddinId.IndexOf ('.') == -1) {
 
222
                                                errors.Add ("Add-ins registered in the global registry must have a namespace.");
 
223
                                        }
 
224
                                            
 
225
                                        if (errors.Count > 0) {
 
226
                                                scanSuccessful = false;
 
227
                                                monitor.ReportError ("Errors found in add-in '" + file + ":", null);
 
228
                                                foreach (string err in errors)
 
229
                                                        monitor.ReportError (err, null);
 
230
                                        }
 
231
                                
 
232
                                        // Make sure all extensions sets are initialized with the correct add-in id
 
233
                                        
 
234
                                        config.SetExtensionsAddinId (config.AddinId);
 
235
                                        
 
236
                                        scanResult.ChangesFound = true;
 
237
                                        
 
238
                                        // If the add-in already existed, try to reuse the relation data it had.
 
239
                                        // Also, the dependencies of the old add-in need to be re-analized
 
240
                                        
 
241
                                        AddinDescription existingDescription = null;
 
242
                                        bool res;
 
243
                                        
 
244
                                        if (config.IsRoot)
 
245
                                                res = database.GetHostDescription (monitor, config.AddinId, config.AddinFile, out existingDescription);
 
246
                                        else
 
247
                                                res = database.GetAddinDescription (monitor, config.AddinId, out existingDescription);
 
248
                                        
 
249
                                        // If we can't get information about the old assembly, just regenerate all relation data
 
250
                                        if (!res)
 
251
                                                scanResult.RegenerateRelationData = true;
 
252
                                        
 
253
                                        string replaceFileName = null;
 
254
                                        
 
255
                                        if (existingDescription != null) {
 
256
                                                // Reuse old relation data
 
257
                                                config.MergeExternalData (existingDescription);
 
258
                                                Util.AddDependencies (existingDescription, scanResult);
 
259
                                                replaceFileName = existingDescription.FileName;
 
260
                                        }
 
261
                                        
 
262
                                        // If the scanned file results in an add-in version different from the one obtained from
 
263
                                        // previous scans, the old add-in needs to be uninstalled.
 
264
                                        if (fi != null && fi.AddinId != null && fi.AddinId != config.AddinId) {
 
265
                                                if (fi.IsRoot)
 
266
                                                        database.UninstallRootAddin (monitor, fi.AddinId, file, scanResult);
 
267
                                                else
 
268
                                                        database.UninstallAddin (monitor, fi.AddinId, scanResult);
 
269
                                                
 
270
                                                // If the add-in version has changed, regenerate everything again since old data can't be reused
 
271
                                                if (Addin.GetIdName (fi.AddinId) == Addin.GetIdName (config.AddinId))
 
272
                                                        scanResult.RegenerateRelationData = true;
 
273
                                        }
 
274
                                        
 
275
                                        // If a description could be generated, save it now (if the scan was successful)
 
276
                                        if (scanSuccessful) {
 
277
                                                if (database.SaveDescription (monitor, config, replaceFileName)) {
 
278
                                                        // The new dependencies also have to be updated
 
279
                                                        Util.AddDependencies (config, scanResult);
 
280
                                                        scanResult.AddAddinToUpdateRelations (config.AddinId);
 
281
                                                        scannedAddinId = config.AddinId;
 
282
                                                        scannedIsRoot = config.IsRoot;
 
283
                                                        return;
 
284
                                                }
 
285
                                        }
 
286
                                }
 
287
                        }
 
288
                        catch (Exception ex) {
 
289
                                monitor.ReportError ("Unexpected error while scanning file: " + file, ex);
 
290
                        }
 
291
                        finally {
 
292
                                folderInfo.SetLastScanTime (file, scannedAddinId, scannedIsRoot, File.GetLastWriteTime (file), !scanSuccessful);
 
293
                                monitor.Log ("plog:endscan");
 
294
                        }
 
295
                }
 
296
                
 
297
                public AddinDescription ScanSingleFile (IProgressStatus monitor, string file, AddinScanResult scanResult)
 
298
                {
 
299
                        AddinDescription config = null;
 
300
                        
 
301
                        if (monitor.LogLevel > 1)
 
302
                                monitor.Log ("Scanning file: " + file);
 
303
                                
 
304
                        monitor.Log ("plog:scan:" + file);
 
305
                        
 
306
                        try {
 
307
                                string ext = Path.GetExtension (file);
 
308
                                bool scanSuccessful;
 
309
                                
 
310
                                if (ext == ".dll" || ext == ".exe")
 
311
                                        scanSuccessful = ScanAssembly (monitor, file, scanResult, out config);
 
312
                                else
 
313
                                        scanSuccessful = ScanConfigAssemblies (monitor, file, scanResult, out config);
 
314
 
 
315
                                if (scanSuccessful && config != null) {
 
316
                                        
 
317
                                        if (config.Version.Length == 0)
 
318
                                                config.Version = "0.0.0.0";
 
319
                                        
 
320
                                        if (config.LocalId.Length == 0) {
 
321
                                                // Generate an internal id for this add-in
 
322
                                                config.LocalId = database.GetUniqueAddinId (file, "", config.Namespace, config.Version);
 
323
                                        }
 
324
                                }
 
325
                        }
 
326
                        catch (Exception ex) {
 
327
                                monitor.ReportError ("Unexpected error while scanning file: " + file, ex);
 
328
                        } finally {
 
329
                                monitor.Log ("plog:endscan");
 
330
                        }
 
331
                        return config;
 
332
                }
 
333
                
 
334
                public void ScanAddinsFile (IProgressStatus monitor, string file, AddinScanResult scanResult)
 
335
                {
 
336
                        XmlTextReader r = null;
 
337
                        StringCollection directories = new StringCollection ();
 
338
                        StringCollection directoriesWithSubdirs = new StringCollection ();
 
339
                        try {
 
340
                                r = new XmlTextReader (new StreamReader (file));
 
341
                                r.MoveToContent ();
 
342
                                if (r.IsEmptyElement)
 
343
                                        return;
 
344
                                r.ReadStartElement ();
 
345
                                r.MoveToContent ();
 
346
                                while (r.NodeType != XmlNodeType.EndElement) {
 
347
                                        if (r.NodeType == XmlNodeType.Element && r.LocalName == "Directory") {
 
348
                                                string subs = r.GetAttribute ("include-subdirs");
 
349
                                                string path = r.ReadElementString ().Trim ();
 
350
                                                if (path.Length > 0) {
 
351
                                                        if (subs == "true")
 
352
                                                                directoriesWithSubdirs.Add (path);
 
353
                                                        else
 
354
                                                                directories.Add (path);
 
355
                                                }
 
356
                                        }
 
357
                                        else if (r.NodeType == XmlNodeType.Element && r.LocalName == "GacAssembly") {
 
358
                                                string aname = r.ReadElementString ().Trim ();
 
359
                                                if (aname.Length > 0) {
 
360
                                                        aname = Util.GetGacPath (aname);
 
361
                                                        if (aname != null)
 
362
                                                                directories.Add (aname);
 
363
                                                }
 
364
                                        }
 
365
                                        else
 
366
                                                r.Skip ();
 
367
                                        r.MoveToContent ();
 
368
                                }
 
369
                        } catch (Exception ex) {
 
370
                                monitor.ReportError ("Could not process addins file: " + file, ex);
 
371
                                return;
 
372
                        } finally {
 
373
                                if (r != null)
 
374
                                        r.Close ();
 
375
                        }
 
376
                        foreach (string d in directories) {
 
377
                                string dir = d;
 
378
                                if (!Path.IsPathRooted (dir))
 
379
                                        dir = Path.Combine (Path.GetDirectoryName (file), dir);
 
380
                                ScanFolder (monitor, dir, scanResult);
 
381
                        }
 
382
                        foreach (string d in directoriesWithSubdirs) {
 
383
                                string dir = d;
 
384
                                if (!Path.IsPathRooted (dir))
 
385
                                        dir = Path.Combine (Path.GetDirectoryName (file), dir);
 
386
                                ScanFolderRec (monitor, dir, scanResult);
 
387
                        }
 
388
                }
 
389
                
 
390
                public void ScanFolderRec (IProgressStatus monitor, string dir, AddinScanResult scanResult)
 
391
                {
 
392
                        ScanFolder (monitor, dir, scanResult);
 
393
                        
 
394
                        if (!Directory.Exists (dir))
 
395
                                return;
 
396
                                
 
397
                        foreach (string sd in Directory.GetDirectories (dir))
 
398
                                ScanFolderRec (monitor, sd, scanResult);
 
399
                }
 
400
                
 
401
                bool ScanConfigAssemblies (IProgressStatus monitor, string filePath, AddinScanResult scanResult, out AddinDescription config)
 
402
                {
 
403
                        config = null;
 
404
                        
 
405
                        try {
 
406
                                string basePath = Path.GetDirectoryName (filePath);
 
407
                                
 
408
                                config = AddinDescription.Read (filePath);
 
409
                                config.BasePath = basePath;
 
410
                                config.AddinFile = filePath;
 
411
                                
 
412
                                return ScanDescription (monitor, config, null, scanResult);
 
413
                        }
 
414
                        catch (Exception ex) {
 
415
                                // Something went wrong while scanning the assembly. We'll ignore it for now.
 
416
                                monitor.ReportError ("There was an error while scanning add-in: " + filePath, ex);
 
417
                                return false;
 
418
                        }
 
419
                }
 
420
                
 
421
                bool ScanAssembly (IProgressStatus monitor, string filePath, AddinScanResult scanResult, out AddinDescription config)
 
422
                {
 
423
                        config = null;
 
424
                                
 
425
                        try {
 
426
                                Assembly asm = Util.LoadAssemblyForReflection (filePath);
 
427
                                
 
428
                                // Get the config file from the resources, if there is one
 
429
                                
 
430
                                string configFile = null;
 
431
                                foreach (string res in asm.GetManifestResourceNames ()) {
 
432
                                        if (res.EndsWith (".addin") || res.EndsWith (".addin.xml")) {
 
433
                                                configFile = res;
 
434
                                                break;
 
435
                                        }
 
436
                                }
 
437
                                
 
438
                                if (configFile != null) {
 
439
                                        using (Stream s = asm.GetManifestResourceStream (configFile)) {
 
440
                                                string asmFile = new Uri (asm.CodeBase).LocalPath;
 
441
                                                config = AddinDescription.Read (s, Path.GetDirectoryName (asmFile));
 
442
                                        }
 
443
                                }
 
444
                                else {
 
445
                                        // On this case, only scan the assembly if it has the Addin attribute.
 
446
                                        AddinAttribute att = (AddinAttribute) Attribute.GetCustomAttribute (asm, typeof(AddinAttribute), false);
 
447
                                        if (att == null) {
 
448
                                                config = null;
 
449
                                                return true;
 
450
                                        }
 
451
                                        config = new AddinDescription ();
 
452
                                }
 
453
                                
 
454
                                config.BasePath = Path.GetDirectoryName (filePath);
 
455
                                config.AddinFile = filePath;
 
456
                                
 
457
                                string rasmFile = Path.GetFileName (filePath);
 
458
                                if (!config.MainModule.Assemblies.Contains (rasmFile))
 
459
                                        config.MainModule.Assemblies.Add (rasmFile);
 
460
                                
 
461
                                return ScanDescription (monitor, config, asm, scanResult);
 
462
                        }
 
463
                        catch (Exception ex) {
 
464
                                // Something went wrong while scanning the assembly. We'll ignore it for now.
 
465
                                monitor.ReportError ("There was an error while scanning assembly: " + filePath, ex);
 
466
                                return false;
 
467
                        }
 
468
                }
 
469
 
 
470
                bool ScanDescription (IProgressStatus monitor, AddinDescription config, Assembly rootAssembly, AddinScanResult scanResult)
 
471
                {
 
472
                        // First of all scan the main module
 
473
                        
 
474
                        ArrayList assemblies = new ArrayList ();
 
475
                        ArrayList asmFiles = new ArrayList ();
 
476
                        ArrayList hostExtensionClasses = new ArrayList ();
 
477
                        
 
478
                        try {
 
479
                                // Add all data files to the ignore file list. It avoids scanning assemblies
 
480
                                // which are included as 'data' in an add-in.
 
481
                                foreach (string df in config.AllFiles) {
 
482
                                        string file = Path.Combine (config.BasePath, df);
 
483
                                        scanResult.AddFileToIgnore (Util.GetFullPath (file));
 
484
                                }
 
485
                                
 
486
                                foreach (string s in config.MainModule.Assemblies) {
 
487
                                        string asmFile = Path.Combine (config.BasePath, s);
 
488
                                        asmFiles.Add (asmFile);
 
489
                                        Assembly asm = Util.LoadAssemblyForReflection (asmFile);
 
490
                                        assemblies.Add (asm);
 
491
                                        scanResult.AddFileToIgnore (Util.GetFullPath (asmFile));
 
492
                                }
 
493
                                
 
494
                                foreach (Assembly asm in assemblies)
 
495
                                        ScanAssemblyAddinHeaders (config, asm, scanResult);
 
496
                                
 
497
                                // The add-in id and version must be already assigned at this point
 
498
                                
 
499
                                // Clean host data from the index. New data will be added.
 
500
                                if (scanResult.HostIndex != null)
 
501
                                        scanResult.HostIndex.RemoveHostData (config.AddinId, config.AddinFile);
 
502
 
 
503
                                foreach (Assembly asm in assemblies)
 
504
                                        ScanAssemblyContents (config, asm, hostExtensionClasses, scanResult);
 
505
                                
 
506
                                if (config.IsRoot && scanResult.HostIndex != null) {
 
507
                                        // If the add-in is a root, register its assemblies
 
508
                                        foreach (string asmFile in asmFiles)
 
509
                                                scanResult.HostIndex.RegisterAssembly (asmFile, config.AddinId, config.AddinFile);
 
510
                                }
 
511
                                
 
512
                        } catch (Exception ex) {
 
513
                                if (monitor.LogLevel > 1)
 
514
                                        monitor.Log ("Could not load some add-in assemblies: " + ex.Message);
 
515
                                scanResult.AddFileToWithFailure (config.AddinFile);
 
516
                                return false;
 
517
                        }
 
518
                        
 
519
                        foreach (Type t in hostExtensionClasses) {
 
520
                                RegisterHostTypeNode (config, t, assemblies);
 
521
                        }
 
522
                        
 
523
                        // Extension node types may have child nodes declared as attributes. Find them.
 
524
                        
 
525
                        Hashtable internalNodeSets = new Hashtable ();
 
526
                        
 
527
                        foreach (ExtensionNodeSet eset in config.ExtensionNodeSets)
 
528
                                ScanNodeSet (config, eset, assemblies, internalNodeSets);
 
529
                        
 
530
                        foreach (ExtensionPoint ep in config.ExtensionPoints) {
 
531
                                ScanNodeSet (config, ep.NodeSet, assemblies, internalNodeSets);
 
532
                        }
 
533
                
 
534
                        // Now scan all modules
 
535
                        
 
536
                        if (!config.IsRoot) {
 
537
                                foreach (ModuleDescription mod in config.OptionalModules) {
 
538
                                        try {
 
539
                                                assemblies.Clear ();
 
540
                                                asmFiles.Clear ();
 
541
                                                foreach (string s in mod.Assemblies) {
 
542
                                                        string asmFile = Path.Combine (config.BasePath, s);
 
543
                                                        asmFiles.Add (asmFile);
 
544
                                                        Assembly asm = Util.LoadAssemblyForReflection (asmFile);
 
545
                                                        assemblies.Add (asm);
 
546
                                                        scanResult.AddFileToIgnore (Util.GetFullPath (asmFile));
 
547
                                                }
 
548
                                                foreach (Assembly asm in assemblies)
 
549
                                                        ScanAssemblyContents (config, asm, null, scanResult);
 
550
                                
 
551
                                                if (config.IsRoot && scanResult.HostIndex != null) {
 
552
                                                        // If the add-in is a root, register its assemblies
 
553
                                                        foreach (string asmFile in asmFiles)
 
554
                                                                scanResult.HostIndex.RegisterAssembly (asmFile, config.AddinId, config.AddinFile);
 
555
                                                }
 
556
                                                
 
557
                                        } catch (Exception ex) {
 
558
                                                if (monitor.LogLevel > 1)
 
559
                                                        monitor.Log ("Could not load some add-in assemblies: " + ex.Message);
 
560
                                                scanResult.AddFileToWithFailure (config.AddinFile);
 
561
                                        }
 
562
                                }
 
563
                        }
 
564
                        
 
565
                        return true;
 
566
                }
 
567
                
 
568
                void ScanAssemblyAddinHeaders (AddinDescription config, Assembly asm, AddinScanResult scanResult)
 
569
                {
 
570
                        // Get basic add-in information
 
571
                        AddinAttribute att = (AddinAttribute) Attribute.GetCustomAttribute (asm, typeof(AddinAttribute), false);
 
572
                        if (att != null) {
 
573
                                if (att.Id.Length > 0)
 
574
                                        config.LocalId = att.Id;
 
575
                                if (att.Version.Length > 0)
 
576
                                        config.Version = att.Version;
 
577
                                if (att.Namespace.Length > 0)
 
578
                                        config.Namespace = att.Namespace;
 
579
                                if (att.Category.Length > 0)
 
580
                                        config.Category = att.Category;
 
581
                                config.IsRoot = att is AddinRootAttribute;
 
582
                        }
 
583
                }
 
584
                
 
585
                void ScanAssemblyContents (AddinDescription config, Assembly asm, ArrayList hostExtensionClasses, AddinScanResult scanResult)
 
586
                {
 
587
                        // Get dependencies
 
588
                        
 
589
                        object[] deps = asm.GetCustomAttributes (typeof(AddinDependencyAttribute), false);
 
590
                        foreach (AddinDependencyAttribute dep in deps) {
 
591
                                AddinDependency adep = new AddinDependency ();
 
592
                                adep.AddinId = dep.Id;
 
593
                                adep.Version = dep.Version;
 
594
                                config.MainModule.Dependencies.Add (adep);
 
595
                        }
 
596
                        
 
597
                        // Get extension points
 
598
                        
 
599
                        object[] extPoints = asm.GetCustomAttributes (typeof(ExtensionPointAttribute), false);
 
600
                        foreach (ExtensionPointAttribute ext in extPoints) {
 
601
                                ExtensionPoint ep = config.AddExtensionPoint (ext.Path);
 
602
                                ep.Description = ext.Description;
 
603
                                ep.Name = ext.Name;
 
604
                                ep.AddExtensionNode (ext.NodeName, ext.NodeType.FullName);
 
605
                        }
 
606
                        
 
607
                        foreach (Type t in asm.GetTypes ()) {
 
608
                                
 
609
                                if (Attribute.IsDefined (t, typeof(ExtensionAttribute))) {
 
610
                                        foreach (ExtensionAttribute eatt in t.GetCustomAttributes (typeof(ExtensionAttribute), false)) {
 
611
                                                string path;
 
612
                                                string nodeName;
 
613
                                                
 
614
                                                if (eatt.Path.Length == 0) {
 
615
                                                        if (config.IsRoot) {
 
616
                                                                // The extension point must be one of the defined by the assembly
 
617
                                                                // Look for it later, when the assembly has been fully scanned.
 
618
                                                                hostExtensionClasses.Add (t);
 
619
                                                                continue;
 
620
                                                        }
 
621
                                                        else {
 
622
                                                                path = GetBaseTypeNameList (t);
 
623
                                                                if (path == "$") {
 
624
                                                                        // The type does not implement any interface and has no superclass.
 
625
                                                                        // Will be reported later as an error.
 
626
                                                                        path = "$" + t.FullName;
 
627
                                                                }
 
628
                                                                nodeName = "Type";
 
629
                                                        }
 
630
                                                } else {
 
631
                                                        path = eatt.Path;
 
632
                                                        nodeName = eatt.NodeName;
 
633
                                                }
 
634
                                                        
 
635
                                                ExtensionNodeDescription elem = config.MainModule.AddExtensionNode (path, nodeName);
 
636
                                                if (eatt.Id.Length > 0) {
 
637
                                                        elem.SetAttribute ("id", eatt.Id);
 
638
                                                        elem.SetAttribute ("type", t.FullName);
 
639
                                                } else {
 
640
                                                        elem.SetAttribute ("id", t.FullName);
 
641
                                                }
 
642
                                                if (eatt.InsertAfter.Length > 0)
 
643
                                                        elem.SetAttribute ("insertafter", eatt.InsertAfter);
 
644
                                                if (eatt.InsertBefore.Length > 0)
 
645
                                                        elem.SetAttribute ("insertbefore", eatt.InsertAfter);
 
646
                                        }
 
647
                                }
 
648
                                else if (Attribute.IsDefined (t, typeof(TypeExtensionPointAttribute))) {
 
649
                                        foreach (TypeExtensionPointAttribute epa in t.GetCustomAttributes (typeof(TypeExtensionPointAttribute), false)) {
 
650
                                                ExtensionPoint ep;
 
651
                                                
 
652
                                                ExtensionNodeType nt = new ExtensionNodeType ();
 
653
                                                
 
654
                                                if (epa.Path.Length > 0) {
 
655
                                                        ep = config.AddExtensionPoint (epa.Path);
 
656
                                                }
 
657
                                                else {
 
658
                                                        ep = config.AddExtensionPoint (GetDefaultTypeExtensionPath (config, t));
 
659
                                                        nt.ObjectTypeName = t.FullName;
 
660
                                                }
 
661
                                                nt.Id = epa.NodeName;
 
662
                                                nt.TypeName = epa.NodeType.FullName;
 
663
                                                ep.NodeSet.NodeTypes.Add (nt);
 
664
                                                ep.Description = epa.Description;
 
665
                                                ep.Name = epa.Name;
 
666
                                                ep.RootAddin = config.AddinId;
 
667
                                                ep.SetExtensionsAddinId (config.AddinId);
 
668
                                        }
 
669
                                }
 
670
                        }
 
671
                }
 
672
                
 
673
                void ScanNodeSet (AddinDescription config, ExtensionNodeSet nset, ArrayList assemblies, Hashtable internalNodeSets)
 
674
                {
 
675
                        foreach (ExtensionNodeType nt in nset.NodeTypes)
 
676
                                ScanNodeType (config, nt, assemblies, internalNodeSets);
 
677
                }
 
678
                
 
679
                void ScanNodeType (AddinDescription config, ExtensionNodeType nt, ArrayList assemblies, Hashtable internalNodeSets)
 
680
                {
 
681
                        if (nt.TypeName.Length == 0)
 
682
                                nt.TypeName = "Mono.Addins.TypeExtensionNode";
 
683
                        
 
684
                        Type ntype = FindAddinType (nt.TypeName, assemblies);
 
685
                        if (ntype == null)
 
686
                                return;
 
687
                        
 
688
                        // Add type information declared with attributes in the code
 
689
                        ExtensionNodeAttribute nodeAtt = (ExtensionNodeAttribute) Attribute.GetCustomAttribute (ntype, typeof(ExtensionNodeAttribute), true);
 
690
                        if (nodeAtt != null) {
 
691
                                if (nt.Id.Length == 0 && nodeAtt.NodeName.Length > 0)
 
692
                                        nt.Id = nodeAtt.NodeName;
 
693
                                if (nt.Description.Length == 0 && nodeAtt.Description.Length > 0)
 
694
                                        nt.Description = nodeAtt.Description;
 
695
                        } else {
 
696
                                // Use the node type name as default name
 
697
                                if (nt.Id.Length == 0)
 
698
                                        nt.Id = ntype.Name;
 
699
                        }
 
700
                        
 
701
                        // Add information about attributes
 
702
                        object[] fieldAtts = ntype.GetCustomAttributes (typeof(NodeAttributeAttribute), true);
 
703
                        foreach (NodeAttributeAttribute fatt in fieldAtts) {
 
704
                                NodeTypeAttribute natt = new NodeTypeAttribute ();
 
705
                                natt.Name = fatt.Name;
 
706
                                natt.Required = fatt.Required;
 
707
                                if (fatt.Type != null)
 
708
                                        natt.Type = fatt.Type.FullName;
 
709
                                if (fatt.Description.Length > 0)
 
710
                                        natt.Description = fatt.Description;
 
711
                                nt.Attributes.Add (natt);
 
712
                        }
 
713
                        
 
714
                        // Check if the type has NodeAttribute attributes applied to fields.
 
715
                        foreach (FieldInfo field in ntype.GetFields (BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) {
 
716
                                NodeAttributeAttribute fatt = (NodeAttributeAttribute) Attribute.GetCustomAttribute (field, typeof(NodeAttributeAttribute));
 
717
                                if (fatt != null) {
 
718
                                        NodeTypeAttribute natt = new NodeTypeAttribute ();
 
719
                                        if (fatt.Name.Length > 0)
 
720
                                                natt.Name = fatt.Name;
 
721
                                        else
 
722
                                                natt.Name = field.Name;
 
723
                                        if (fatt.Description.Length > 0)
 
724
                                                natt.Description = fatt.Description;
 
725
                                        natt.Type = field.FieldType.FullName;
 
726
                                        natt.Required = fatt.Required;
 
727
                                        nt.Attributes.Add (natt);
 
728
                                }
 
729
                        }
 
730
                        
 
731
                        // Check if the extension type allows children by looking for [ExtensionNodeChild] attributes.
 
732
                        // First of all, look in the internalNodeSets hashtable, which is being used as cache
 
733
                        
 
734
                        string childSet = (string) internalNodeSets [nt.TypeName];
 
735
                        
 
736
                        if (childSet == null) {
 
737
                                object[] ats = ntype.GetCustomAttributes (typeof(ExtensionNodeChildAttribute), true);
 
738
                                if (ats.Length > 0) {
 
739
                                        // Create a new node set for this type. It is necessary to create a new node set
 
740
                                        // instead of just adding child ExtensionNodeType objects to the this node type
 
741
                                        // because child types references can be recursive.
 
742
                                        ExtensionNodeSet internalSet = new ExtensionNodeSet ();
 
743
                                        internalSet.Id = ntype.Name + "_" + Guid.NewGuid().ToString ();
 
744
                                        foreach (ExtensionNodeChildAttribute at in ats) {
 
745
                                                ExtensionNodeType internalType = new ExtensionNodeType ();
 
746
                                                internalType.Id = at.NodeName;
 
747
                                                internalType.TypeName = at.ExtensionNodeType.FullName;
 
748
                                                internalSet.NodeTypes.Add (internalType);
 
749
                                        }
 
750
                                        config.ExtensionNodeSets.Add (internalSet);
 
751
                                        nt.NodeSets.Add (internalSet.Id);
 
752
                                        
 
753
                                        // Register the new set in a hashtable, to allow recursive references to the
 
754
                                        // same internal set.
 
755
                                        internalNodeSets [nt.TypeName] = internalSet.Id;
 
756
                                        internalNodeSets [ntype.AssemblyQualifiedName] = internalSet.Id;
 
757
                                        ScanNodeSet (config, internalSet, assemblies, internalNodeSets);
 
758
                                }
 
759
                        }
 
760
                        else {
 
761
                                if (childSet.Length == 0) {
 
762
                                        // The extension type does not declare children.
 
763
                                        return;
 
764
                                }
 
765
                                // The extension type can have children. The allowed children are
 
766
                                // defined in this extension set.
 
767
                                nt.NodeSets.Add (childSet);
 
768
                                return;
 
769
                        }
 
770
                        
 
771
                        ScanNodeSet (config, nt, assemblies, internalNodeSets);
 
772
                }
 
773
                
 
774
                string GetBaseTypeNameList (Type type)
 
775
                {
 
776
                        StringBuilder sb = new StringBuilder ("$");
 
777
                        Type btype = type.BaseType;
 
778
                        while (btype != typeof(object)) {
 
779
                                sb.Append (btype.FullName).Append (',');
 
780
                                btype = btype.BaseType;
 
781
                        }
 
782
                        foreach (Type iterf in type.GetInterfaces ()) {
 
783
                                sb.Append (iterf.FullName).Append (',');
 
784
                        }
 
785
                        if (sb.Length > 0)
 
786
                                sb.Remove (sb.Length - 1, 1);
 
787
                        return sb.ToString ();
 
788
                }
 
789
                
 
790
                void RegisterHostTypeNode (AddinDescription config, Type t, ArrayList assemblies)
 
791
                {
 
792
                        foreach (ExtensionAttribute eatt in t.GetCustomAttributes (typeof(ExtensionAttribute), false)) {
 
793
                                if (eatt.Path.Length > 0)
 
794
                                        continue;
 
795
                                
 
796
                                foreach (ExtensionPoint ep in config.ExtensionPoints) {
 
797
                                        foreach (ExtensionNodeType nt in ep.NodeSet.NodeTypes) {
 
798
                                                if (nt.ObjectTypeName.Length == 0)
 
799
                                                        continue;
 
800
                                                Type etype = FindAddinType (nt.ObjectTypeName, assemblies);
 
801
                                                if (etype != null && etype.IsAssignableFrom (t)) {
 
802
                                                        RegisterTypeNode (config, eatt, ep.Path, nt.Id, t);
 
803
                                                        return;
 
804
                                                }
 
805
                                        }
 
806
                                }
 
807
                        }
 
808
                }
 
809
                
 
810
                Type FindAddinType (string typeName, ArrayList assemblies)
 
811
                {
 
812
                        // Look in the current assembly
 
813
                        Type etype = Type.GetType (typeName, false);
 
814
                        if (etype != null)
 
815
                                return etype;
 
816
                        
 
817
                        // Look in referenced assemblies
 
818
                        foreach (Assembly asm in assemblies) {
 
819
                                etype = asm.GetType (typeName);
 
820
                                if (etype != null)
 
821
                                        return etype;
 
822
                        }
 
823
                        
 
824
                        Hashtable visited = new Hashtable ();
 
825
                        
 
826
                        // Look in indirectly referenced assemblies
 
827
                        foreach (Assembly asm in assemblies) {
 
828
                                foreach (AssemblyName aref in asm.GetReferencedAssemblies ()) {
 
829
                                        if (visited.Contains (aref))
 
830
                                                continue;
 
831
                                        visited.Add (aref, aref);
 
832
                                        Assembly rasm = Assembly.Load (aref);
 
833
                                        etype = rasm.GetType (typeName);
 
834
                                        if (etype != null)
 
835
                                                return etype;
 
836
                                }
 
837
                        }
 
838
                        return null;
 
839
                }
 
840
 
 
841
                void RegisterTypeNode (AddinDescription config, ExtensionAttribute eatt, string path, string nodeName, Type t)
 
842
                {
 
843
                        ExtensionNodeDescription elem = config.MainModule.AddExtensionNode (path, nodeName);
 
844
                        if (eatt.Id.Length > 0) {
 
845
                                elem.SetAttribute ("id", eatt.Id);
 
846
                                elem.SetAttribute ("type", t.FullName);
 
847
                        } else {
 
848
                                elem.SetAttribute ("id", t.FullName);
 
849
                        }
 
850
                        if (eatt.InsertAfter.Length > 0)
 
851
                                elem.SetAttribute ("insertafter", eatt.InsertAfter);
 
852
                        if (eatt.InsertBefore.Length > 0)
 
853
                                elem.SetAttribute ("insertbefore", eatt.InsertAfter);
 
854
                }
 
855
                
 
856
                internal string GetDefaultTypeExtensionPath (AddinDescription desc, Type type)
 
857
                {
 
858
                        return "/" + Addin.GetIdName (desc.AddinId) + "/TypeExtensions/" + type.FullName;
 
859
                }
 
860
        }
 
861
}