Merge pull request #101 from mono/gen-data-files
Adds support for generating add-in data cache files
This commit is contained in:
Коммит
041854b7aa
|
@ -522,6 +522,20 @@ namespace Mono.Addins.Setup
|
|||
registry.Rebuild (new ConsoleProgressStatus (verbose));
|
||||
}
|
||||
|
||||
void GenerateAddinScanDataFiles (string[] args)
|
||||
{
|
||||
bool recursive = false;
|
||||
int i = 0;
|
||||
if (args.Length > 0 && args [0] == "-r") {
|
||||
recursive = true;
|
||||
i = 1;
|
||||
}
|
||||
if (i >= args.Length)
|
||||
registry.GenerateAddinScanDataFiles (new ConsoleProgressStatus (verbose), recursive:recursive);
|
||||
else
|
||||
registry.GenerateAddinScanDataFiles (new ConsoleProgressStatus (verbose), args[0], recursive);
|
||||
}
|
||||
|
||||
void DumpRegistryFile (string[] args)
|
||||
{
|
||||
if (args.Length < 1)
|
||||
|
@ -1086,7 +1100,20 @@ namespace Mono.Addins.Setup
|
|||
|
||||
cmd = new SetupCommand (cat, "reg-build", "rgb", new SetupCommandHandler (RepairRegistry));
|
||||
cmd.Description = "Rebuilds the add-in registry.";
|
||||
cmd.AppendDesc ("Regenerates the add-in registry");
|
||||
cmd.AppendDesc ("Regenerates the add-in registry.");
|
||||
commands.Add (cmd);
|
||||
|
||||
cmd = new SetupCommand (cat, "reg-gen-data", "rgd", new SetupCommandHandler (GenerateAddinScanDataFiles));
|
||||
cmd.Usage = "[-r] <path>";
|
||||
cmd.Description = "Generates add-in scan data files.";
|
||||
cmd.AppendDesc ("Generates binary add-in scan data files next to each");
|
||||
cmd.AppendDesc ("add-in file. When such a file is present for an");
|
||||
cmd.AppendDesc ("add-in, the add-in scanner will load the information");
|
||||
cmd.AppendDesc ("from the data file instead of doing a full scan.");
|
||||
cmd.AppendDesc ("Data files will be generated only add-ins located");
|
||||
cmd.AppendDesc ("in the provided folder.");
|
||||
cmd.AppendDesc ("Options:");
|
||||
cmd.AppendDesc ("-r: Recursively look in subdirectories.");
|
||||
commands.Add (cmd);
|
||||
|
||||
cmd = new SetupCommand (cat, "info", null, new SetupCommandHandler (PrintAddinInfo));
|
||||
|
|
|
@ -37,6 +37,7 @@ using System.Reflection;
|
|||
using Mono.Addins.Description;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Mono.Addins.Serialization;
|
||||
|
||||
namespace Mono.Addins.Database
|
||||
{
|
||||
|
@ -53,7 +54,6 @@ namespace Mono.Addins.Database
|
|||
internal static bool RunningSetupProcess;
|
||||
bool fatalDatabseError;
|
||||
Hashtable cachedAddinSetupInfos = new Hashtable ();
|
||||
AddinScanResult currentScanResult;
|
||||
AddinHostIndex hostIndex;
|
||||
FileDatabase fileDatabase;
|
||||
string addinDbDir;
|
||||
|
@ -1023,8 +1023,14 @@ namespace Mono.Addins.Database
|
|||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
public void Repair (IProgressStatus monitor, string domain)
|
||||
|
||||
public void GenerateScanDataFiles (IProgressStatus monitor, string folder, bool recursive)
|
||||
{
|
||||
ISetupHandler setup = GetSetupHandler ();
|
||||
setup.GenerateScanDataFiles (monitor, registry, Path.GetFullPath (folder), recursive);
|
||||
}
|
||||
|
||||
public void Repair (IProgressStatus monitor, string domain, ScanOptions context = null)
|
||||
{
|
||||
using (fileDatabase.LockWrite ()) {
|
||||
try {
|
||||
|
@ -1041,10 +1047,10 @@ namespace Mono.Addins.Database
|
|||
}
|
||||
ResetBasicCachedData ();
|
||||
|
||||
Update (monitor, domain);
|
||||
Update (monitor, domain, context);
|
||||
}
|
||||
|
||||
public void Update (IProgressStatus monitor, string domain)
|
||||
public void Update (IProgressStatus monitor, string domain, ScanOptions context = null)
|
||||
{
|
||||
if (monitor == null)
|
||||
monitor = new ConsoleProgressStatus (false);
|
||||
|
@ -1079,7 +1085,7 @@ namespace Mono.Addins.Database
|
|||
}
|
||||
}
|
||||
|
||||
RunScannerProcess (monitor);
|
||||
RunScannerProcess (monitor, context);
|
||||
|
||||
ResetCachedData ();
|
||||
|
||||
|
@ -1160,29 +1166,33 @@ namespace Mono.Addins.Database
|
|||
SaveConfiguration ();
|
||||
}
|
||||
|
||||
void RunScannerProcess (IProgressStatus monitor)
|
||||
void RunScannerProcess (IProgressStatus monitor, ScanOptions context)
|
||||
{
|
||||
ISetupHandler setup = GetSetupHandler ();
|
||||
|
||||
|
||||
|
||||
IProgressStatus scanMonitor = monitor;
|
||||
ArrayList pparams = new ArrayList ();
|
||||
|
||||
context = context ?? new ScanOptions ();
|
||||
|
||||
if (fs.GetType () != typeof (AddinFileSystemExtension))
|
||||
context.FileSystemExtension = fs;
|
||||
|
||||
bool retry = false;
|
||||
do {
|
||||
try {
|
||||
if (monitor.LogLevel > 1)
|
||||
monitor.Log ("Looking for addins");
|
||||
setup.Scan (scanMonitor, registry, null, (string[]) pparams.ToArray (typeof(string)));
|
||||
setup.Scan (scanMonitor, registry, null, context);
|
||||
retry = false;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
ProcessFailedException pex = ex as ProcessFailedException;
|
||||
if (pex != null) {
|
||||
// Get the last logged operation.
|
||||
if (pex.LastLog.StartsWith ("scan:")) {
|
||||
if (pex.LastLog.StartsWith ("scan:", StringComparison.Ordinal)) {
|
||||
// It crashed while scanning a file. Add the file to the ignore list and try again.
|
||||
string file = pex.LastLog.Substring (5);
|
||||
pparams.Add (file);
|
||||
context.FilesToIgnore.Add (file);
|
||||
monitor.ReportWarning ("Could not scan file: " + file);
|
||||
retry = true;
|
||||
continue;
|
||||
|
@ -1257,64 +1267,46 @@ namespace Mono.Addins.Database
|
|||
}
|
||||
}
|
||||
|
||||
internal void ScanFolders (IProgressStatus monitor, string currentDomain, string folderToScan, StringCollection filesToIgnore)
|
||||
internal void ScanFolders (IProgressStatus monitor, string currentDomain, string folderToScan, ScanOptions context)
|
||||
{
|
||||
AddinScanResult res = new AddinScanResult ();
|
||||
res.Domain = currentDomain;
|
||||
res.AddPathsToIgnore (filesToIgnore);
|
||||
res.ScanContext.AddPathsToIgnore (context.FilesToIgnore);
|
||||
res.CleanGeneratedAddinScanDataFiles = context.CleanGeneratedAddinScanDataFiles;
|
||||
ScanFolders (monitor, res);
|
||||
}
|
||||
|
||||
internal void GenerateScanDataFilesInProcess (IProgressStatus monitor, string folderToScan, bool recursive)
|
||||
{
|
||||
using (var visitor = new AddinScanDataFileGenerator (this, registry, folderToScan)) {
|
||||
visitor.VisitFolder (monitor, folderToScan, null, recursive);
|
||||
}
|
||||
}
|
||||
|
||||
void ScanFolders (IProgressStatus monitor, AddinScanResult scanResult)
|
||||
{
|
||||
IDisposable checkLock = null;
|
||||
// All changes are done in a transaction, which won't be committed until
|
||||
// all files have been updated.
|
||||
|
||||
if (scanResult.CheckOnly)
|
||||
checkLock = fileDatabase.LockRead ();
|
||||
else {
|
||||
// All changes are done in a transaction, which won't be committed until
|
||||
// all files have been updated.
|
||||
|
||||
if (!fileDatabase.BeginTransaction ()) {
|
||||
// The database is already being updated. Can't do anything for now.
|
||||
return;
|
||||
}
|
||||
if (!fileDatabase.BeginTransaction ()) {
|
||||
// The database is already being updated. Can't do anything for now.
|
||||
return;
|
||||
}
|
||||
|
||||
EventInfo einfo = typeof(AppDomain).GetEvent ("ReflectionOnlyAssemblyResolve");
|
||||
ResolveEventHandler resolver = new ResolveEventHandler (OnResolveAddinAssembly);
|
||||
|
||||
try
|
||||
{
|
||||
// Perform the add-in scan
|
||||
|
||||
if (!scanResult.CheckOnly) {
|
||||
AppDomain.CurrentDomain.AssemblyResolve += resolver;
|
||||
if (einfo != null) einfo.AddEventHandler (AppDomain.CurrentDomain, resolver);
|
||||
}
|
||||
|
||||
InternalScanFolders (monitor, scanResult);
|
||||
|
||||
if (!scanResult.CheckOnly)
|
||||
fileDatabase.CommitTransaction ();
|
||||
fileDatabase.CommitTransaction ();
|
||||
}
|
||||
catch {
|
||||
if (!scanResult.CheckOnly)
|
||||
fileDatabase.RollbackTransaction ();
|
||||
fileDatabase.RollbackTransaction ();
|
||||
throw;
|
||||
}
|
||||
finally {
|
||||
currentScanResult = null;
|
||||
|
||||
if (scanResult.CheckOnly)
|
||||
checkLock.Dispose ();
|
||||
else {
|
||||
AppDomain.CurrentDomain.AssemblyResolve -= resolver;
|
||||
if (einfo != null) einfo.RemoveEventHandler (AppDomain.CurrentDomain, resolver);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void InternalScanFolders (IProgressStatus monitor, AddinScanResult scanResult)
|
||||
{
|
||||
try {
|
||||
|
@ -1345,63 +1337,63 @@ namespace Mono.Addins.Database
|
|||
scanResult.RegenerateAllData = true;
|
||||
}
|
||||
|
||||
AddinScanner scanner = new AddinScanner (this, scanResult, monitor);
|
||||
try {
|
||||
var updater = new AddinRegistryUpdater (this, scanResult);
|
||||
|
||||
// Check if any of the previously scanned folders has been deleted
|
||||
// Check if any of the previously scanned folders has been deleted
|
||||
|
||||
foreach (string file in Directory.GetFiles (AddinFolderCachePath, "*.data")) {
|
||||
AddinScanFolderInfo folderInfo;
|
||||
bool res = ReadFolderInfo (monitor, file, out folderInfo);
|
||||
bool validForDomain = scanResult.Domain == null || folderInfo.Domain == GlobalDomain || folderInfo.Domain == scanResult.Domain;
|
||||
if (!res || (validForDomain && !fs.DirectoryExists (folderInfo.Folder))) {
|
||||
if (res) {
|
||||
// Folder has been deleted. Remove the add-ins it had.
|
||||
scanner.UpdateDeletedAddins (monitor, folderInfo, scanResult);
|
||||
} else {
|
||||
// Folder info file corrupt. Regenerate all.
|
||||
scanResult.ChangesFound = true;
|
||||
scanResult.RegenerateRelationData = true;
|
||||
}
|
||||
|
||||
if (!scanResult.CheckOnly)
|
||||
SafeDelete (monitor, file);
|
||||
else if (scanResult.ChangesFound)
|
||||
return;
|
||||
foreach (string file in Directory.GetFiles (AddinFolderCachePath, "*.data")) {
|
||||
AddinScanFolderInfo folderInfo;
|
||||
bool res = ReadFolderInfo (monitor, file, out folderInfo);
|
||||
bool validForDomain = scanResult.Domain == null || folderInfo.Domain == GlobalDomain || folderInfo.Domain == scanResult.Domain;
|
||||
if (!res || (validForDomain && !fs.DirectoryExists (folderInfo.Folder))) {
|
||||
if (res) {
|
||||
// Folder has been deleted. Remove the add-ins it had.
|
||||
updater.UpdateDeletedAddins (monitor, folderInfo);
|
||||
} else {
|
||||
// Folder info file corrupt. Regenerate all.
|
||||
scanResult.ChangesFound = true;
|
||||
scanResult.RegenerateRelationData = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Look for changes in the add-in folders
|
||||
|
||||
if (registry.StartupDirectory != null)
|
||||
scanner.ScanFolder (monitor, registry.StartupDirectory, null, scanResult);
|
||||
|
||||
if (scanResult.CheckOnly && (scanResult.ChangesFound || monitor.IsCanceled))
|
||||
return;
|
||||
|
||||
if (scanResult.Domain == null)
|
||||
scanner.ScanFolder (monitor, HostsPath, GlobalDomain, scanResult);
|
||||
|
||||
if (scanResult.CheckOnly && (scanResult.ChangesFound || monitor.IsCanceled))
|
||||
return;
|
||||
|
||||
foreach (string dir in registry.GlobalAddinDirectories) {
|
||||
if (scanResult.CheckOnly && (scanResult.ChangesFound || monitor.IsCanceled))
|
||||
if (!scanResult.CheckOnly)
|
||||
SafeDelete (monitor, file);
|
||||
else if (scanResult.ChangesFound)
|
||||
return;
|
||||
scanner.ScanFolderRec (monitor, dir, GlobalDomain, scanResult);
|
||||
}
|
||||
}
|
||||
|
||||
if (scanResult.CheckOnly || !scanResult.ChangesFound)
|
||||
// Look for changes in the add-in folders
|
||||
|
||||
if (registry.StartupDirectory != null)
|
||||
updater.VisitFolder (monitor, registry.StartupDirectory, null, false);
|
||||
|
||||
if (scanResult.CheckOnly && (scanResult.ChangesFound || monitor.IsCanceled))
|
||||
return;
|
||||
|
||||
if (scanResult.Domain == null)
|
||||
updater.VisitFolder (monitor, HostsPath, GlobalDomain, false);
|
||||
|
||||
if (scanResult.CheckOnly && (scanResult.ChangesFound || monitor.IsCanceled))
|
||||
return;
|
||||
|
||||
foreach (string dir in registry.GlobalAddinDirectories) {
|
||||
if (scanResult.CheckOnly && (scanResult.ChangesFound || monitor.IsCanceled))
|
||||
return;
|
||||
updater.VisitFolder (monitor, dir, GlobalDomain, true);
|
||||
}
|
||||
|
||||
// Scan the files which have been modified
|
||||
if (scanResult.CheckOnly || !scanResult.ChangesFound)
|
||||
return;
|
||||
|
||||
currentScanResult = scanResult;
|
||||
// Scan the files which have been modified
|
||||
|
||||
// AssemblyIndex will contain all assemblies that were
|
||||
// found while looking for add-ins. Use it to resolve assemblies
|
||||
// while scanning those add-ins.
|
||||
|
||||
using (var scanner = new AddinScanner (this, scanResult.AssemblyIndex)) {
|
||||
foreach (FileToScan file in scanResult.FilesToScan)
|
||||
scanner.ScanFile (monitor, file.File, file.AddinScanFolderInfo, scanResult);
|
||||
} finally {
|
||||
scanner.CleanupReflector ();
|
||||
scanner.ScanFile (monitor, file, scanResult, scanResult.CleanGeneratedAddinScanDataFiles);
|
||||
}
|
||||
|
||||
// Save folder info
|
||||
|
@ -1468,17 +1460,10 @@ namespace Mono.Addins.Database
|
|||
|
||||
AddinScanResult sr = new AddinScanResult ();
|
||||
sr.Domain = domain;
|
||||
AddinScanner scanner = new AddinScanner (this, sr, progressStatus);
|
||||
|
||||
SingleFileAssemblyResolver res = new SingleFileAssemblyResolver (progressStatus, registry, scanner);
|
||||
ResolveEventHandler resolver = new ResolveEventHandler (res.Resolve);
|
||||
|
||||
EventInfo einfo = typeof(AppDomain).GetEvent ("ReflectionOnlyAssemblyResolve");
|
||||
|
||||
try {
|
||||
AppDomain.CurrentDomain.AssemblyResolve += resolver;
|
||||
if (einfo != null) einfo.AddEventHandler (AppDomain.CurrentDomain, resolver);
|
||||
|
||||
var res = new AssemblyLocatorVisitor (this, registry, true);
|
||||
|
||||
using (var scanner = new AddinScanner (this, res)) {
|
||||
AddinDescription desc = scanner.ScanSingleFile (progressStatus, file, sr);
|
||||
if (desc != null) {
|
||||
// Reset the xml doc so that it is not reused when saving. We want a brand new document
|
||||
|
@ -1486,11 +1471,6 @@ namespace Mono.Addins.Database
|
|||
desc.Save (outFile);
|
||||
}
|
||||
}
|
||||
finally {
|
||||
scanner.CleanupReflector ();
|
||||
AppDomain.CurrentDomain.AssemblyResolve -= resolver;
|
||||
if (einfo != null) einfo.RemoveEventHandler (AppDomain.CurrentDomain, resolver);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1512,18 +1492,6 @@ namespace Mono.Addins.Database
|
|||
return UnknownDomain;
|
||||
}
|
||||
|
||||
Assembly OnResolveAddinAssembly (object s, ResolveEventArgs args)
|
||||
{
|
||||
string file = currentScanResult != null ? currentScanResult.GetAssemblyLocation (args.Name) : null;
|
||||
if (file != null)
|
||||
return Util.LoadAssemblyForReflection (file);
|
||||
else {
|
||||
if (!args.Name.StartsWith ("Mono.Addins.CecilReflector"))
|
||||
Console.WriteLine ("Assembly not found: " + args.Name);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public string GetFolderConfigFile (string path)
|
||||
{
|
||||
path = Path.GetFullPath (path);
|
||||
|
@ -1780,7 +1748,7 @@ namespace Mono.Addins.Database
|
|||
if (hostIndex != null)
|
||||
hostIndex.Write (fileDatabase, HostIndexFile);
|
||||
}
|
||||
|
||||
|
||||
internal string GetUniqueAddinId (string file, string oldId, string ns, string version)
|
||||
{
|
||||
string baseId = "__" + Path.GetFileNameWithoutExtension (file);
|
||||
|
@ -1856,40 +1824,6 @@ namespace Mono.Addins.Database
|
|||
}
|
||||
}
|
||||
|
||||
class SingleFileAssemblyResolver
|
||||
{
|
||||
AddinScanResult scanResult;
|
||||
AddinScanner scanner;
|
||||
AddinRegistry registry;
|
||||
IProgressStatus progressStatus;
|
||||
|
||||
public SingleFileAssemblyResolver (IProgressStatus progressStatus, AddinRegistry registry, AddinScanner scanner)
|
||||
{
|
||||
this.scanner = scanner;
|
||||
this.registry = registry;
|
||||
this.progressStatus = progressStatus;
|
||||
}
|
||||
|
||||
public Assembly Resolve (object s, ResolveEventArgs args)
|
||||
{
|
||||
if (scanResult == null) {
|
||||
scanResult = new AddinScanResult ();
|
||||
scanResult.LocateAssembliesOnly = true;
|
||||
|
||||
if (registry.StartupDirectory != null)
|
||||
scanner.ScanFolder (progressStatus, registry.StartupDirectory, null, scanResult);
|
||||
foreach (string dir in registry.GlobalAddinDirectories)
|
||||
scanner.ScanFolderRec (progressStatus, dir, AddinDatabase.GlobalDomain, scanResult);
|
||||
}
|
||||
|
||||
string afile = scanResult.GetAssemblyLocation (args.Name);
|
||||
if (afile != null)
|
||||
return Util.LoadAssemblyForReflection (afile);
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class AddinIndex
|
||||
{
|
||||
Dictionary<string, List<AddinDescription>> addins = new Dictionary<string, List<AddinDescription>> ();
|
||||
|
|
|
@ -36,6 +36,7 @@ namespace Mono.Addins.Database
|
|||
/// File system extensions can override the behavior of the add-in scanner and provide custom rules for
|
||||
/// locating and scanning assemblies.
|
||||
/// </remarks>
|
||||
[Serializable]
|
||||
public class AddinFileSystemExtension
|
||||
{
|
||||
IAssemblyReflector reflector;
|
||||
|
@ -191,6 +192,11 @@ namespace Mono.Addins.Database
|
|||
return reflector;
|
||||
}
|
||||
|
||||
public virtual void DeleteFile (string filePath)
|
||||
{
|
||||
File.Delete (filePath);
|
||||
}
|
||||
|
||||
internal void CleanupReflector()
|
||||
{
|
||||
var disposable = reflector as IDisposable;
|
||||
|
|
|
@ -0,0 +1,195 @@
|
|||
//
|
||||
// AddinScannerBase.cs
|
||||
//
|
||||
// Author:
|
||||
// Lluis Sanchez <llsan@microsoft.com>
|
||||
//
|
||||
// Copyright (c) 2018 Microsoft
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Xml;
|
||||
|
||||
namespace Mono.Addins.Database
|
||||
{
|
||||
class AddinFolderVisitor
|
||||
{
|
||||
AddinDatabase database;
|
||||
HashSet<string> visitedFolders = new HashSet<string> ();
|
||||
|
||||
public ScanContext ScanContext { get; set; } = new ScanContext();
|
||||
|
||||
protected AddinFileSystemExtension FileSystem {
|
||||
get { return database.FileSystem; }
|
||||
}
|
||||
|
||||
public AddinFolderVisitor (AddinDatabase database)
|
||||
{
|
||||
this.database = database;
|
||||
}
|
||||
|
||||
public void VisitFolder (IProgressStatus monitor, string path, string domain, bool recursive)
|
||||
{
|
||||
path = Path.GetFullPath (path);
|
||||
|
||||
// Avoid folders including each other
|
||||
if (!visitedFolders.Add (path) || ScanContext.IgnorePath (path))
|
||||
return;
|
||||
|
||||
OnVisitFolder (monitor, path, domain, recursive);
|
||||
}
|
||||
|
||||
protected virtual void OnVisitFolder (IProgressStatus monitor, string path, string domain, bool recursive)
|
||||
{
|
||||
if (!FileSystem.DirectoryExists (path))
|
||||
return;
|
||||
|
||||
var files = FileSystem.GetFiles (path);
|
||||
|
||||
// First of all scan .addins files, since they can contain exclude paths.
|
||||
// Only extract the information, don't follow directory inclusions yet
|
||||
|
||||
List<AddinsEntry> addinsFileEntries = new List<AddinsEntry>();
|
||||
|
||||
foreach (string file in files) {
|
||||
if (Path.GetExtension (file).EndsWith (".addins", StringComparison.Ordinal))
|
||||
addinsFileEntries.AddRange (ParseAddinsFile (monitor, file, domain));
|
||||
}
|
||||
|
||||
// Now look for .addin files. Addin files must be processed before
|
||||
// assemblies, because they may add files to the ignore list (i.e., assemblies
|
||||
// included in .addin files won't be scanned twice).
|
||||
|
||||
foreach (string file in files) {
|
||||
if (!ScanContext.IgnorePath (file) && (file.EndsWith (".addin.xml", StringComparison.Ordinal) || file.EndsWith (".addin", StringComparison.Ordinal)))
|
||||
OnVisitAddinManifestFile (monitor, file);
|
||||
}
|
||||
|
||||
// Now scan assemblies. They can also add files to the ignore list.
|
||||
|
||||
foreach (string file in files) {
|
||||
if (!ScanContext.IgnorePath (file)) {
|
||||
string ext = Path.GetExtension(file).ToLower();
|
||||
if (ext == ".dll" || ext == ".exe")
|
||||
OnVisitAssemblyFile(monitor, file);
|
||||
}
|
||||
}
|
||||
|
||||
// Follow .addins file inclusions
|
||||
|
||||
foreach (var entry in addinsFileEntries) {
|
||||
string dir = entry.Folder;
|
||||
if (!Path.IsPathRooted (dir))
|
||||
dir = Path.Combine (path, entry.Folder);
|
||||
VisitFolder (monitor, dir, entry.Domain, entry.Recursive);
|
||||
}
|
||||
|
||||
// Scan subfolders
|
||||
|
||||
if (recursive) {
|
||||
foreach (string sd in FileSystem.GetDirectories (path))
|
||||
VisitFolder (monitor, sd, domain, true);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnVisitAddinManifestFile (IProgressStatus monitor, string file)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected virtual void OnVisitAssemblyFile (IProgressStatus monitor, string file)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
List<AddinsEntry> ParseAddinsFile (IProgressStatus monitor, string file, string domain)
|
||||
{
|
||||
List<AddinsEntry> entries = new List<AddinsEntry>();
|
||||
XmlTextReader r = null;
|
||||
List<string []> directories = new List<string []> ();
|
||||
List<string []> directoriesWithSubdirs = new List<string []> ();
|
||||
string basePath = Path.GetDirectoryName (file);
|
||||
|
||||
try {
|
||||
r = new XmlTextReader (FileSystem.OpenTextFile (file));
|
||||
r.MoveToContent ();
|
||||
if (r.IsEmptyElement)
|
||||
return entries;
|
||||
r.ReadStartElement ();
|
||||
r.MoveToContent ();
|
||||
while (r.NodeType != XmlNodeType.EndElement) {
|
||||
if (r.NodeType == XmlNodeType.Element && r.LocalName == "Directory") {
|
||||
bool.TryParse(r.GetAttribute("include-subdirs"), out var subs);
|
||||
string sdom;
|
||||
string share = r.GetAttribute ("shared");
|
||||
if (share == "true")
|
||||
sdom = AddinDatabase.GlobalDomain;
|
||||
else if (share == "false")
|
||||
sdom = null;
|
||||
else
|
||||
sdom = domain; // Inherit the domain
|
||||
|
||||
string path = r.ReadElementString ().Trim ();
|
||||
if (path.Length > 0) {
|
||||
path = Util.NormalizePath (path);
|
||||
entries.Add (new AddinsEntry { Folder = path, Domain = sdom, Recursive = subs });
|
||||
}
|
||||
} else if (r.NodeType == XmlNodeType.Element && r.LocalName == "GacAssembly") {
|
||||
string aname = r.ReadElementString ().Trim ();
|
||||
if (aname.Length > 0) {
|
||||
aname = Util.NormalizePath (aname);
|
||||
aname = Util.GetGacPath (aname);
|
||||
if (aname != null) {
|
||||
// Gac assemblies always use the global domain
|
||||
entries.Add (new AddinsEntry { Folder = aname, Domain = AddinDatabase.GlobalDomain });
|
||||
}
|
||||
}
|
||||
} else if (r.NodeType == XmlNodeType.Element && r.LocalName == "Exclude") {
|
||||
string path = r.ReadElementString ().Trim ();
|
||||
if (path.Length > 0) {
|
||||
path = Util.NormalizePath (path);
|
||||
if (!Path.IsPathRooted (path))
|
||||
path = Path.Combine (basePath, path);
|
||||
ScanContext.AddPathToIgnore (Path.GetFullPath (path));
|
||||
}
|
||||
} else
|
||||
r.Skip ();
|
||||
r.MoveToContent ();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
if (monitor != null)
|
||||
monitor.ReportError ("Could not process addins file: " + file, ex);
|
||||
} finally {
|
||||
if (r != null)
|
||||
r.Close ();
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
class AddinsEntry
|
||||
{
|
||||
public string Folder;
|
||||
public string Domain;
|
||||
public bool Recursive;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,207 @@
|
|||
//
|
||||
// AddinRegistryUpdater.cs
|
||||
//
|
||||
// Author:
|
||||
// Lluis Sanchez <llsan@microsoft.com>
|
||||
//
|
||||
// Copyright (c) 2018 Microsoft
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Mono.Addins.Database
|
||||
{
|
||||
class AddinRegistryUpdater: AddinFolderVisitor
|
||||
{
|
||||
AddinDatabase database;
|
||||
AddinScanFolderInfo currentFolderInfo;
|
||||
AddinScanResult scanResult;
|
||||
|
||||
public AddinRegistryUpdater (AddinDatabase database, AddinScanResult scanResult): base (database)
|
||||
{
|
||||
this.database = database;
|
||||
this.scanResult = scanResult;
|
||||
ScanContext = scanResult.ScanContext;
|
||||
}
|
||||
|
||||
protected override void OnVisitFolder (IProgressStatus monitor, string path, string domain, bool recursive)
|
||||
{
|
||||
AddinScanFolderInfo folderInfo;
|
||||
|
||||
if (!database.GetFolderInfoForPath (monitor, path, out folderInfo)) {
|
||||
// folderInfo file was corrupt.
|
||||
// Just in case, we are going to regenerate all relation data.
|
||||
if (!FileSystem.DirectoryExists (path))
|
||||
scanResult.RegenerateRelationData = true;
|
||||
} else {
|
||||
// Directory is included but it doesn't exist. Ignore it.
|
||||
if (folderInfo == null && !FileSystem.DirectoryExists (path))
|
||||
return;
|
||||
}
|
||||
|
||||
// if domain is null it means that a new domain has to be created.
|
||||
|
||||
// Look for an add-in scan data index file. If it is present, it means the folder has been pre-scanned
|
||||
|
||||
var dirScanDataIndex = AddinScanDataIndex.LoadFromFolder (monitor, path);
|
||||
|
||||
if (dirScanDataIndex != null && scanResult.CleanGeneratedAddinScanDataFiles) {
|
||||
// Remove any existing dir.addindata if data is being generated
|
||||
dirScanDataIndex.Delete ();
|
||||
dirScanDataIndex = null;
|
||||
}
|
||||
|
||||
bool sharedFolder = domain == AddinDatabase.GlobalDomain;
|
||||
bool isNewFolder = folderInfo == null;
|
||||
bool folderHasIndex = dirScanDataIndex != null;
|
||||
|
||||
if (isNewFolder) {
|
||||
// No folder info. It is the first time this folder is scanned.
|
||||
// There is no need to store this object if the folder does not
|
||||
// contain add-ins.
|
||||
folderInfo = new AddinScanFolderInfo (path);
|
||||
folderInfo.FolderHasScanDataIndex = folderHasIndex;
|
||||
} else if (folderInfo.FolderHasScanDataIndex != folderHasIndex) {
|
||||
// A scan data index appeared or disappeared. The information in folderInfo is not reliable.
|
||||
// Update the folder info and regenerate everything.
|
||||
scanResult.RegenerateRelationData = true;
|
||||
folderInfo.Reset ();
|
||||
scanResult.RegisterModifiedFolderInfo (folderInfo);
|
||||
folderInfo.FolderHasScanDataIndex = folderHasIndex;
|
||||
}
|
||||
|
||||
if (!sharedFolder && (folderInfo.SharedFolder || folderInfo.Domain != domain)) {
|
||||
// If the folder already has a domain, reuse it
|
||||
if (domain == null && folderInfo.RootsDomain != null && folderInfo.RootsDomain != AddinDatabase.GlobalDomain)
|
||||
domain = folderInfo.RootsDomain;
|
||||
else if (domain == null) {
|
||||
folderInfo.Domain = domain = database.GetUniqueDomainId ();
|
||||
scanResult.RegenerateRelationData = true;
|
||||
} else {
|
||||
folderInfo.Domain = domain;
|
||||
if (!isNewFolder) {
|
||||
// Domain has changed. Update the folder info and regenerate everything.
|
||||
scanResult.RegenerateRelationData = true;
|
||||
scanResult.RegisterModifiedFolderInfo (folderInfo);
|
||||
}
|
||||
}
|
||||
} else if (!folderInfo.SharedFolder && sharedFolder) {
|
||||
scanResult.RegenerateRelationData = true;
|
||||
}
|
||||
|
||||
folderInfo.SharedFolder = sharedFolder;
|
||||
|
||||
// If there is no domain assigned to the host, get one now
|
||||
if (scanResult.Domain == AddinDatabase.UnknownDomain)
|
||||
scanResult.Domain = domain;
|
||||
|
||||
// Discard folders not belonging to the required domain
|
||||
if (scanResult.Domain != null && domain != scanResult.Domain && domain != AddinDatabase.GlobalDomain)
|
||||
return;
|
||||
|
||||
if (monitor.LogLevel > 1)
|
||||
monitor.Log ("Checking: " + path);
|
||||
|
||||
if (dirScanDataIndex != null) {
|
||||
// Instead of scanning the folder, just register the files in the index
|
||||
foreach (var file in dirScanDataIndex.Files)
|
||||
RegisterFileToScan (monitor, file.FileName, folderInfo, file);
|
||||
foreach (var file in dirScanDataIndex.Assemblies)
|
||||
scanResult.AssemblyIndex.AddAssemblyLocation (file);
|
||||
} else {
|
||||
currentFolderInfo = folderInfo;
|
||||
|
||||
base.OnVisitFolder (monitor, path, domain, recursive);
|
||||
|
||||
if (!FileSystem.DirectoryExists (path)) {
|
||||
// The folder has been deleted. All add-ins defined in that folder should also be deleted.
|
||||
scanResult.RegenerateRelationData = true;
|
||||
scanResult.ChangesFound = true;
|
||||
if (scanResult.CheckOnly)
|
||||
return;
|
||||
database.DeleteFolderInfo (monitor, folderInfo);
|
||||
}
|
||||
}
|
||||
|
||||
// Look for deleted add-ins.
|
||||
|
||||
UpdateDeletedAddins (monitor, folderInfo);
|
||||
}
|
||||
|
||||
protected override void OnVisitAddinManifestFile (IProgressStatus monitor, string file)
|
||||
{
|
||||
RegisterFileToScan (monitor, file, currentFolderInfo, null);
|
||||
}
|
||||
|
||||
protected override void OnVisitAssemblyFile (IProgressStatus monitor, string file)
|
||||
{
|
||||
RegisterFileToScan (monitor, file, currentFolderInfo, null);
|
||||
scanResult.AssemblyIndex.AddAssemblyLocation (file);
|
||||
}
|
||||
|
||||
public void UpdateDeletedAddins (IProgressStatus monitor, AddinScanFolderInfo folderInfo)
|
||||
{
|
||||
var missing = folderInfo.GetMissingAddins (FileSystem);
|
||||
if (missing.Count > 0) {
|
||||
if (FileSystem.DirectoryExists (folderInfo.Folder))
|
||||
scanResult.RegisterModifiedFolderInfo (folderInfo);
|
||||
scanResult.ChangesFound = true;
|
||||
if (scanResult.CheckOnly)
|
||||
return;
|
||||
|
||||
foreach (AddinFileInfo info in missing) {
|
||||
database.UninstallAddin (monitor, info.Domain, info.AddinId, info.File, scanResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RegisterFileToScan (IProgressStatus monitor, string file, AddinScanFolderInfo folderInfo, AddinScanData scanData)
|
||||
{
|
||||
AddinFileInfo finfo = folderInfo.GetAddinFileInfo (file);
|
||||
bool added = false;
|
||||
|
||||
if (finfo != null && (!finfo.IsAddin || finfo.Domain == folderInfo.GetDomain (finfo.IsRoot)) && !finfo.HasChanged (FileSystem, scanData?.MD5) && !scanResult.RegenerateAllData) {
|
||||
if (finfo.ScanError) {
|
||||
// Always schedule the file for scan if there was an error in a previous scan.
|
||||
// However, don't set ChangesFound=true, in this way if there isn't any other
|
||||
// change in the registry, the file won't be scanned again.
|
||||
scanResult.AddFileToScan (file, folderInfo, scanData);
|
||||
added = true;
|
||||
}
|
||||
|
||||
if (!finfo.IsAddin)
|
||||
return;
|
||||
|
||||
if (database.AddinDescriptionExists (finfo.Domain, finfo.AddinId)) {
|
||||
// It is an add-in and it has not changed. Paths in the ignore list
|
||||
// are still valid, so they can be used.
|
||||
if (finfo.IgnorePaths != null)
|
||||
scanResult.ScanContext.AddPathsToIgnore (finfo.IgnorePaths);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
scanResult.ChangesFound = true;
|
||||
|
||||
if (!scanResult.CheckOnly && !added)
|
||||
scanResult.AddFileToScan (file, folderInfo, scanData);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,162 @@
|
|||
//
|
||||
// AddinScanData.cs
|
||||
//
|
||||
// Author:
|
||||
// Lluis Sanchez <llsan@microsoft.com>
|
||||
//
|
||||
// Copyright (c) 2018 Microsoft
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Mono.Addins.Serialization;
|
||||
|
||||
namespace Mono.Addins.Database
|
||||
{
|
||||
class AddinScanDataIndex: IBinaryXmlElement
|
||||
{
|
||||
static BinaryXmlTypeMap typeMap = new BinaryXmlTypeMap (typeof (AddinScanDataIndex), typeof(AddinScanData));
|
||||
|
||||
List<AddinScanData> files = new List<AddinScanData> ();
|
||||
List<string> assemblies = new List<string> ();
|
||||
|
||||
string file;
|
||||
|
||||
public static AddinScanDataIndex LoadFromFolder (IProgressStatus monitor, string path)
|
||||
{
|
||||
var file = Path.Combine (path, "dir.addindata");
|
||||
if (File.Exists (file)) {
|
||||
try {
|
||||
using (Stream s = File.OpenRead (file)) {
|
||||
BinaryXmlReader reader = new BinaryXmlReader (s, typeMap);
|
||||
reader.ContextData = file;
|
||||
return (AddinScanDataIndex)reader.ReadValue ("data");
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
if (monitor != null)
|
||||
monitor.ReportError ("Could not load dir.addindata file", ex);
|
||||
// The addindata file is corrupted or changed format.
|
||||
// It is not useful anymore, so remove it
|
||||
try {
|
||||
File.Delete (file);
|
||||
} catch {
|
||||
// Ignore error deleting. Maybe there is a permission issue.
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void SaveToFolder (string path)
|
||||
{
|
||||
file = Path.Combine (path, "dir.addindata");
|
||||
using (Stream s = File.OpenWrite (file)) {
|
||||
var writter = new BinaryXmlWriter (s, typeMap);
|
||||
writter.WriteValue ("data", this);
|
||||
}
|
||||
}
|
||||
|
||||
public void Delete ()
|
||||
{
|
||||
if (File.Exists (file))
|
||||
File.Delete (file);
|
||||
}
|
||||
|
||||
void IBinaryXmlElement.Read (BinaryXmlReader reader)
|
||||
{
|
||||
file = (string) reader.ContextData;
|
||||
|
||||
reader.ReadValue ("files", files);
|
||||
|
||||
// Generate absolute paths
|
||||
|
||||
var basePath = Path.GetDirectoryName (file);
|
||||
foreach (var f in files)
|
||||
f.FileName = Path.GetFullPath (Path.Combine (basePath, f.RelativeFileName));
|
||||
|
||||
var asms = (string[])reader.ReadValue ("assemblies");
|
||||
|
||||
// Generate absolute paths
|
||||
|
||||
for (int n = 0; n < asms.Length; n++)
|
||||
asms [n] = Path.GetFullPath (Path.Combine (basePath, asms [n]));
|
||||
|
||||
assemblies = new List<string> (asms);
|
||||
}
|
||||
|
||||
void IBinaryXmlElement.Write (BinaryXmlWriter writer)
|
||||
{
|
||||
var basePath = Path.GetDirectoryName (file);
|
||||
|
||||
// Store files as relative paths
|
||||
|
||||
foreach (var f in files)
|
||||
f.RelativeFileName = Util.AbsoluteToRelativePath (basePath, f.FileName);
|
||||
|
||||
writer.WriteValue ("files", files);
|
||||
|
||||
// Store assemblies as relative paths
|
||||
|
||||
var array = new string [assemblies.Count];
|
||||
for (int n = 0; n < assemblies.Count; n++)
|
||||
array [n] = Util.AbsoluteToRelativePath (basePath, assemblies [n]);
|
||||
|
||||
writer.WriteValue ("assemblies", array);
|
||||
}
|
||||
|
||||
public List<AddinScanData> Files {
|
||||
get { return files; }
|
||||
}
|
||||
|
||||
public List<string> Assemblies {
|
||||
get { return assemblies; }
|
||||
}
|
||||
}
|
||||
|
||||
class AddinScanData: IBinaryXmlElement
|
||||
{
|
||||
public string RelativeFileName { get; set; }
|
||||
public string FileName { get; set; }
|
||||
public string MD5 { get; set; }
|
||||
|
||||
public AddinScanData ()
|
||||
{
|
||||
}
|
||||
|
||||
public AddinScanData (string file, string md5)
|
||||
{
|
||||
FileName = file;
|
||||
MD5 = md5;
|
||||
}
|
||||
|
||||
void IBinaryXmlElement.Read (BinaryXmlReader reader)
|
||||
{
|
||||
RelativeFileName = reader.ReadStringValue ("FileName");
|
||||
MD5 = reader.ReadStringValue ("MD5");
|
||||
}
|
||||
|
||||
void IBinaryXmlElement.Write (BinaryXmlWriter writer)
|
||||
{
|
||||
writer.WriteValue ("FileName", RelativeFileName);
|
||||
writer.WriteValue ("MD5", MD5);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
//
|
||||
// AddinScanDataFileGenerator.cs
|
||||
//
|
||||
// Author:
|
||||
// Lluis Sanchez <llsan@microsoft.com>
|
||||
//
|
||||
// Copyright (c) 2018 Microsoft
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Mono.Addins.Description;
|
||||
|
||||
namespace Mono.Addins.Database
|
||||
{
|
||||
class AddinScanDataFileGenerator: AddinFolderVisitor, IDisposable
|
||||
{
|
||||
string rootFolder;
|
||||
AddinScanner scanner;
|
||||
AddinDatabase database;
|
||||
AddinScanDataIndex scanDataIndex;
|
||||
AssemblyLocator locator;
|
||||
AssemblyIndex assemblyIndex;
|
||||
|
||||
List<string> foundFiles = new List<string> ();
|
||||
List<string> foundAssemblies = new List<string> ();
|
||||
|
||||
public AddinScanDataFileGenerator (AddinDatabase database, AddinRegistry registry, string rootFolder): base (database)
|
||||
{
|
||||
this.database = database;
|
||||
this.rootFolder = Path.GetFullPath (rootFolder);
|
||||
|
||||
assemblyIndex = new AssemblyIndex ();
|
||||
locator = new AssemblyLocator (database, registry, assemblyIndex);
|
||||
scanner = new AddinScanner (database, locator);
|
||||
}
|
||||
|
||||
protected override void OnVisitFolder (IProgressStatus monitor, string path, string domain, bool recursive)
|
||||
{
|
||||
if (path == rootFolder) {
|
||||
|
||||
// Create an index
|
||||
scanDataIndex = new AddinScanDataIndex ();
|
||||
|
||||
base.OnVisitFolder (monitor, path, domain, recursive);
|
||||
|
||||
// Scan the files after visiting the folder tree. At this point the assembly index will be complete
|
||||
// and will be able to resolve assemblies during the add-in scan
|
||||
|
||||
foreach (var file in foundFiles) {
|
||||
if (scanner.ScanConfigAssemblies (monitor, file, ScanContext, out var config) && config != null)
|
||||
StoreScanDataFile (monitor, file, config);
|
||||
}
|
||||
foreach (var file in foundAssemblies) {
|
||||
if (scanner.ScanAssembly (monitor, file, ScanContext, out var config) && config != null)
|
||||
StoreScanDataFile (monitor, file, config);
|
||||
|
||||
// The index contains a list of all assemblies, no matter if they are add-ins or not
|
||||
scanDataIndex.Assemblies.Add (file);
|
||||
}
|
||||
|
||||
foundFiles.Clear ();
|
||||
foundAssemblies.Clear ();
|
||||
|
||||
scanDataIndex.SaveToFolder (path);
|
||||
scanDataIndex = null;
|
||||
} else
|
||||
base.OnVisitFolder (monitor, path, domain, recursive);
|
||||
}
|
||||
|
||||
protected override void OnVisitAddinManifestFile (IProgressStatus monitor, string file)
|
||||
{
|
||||
if (scanDataIndex != null)
|
||||
foundFiles.Add (file);
|
||||
}
|
||||
|
||||
protected override void OnVisitAssemblyFile (IProgressStatus monitor, string file)
|
||||
{
|
||||
if (!Util.IsManagedAssembly (file))
|
||||
return;
|
||||
|
||||
assemblyIndex.AddAssemblyLocation (file);
|
||||
|
||||
if (scanDataIndex != null)
|
||||
foundAssemblies.Add (file);
|
||||
}
|
||||
|
||||
void StoreScanDataFile (IProgressStatus monitor, string file, AddinDescription config)
|
||||
{
|
||||
// Save a binary data file next to the scanned file
|
||||
var scanDataFile = file + ".addindata";
|
||||
database.SaveDescription (monitor, config, scanDataFile);
|
||||
var md5 = Util.GetMD5 (scanDataFile);
|
||||
scanDataIndex.Files.Add (new AddinScanData (file, md5));
|
||||
}
|
||||
|
||||
public void Dispose ()
|
||||
{
|
||||
scanner.Dispose ();
|
||||
}
|
||||
|
||||
class AssemblyLocator : IAssemblyLocator
|
||||
{
|
||||
// This is a custom assembly locator that will look first at the
|
||||
// assembly index being generated during the add-in lookup, and
|
||||
// will use a global assembly locator as fallback.
|
||||
|
||||
AssemblyLocatorVisitor globalLocator;
|
||||
AssemblyIndex index;
|
||||
|
||||
public AssemblyLocator (AddinDatabase database, AddinRegistry registry, AssemblyIndex index)
|
||||
{
|
||||
this.index = index;
|
||||
globalLocator = new AssemblyLocatorVisitor (database, registry, false);
|
||||
}
|
||||
|
||||
public string GetAssemblyLocation (string fullName)
|
||||
{
|
||||
var res = index.GetAssemblyLocation (fullName);
|
||||
if (res != null)
|
||||
return res;
|
||||
|
||||
// Fallback to a global visitor
|
||||
|
||||
return globalLocator.GetAssemblyLocation (fullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -32,6 +32,7 @@ using System.IO;
|
|||
using System.Collections;
|
||||
using System.Collections.Specialized;
|
||||
using Mono.Addins.Serialization;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Mono.Addins.Database
|
||||
{
|
||||
|
@ -144,7 +145,14 @@ namespace Mono.Addins.Database
|
|||
sharedFolder = value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public bool FolderHasScanDataIndex { get; set; }
|
||||
|
||||
public void Reset ()
|
||||
{
|
||||
files.Clear ();
|
||||
}
|
||||
|
||||
public DateTime GetLastScanTime (string file)
|
||||
{
|
||||
AddinFileInfo info = (AddinFileInfo) files [file];
|
||||
|
@ -159,7 +167,7 @@ namespace Mono.Addins.Database
|
|||
return (AddinFileInfo) files [file];
|
||||
}
|
||||
|
||||
public AddinFileInfo SetLastScanTime (string file, string addinId, bool isRoot, DateTime time, bool scanError)
|
||||
public AddinFileInfo SetLastScanTime (string file, string addinId, bool isRoot, DateTime time, bool scanError, string scanDataMD5 = null)
|
||||
{
|
||||
AddinFileInfo info = (AddinFileInfo) files [file];
|
||||
if (info == null) {
|
||||
|
@ -171,16 +179,17 @@ namespace Mono.Addins.Database
|
|||
info.AddinId = addinId;
|
||||
info.IsRoot = isRoot;
|
||||
info.ScanError = scanError;
|
||||
info.ScanDataMD5 = scanDataMD5;
|
||||
if (addinId != null)
|
||||
info.Domain = GetDomain (isRoot);
|
||||
else
|
||||
info.Domain = null;
|
||||
return info;
|
||||
}
|
||||
|
||||
public ArrayList GetMissingAddins (AddinFileSystemExtension fs)
|
||||
|
||||
public List<AddinFileInfo> GetMissingAddins (AddinFileSystemExtension fs)
|
||||
{
|
||||
ArrayList missing = new ArrayList ();
|
||||
var missing = new List<AddinFileInfo> ();
|
||||
|
||||
if (!fs.DirectoryExists (folder)) {
|
||||
// All deleted
|
||||
|
@ -218,6 +227,7 @@ namespace Mono.Addins.Database
|
|||
writer.WriteValue ("files", files);
|
||||
writer.WriteValue ("domain", domain);
|
||||
writer.WriteValue ("sharedFolder", sharedFolder);
|
||||
writer.WriteValue ("folderHasDataIndex", FolderHasScanDataIndex);
|
||||
}
|
||||
|
||||
void IBinaryXmlElement.Read (BinaryXmlReader reader)
|
||||
|
@ -226,6 +236,7 @@ namespace Mono.Addins.Database
|
|||
reader.ReadValue ("files", files);
|
||||
domain = reader.ReadStringValue ("domain");
|
||||
sharedFolder = reader.ReadBooleanValue ("sharedFolder");
|
||||
FolderHasScanDataIndex = reader.ReadBooleanValue ("folderHasDataIndex");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -239,6 +250,7 @@ namespace Mono.Addins.Database
|
|||
public bool ScanError;
|
||||
public string Domain;
|
||||
public StringCollection IgnorePaths;
|
||||
public string ScanDataMD5;
|
||||
|
||||
public bool IsAddin {
|
||||
get { return AddinId != null && AddinId.Length != 0; }
|
||||
|
@ -250,6 +262,13 @@ namespace Mono.Addins.Database
|
|||
IgnorePaths = new StringCollection ();
|
||||
IgnorePaths.Add (path);
|
||||
}
|
||||
|
||||
public bool HasChanged (AddinFileSystemExtension fs, string md5)
|
||||
{
|
||||
if (md5 != null && ScanDataMD5 != null)
|
||||
return md5 != ScanDataMD5;
|
||||
return fs.GetLastWriteTime (File) != LastScan;
|
||||
}
|
||||
|
||||
void IBinaryXmlElement.Write (BinaryXmlWriter writer)
|
||||
{
|
||||
|
@ -259,8 +278,8 @@ namespace Mono.Addins.Database
|
|||
writer.WriteValue ("IsRoot", IsRoot);
|
||||
writer.WriteValue ("ScanError", ScanError);
|
||||
writer.WriteValue ("Domain", Domain);
|
||||
if (IgnorePaths != null && IgnorePaths.Count > 0)
|
||||
writer.WriteValue ("IgnorePaths", IgnorePaths);
|
||||
writer.WriteValue ("IgnorePaths", IgnorePaths);
|
||||
writer.WriteValue ("MD5", ScanDataMD5);
|
||||
}
|
||||
|
||||
void IBinaryXmlElement.Read (BinaryXmlReader reader)
|
||||
|
@ -272,6 +291,7 @@ namespace Mono.Addins.Database
|
|||
ScanError = reader.ReadBooleanValue ("ScanError");
|
||||
Domain = reader.ReadStringValue ("Domain");
|
||||
IgnorePaths = (StringCollection) reader.ReadValue ("IgnorePaths", new StringCollection ());
|
||||
ScanDataMD5 = reader.ReadStringValue ("MD5");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,25 +32,20 @@ using System.Collections;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Linq;
|
||||
|
||||
namespace Mono.Addins.Database
|
||||
{
|
||||
internal class AddinScanResult: MarshalByRefObject, IAssemblyLocator
|
||||
internal class AddinScanResult: MarshalByRefObject
|
||||
{
|
||||
internal ArrayList AddinsToScan = new ArrayList ();
|
||||
internal List<string> AddinsToUpdateRelations = new List<string> ();
|
||||
internal List<string> AddinsToUpdate = new List<string> ();
|
||||
internal ArrayList FilesToScan = new ArrayList ();
|
||||
internal ArrayList ModifiedFolderInfos = new ArrayList ();
|
||||
internal ArrayList FilesWithScanFailure = new ArrayList ();
|
||||
internal AddinHostIndex HostIndex;
|
||||
internal List<string> RemovedAddins = new List<string> ();
|
||||
Hashtable visitedFolders = new Hashtable ();
|
||||
|
||||
Hashtable assemblyLocations = new Hashtable ();
|
||||
Hashtable assemblyLocationsByFullName = new Hashtable ();
|
||||
Hashtable filesToIgnore;
|
||||
|
||||
|
||||
bool regenerateRelationData;
|
||||
bool changesFound;
|
||||
|
||||
|
@ -58,7 +53,13 @@ namespace Mono.Addins.Database
|
|||
public bool CheckOnly;
|
||||
public bool LocateAssembliesOnly;
|
||||
public string Domain;
|
||||
|
||||
|
||||
public ScanContext ScanContext { get; } = new ScanContext ();
|
||||
|
||||
public AssemblyIndex AssemblyIndex { get; } = new AssemblyIndex ();
|
||||
|
||||
public bool CleanGeneratedAddinScanDataFiles { get; set; }
|
||||
|
||||
public bool ChangesFound {
|
||||
get { return changesFound; }
|
||||
set { changesFound = value; }
|
||||
|
@ -73,42 +74,6 @@ namespace Mono.Addins.Database
|
|||
}
|
||||
}
|
||||
|
||||
public bool VisitFolder (string folder)
|
||||
{
|
||||
if (visitedFolders.Contains (folder) || IgnorePath (folder))
|
||||
return false;
|
||||
else {
|
||||
visitedFolders.Add (folder, folder);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IgnorePath (string file)
|
||||
{
|
||||
if (filesToIgnore == null)
|
||||
return false;
|
||||
string root = Path.GetPathRoot (file);
|
||||
while (root != file) {
|
||||
if (filesToIgnore.Contains (file))
|
||||
return true;
|
||||
file = Path.GetDirectoryName (file);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void AddPathToIgnore (string path)
|
||||
{
|
||||
if (filesToIgnore == null)
|
||||
filesToIgnore = new Hashtable ();
|
||||
filesToIgnore [path] = path;
|
||||
}
|
||||
|
||||
public void AddPathsToIgnore (IEnumerable paths)
|
||||
{
|
||||
foreach (string p in paths)
|
||||
AddPathToIgnore (p);
|
||||
}
|
||||
|
||||
public void AddAddinToScan (string addinId)
|
||||
{
|
||||
if (!AddinsToScan.Contains (addinId))
|
||||
|
@ -121,17 +86,12 @@ namespace Mono.Addins.Database
|
|||
RemovedAddins.Add (addinId);
|
||||
}
|
||||
|
||||
public void AddFileToWithFailure (string file)
|
||||
{
|
||||
if (!FilesWithScanFailure.Contains (file))
|
||||
FilesWithScanFailure.Add (file);
|
||||
}
|
||||
|
||||
public void AddFileToScan (string file, AddinScanFolderInfo folderInfo)
|
||||
public void AddFileToScan (string file, AddinScanFolderInfo folderInfo, AddinScanData scanData)
|
||||
{
|
||||
FileToScan di = new FileToScan ();
|
||||
di.File = file;
|
||||
di.AddinScanFolderInfo = folderInfo;
|
||||
di.ScanDataMD5 = scanData?.MD5;
|
||||
FilesToScan.Add (di);
|
||||
RegisterModifiedFolderInfo (folderInfo);
|
||||
}
|
||||
|
@ -153,58 +113,43 @@ namespace Mono.Addins.Database
|
|||
if (!AddinsToUpdate.Contains (addinId))
|
||||
AddinsToUpdate.Add (addinId);
|
||||
}
|
||||
|
||||
public void AddAssemblyLocation (string file)
|
||||
{
|
||||
string name = Path.GetFileNameWithoutExtension (file);
|
||||
ArrayList list = assemblyLocations [name] as ArrayList;
|
||||
if (list == null) {
|
||||
list = new ArrayList ();
|
||||
assemblyLocations [name] = list;
|
||||
}
|
||||
list.Add (file);
|
||||
}
|
||||
|
||||
public string GetAssemblyLocation (string fullName)
|
||||
{
|
||||
string loc = assemblyLocationsByFullName [fullName] as String;
|
||||
if (loc != null)
|
||||
return loc;
|
||||
|
||||
int i = fullName.IndexOf (',');
|
||||
string name = fullName.Substring (0,i);
|
||||
if (name == "Mono.Addins")
|
||||
return GetType ().Assembly.Location;
|
||||
ArrayList list = assemblyLocations [name] as ArrayList;
|
||||
if (list == null)
|
||||
return null;
|
||||
|
||||
string lastAsm = null;
|
||||
foreach (string file in list.ToArray ()) {
|
||||
try {
|
||||
list.Remove (file);
|
||||
AssemblyName aname = AssemblyName.GetAssemblyName (file);
|
||||
lastAsm = file;
|
||||
assemblyLocationsByFullName [aname.FullName] = file;
|
||||
if (aname.FullName == fullName)
|
||||
return file;
|
||||
} catch {
|
||||
// Could not get the assembly name. The file either doesn't exist or it is not a valid assembly.
|
||||
// In this case, just ignore it.
|
||||
}
|
||||
}
|
||||
|
||||
if (lastAsm != null) {
|
||||
// If an exact version is not found, just take any of them
|
||||
return lastAsm;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class FileToScan
|
||||
{
|
||||
public string File;
|
||||
public AddinScanFolderInfo AddinScanFolderInfo;
|
||||
public string ScanDataMD5;
|
||||
}
|
||||
|
||||
class ScanContext
|
||||
{
|
||||
HashSet<string> filesToIgnore;
|
||||
|
||||
public void AddPathToIgnore (string path)
|
||||
{
|
||||
if (filesToIgnore == null)
|
||||
filesToIgnore = new HashSet<string> ();
|
||||
filesToIgnore.Add (path);
|
||||
}
|
||||
|
||||
public bool IgnorePath (string file)
|
||||
{
|
||||
if (filesToIgnore == null)
|
||||
return false;
|
||||
string root = Path.GetPathRoot (file);
|
||||
while (root != file) {
|
||||
if (filesToIgnore.Contains (file))
|
||||
return true;
|
||||
file = Path.GetDirectoryName (file);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void AddPathsToIgnore (IEnumerable paths)
|
||||
{
|
||||
foreach (string p in paths)
|
||||
AddPathToIgnore (p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
//
|
||||
//
|
||||
// AddinScanner.cs
|
||||
//
|
||||
// Author:
|
||||
|
@ -34,228 +34,135 @@ using System.IO;
|
|||
using System.Text;
|
||||
using System.Reflection;
|
||||
using System.Collections.Specialized;
|
||||
using System.Xml;
|
||||
using System.ComponentModel;
|
||||
|
||||
using Mono.Addins.Description;
|
||||
|
||||
namespace Mono.Addins.Database
|
||||
{
|
||||
class AddinScanner: MarshalByRefObject
|
||||
class AddinScanner: IDisposable
|
||||
{
|
||||
AddinDatabase database;
|
||||
AddinFileSystemExtension fs;
|
||||
Dictionary<IAssemblyReflector,object> coreAssemblies = new Dictionary<IAssemblyReflector, object> ();
|
||||
|
||||
public AddinScanner (AddinDatabase database, AddinScanResult scanResult, IProgressStatus monitor)
|
||||
IAssemblyLocator assemblyLocator;
|
||||
|
||||
public AddinScanner (AddinDatabase database, IAssemblyLocator locator)
|
||||
{
|
||||
this.database = database;
|
||||
fs = database.FileSystem;
|
||||
assemblyLocator = locator;
|
||||
SetupAssemblyResolver ();
|
||||
}
|
||||
|
||||
public void ScanFolder (IProgressStatus monitor, string path, string domain, AddinScanResult scanResult)
|
||||
{
|
||||
path = Path.GetFullPath (path);
|
||||
|
||||
// Avoid folders including each other
|
||||
if (!scanResult.VisitFolder (path))
|
||||
return;
|
||||
|
||||
AddinScanFolderInfo folderInfo;
|
||||
if (!database.GetFolderInfoForPath (monitor, path, out folderInfo)) {
|
||||
// folderInfo file was corrupt.
|
||||
// Just in case, we are going to regenerate all relation data.
|
||||
if (!fs.DirectoryExists (path))
|
||||
scanResult.RegenerateRelationData = true;
|
||||
} else {
|
||||
// Directory is included but it doesn't exist. Ignore it.
|
||||
if (folderInfo == null && !fs.DirectoryExists (path))
|
||||
return;
|
||||
}
|
||||
|
||||
// if domain is null it means that a new domain has to be created.
|
||||
|
||||
bool sharedFolder = domain == AddinDatabase.GlobalDomain;
|
||||
bool isNewFolder = folderInfo == null;
|
||||
|
||||
if (isNewFolder) {
|
||||
// No folder info. It is the first time this folder is scanned.
|
||||
// There is no need to store this object if the folder does not
|
||||
// contain add-ins.
|
||||
folderInfo = new AddinScanFolderInfo (path);
|
||||
}
|
||||
|
||||
if (!sharedFolder && (folderInfo.SharedFolder || folderInfo.Domain != domain)) {
|
||||
// If the folder already has a domain, reuse it
|
||||
if (domain == null && folderInfo.RootsDomain != null && folderInfo.RootsDomain != AddinDatabase.GlobalDomain)
|
||||
domain = folderInfo.RootsDomain;
|
||||
else if (domain == null) {
|
||||
folderInfo.Domain = domain = database.GetUniqueDomainId ();
|
||||
scanResult.RegenerateRelationData = true;
|
||||
}
|
||||
else {
|
||||
folderInfo.Domain = domain;
|
||||
if (!isNewFolder) {
|
||||
// Domain has changed. Update the folder info and regenerate everything.
|
||||
scanResult.RegenerateRelationData = true;
|
||||
scanResult.RegisterModifiedFolderInfo (folderInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!folderInfo.SharedFolder && sharedFolder) {
|
||||
scanResult.RegenerateRelationData = true;
|
||||
}
|
||||
|
||||
folderInfo.SharedFolder = sharedFolder;
|
||||
|
||||
// If there is no domain assigned to the host, get one now
|
||||
if (scanResult.Domain == AddinDatabase.UnknownDomain)
|
||||
scanResult.Domain = domain;
|
||||
|
||||
// Discard folders not belonging to the required domain
|
||||
if (scanResult.Domain != null && domain != scanResult.Domain && domain != AddinDatabase.GlobalDomain) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (monitor.LogLevel > 1 && !scanResult.LocateAssembliesOnly)
|
||||
monitor.Log ("Checking: " + path);
|
||||
|
||||
if (fs.DirectoryExists (path))
|
||||
{
|
||||
IEnumerable<string> files = fs.GetFiles (path);
|
||||
|
||||
// First of all, look for .addin files. Addin files must be processed before
|
||||
// assemblies, because they may add files to the ignore list (i.e., assemblies
|
||||
// included in .addin files won't be scanned twice).
|
||||
foreach (string file in files) {
|
||||
if (file.EndsWith (".addin.xml") || file.EndsWith (".addin")) {
|
||||
RegisterFileToScan (monitor, file, scanResult, folderInfo);
|
||||
}
|
||||
}
|
||||
|
||||
// Now scan assemblies. They can also add files to the ignore list.
|
||||
|
||||
foreach (string file in files) {
|
||||
string ext = Path.GetExtension (file).ToLower ();
|
||||
if (ext == ".dll" || ext == ".exe") {
|
||||
RegisterFileToScan (monitor, file, scanResult, folderInfo);
|
||||
scanResult.AddAssemblyLocation (file);
|
||||
}
|
||||
}
|
||||
|
||||
// Finally scan .addins files
|
||||
|
||||
foreach (string file in files) {
|
||||
if (Path.GetExtension (file).EndsWith (".addins")) {
|
||||
ScanAddinsFile (monitor, file, domain, scanResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (!scanResult.LocateAssembliesOnly) {
|
||||
// The folder has been deleted. All add-ins defined in that folder should also be deleted.
|
||||
scanResult.RegenerateRelationData = true;
|
||||
scanResult.ChangesFound = true;
|
||||
if (scanResult.CheckOnly)
|
||||
return;
|
||||
database.DeleteFolderInfo (monitor, folderInfo);
|
||||
}
|
||||
|
||||
if (scanResult.LocateAssembliesOnly)
|
||||
return;
|
||||
|
||||
// Look for deleted add-ins.
|
||||
|
||||
UpdateDeletedAddins (monitor, folderInfo, scanResult);
|
||||
}
|
||||
|
||||
public void UpdateDeletedAddins (IProgressStatus monitor, AddinScanFolderInfo folderInfo, AddinScanResult scanResult)
|
||||
{
|
||||
ArrayList missing = folderInfo.GetMissingAddins (fs);
|
||||
if (missing.Count > 0) {
|
||||
if (fs.DirectoryExists (folderInfo.Folder))
|
||||
scanResult.RegisterModifiedFolderInfo (folderInfo);
|
||||
scanResult.ChangesFound = true;
|
||||
if (scanResult.CheckOnly)
|
||||
return;
|
||||
|
||||
foreach (AddinFileInfo info in missing) {
|
||||
database.UninstallAddin (monitor, info.Domain, info.AddinId, info.File, scanResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RegisterFileToScan (IProgressStatus monitor, string file, AddinScanResult scanResult, AddinScanFolderInfo folderInfo)
|
||||
{
|
||||
if (scanResult.LocateAssembliesOnly)
|
||||
return;
|
||||
|
||||
AddinFileInfo finfo = folderInfo.GetAddinFileInfo (file);
|
||||
bool added = false;
|
||||
|
||||
if (finfo != null && (!finfo.IsAddin || finfo.Domain == folderInfo.GetDomain (finfo.IsRoot)) && fs.GetLastWriteTime (file) == finfo.LastScan && !scanResult.RegenerateAllData) {
|
||||
if (finfo.ScanError) {
|
||||
// Always schedule the file for scan if there was an error in a previous scan.
|
||||
// However, don't set ChangesFound=true, in this way if there isn't any other
|
||||
// change in the registry, the file won't be scanned again.
|
||||
scanResult.AddFileToScan (file, folderInfo);
|
||||
added = true;
|
||||
}
|
||||
|
||||
if (!finfo.IsAddin)
|
||||
return;
|
||||
|
||||
if (database.AddinDescriptionExists (finfo.Domain, finfo.AddinId)) {
|
||||
// It is an add-in and it has not changed. Paths in the ignore list
|
||||
// are still valid, so they can be used.
|
||||
if (finfo.IgnorePaths != null)
|
||||
scanResult.AddPathsToIgnore (finfo.IgnorePaths);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
scanResult.ChangesFound = true;
|
||||
|
||||
if (!scanResult.CheckOnly && !added)
|
||||
scanResult.AddFileToScan (file, folderInfo);
|
||||
}
|
||||
|
||||
public void ScanFile (IProgressStatus monitor, string file, AddinScanFolderInfo folderInfo, AddinScanResult scanResult)
|
||||
public void Dispose ()
|
||||
{
|
||||
if (scanResult.IgnorePath (file)) {
|
||||
TearDownAssemblyResolver ();
|
||||
database.FileSystem.CleanupReflector ();
|
||||
}
|
||||
|
||||
void SetupAssemblyResolver ()
|
||||
{
|
||||
ResolveEventHandler resolver = new ResolveEventHandler (OnResolveAddinAssembly);
|
||||
AppDomain.CurrentDomain.AssemblyResolve += OnResolveAddinAssembly;
|
||||
|
||||
EventInfo einfo = typeof (AppDomain).GetEvent ("ReflectionOnlyAssemblyResolve");
|
||||
if (einfo != null)
|
||||
einfo.AddEventHandler (AppDomain.CurrentDomain, resolver);
|
||||
}
|
||||
|
||||
void TearDownAssemblyResolver ()
|
||||
{
|
||||
ResolveEventHandler resolver = new ResolveEventHandler (OnResolveAddinAssembly);
|
||||
AppDomain.CurrentDomain.AssemblyResolve -= resolver;
|
||||
|
||||
EventInfo einfo = typeof (AppDomain).GetEvent ("ReflectionOnlyAssemblyResolve");
|
||||
if (einfo != null)
|
||||
einfo.RemoveEventHandler (AppDomain.CurrentDomain, resolver);
|
||||
assemblyLocator = null;
|
||||
}
|
||||
|
||||
Assembly OnResolveAddinAssembly (object s, ResolveEventArgs args)
|
||||
{
|
||||
string file = assemblyLocator != null ? assemblyLocator.GetAssemblyLocation (args.Name) : null;
|
||||
if (file != null)
|
||||
return Util.LoadAssemblyForReflection (file);
|
||||
else {
|
||||
if (!args.Name.StartsWith ("Mono.Addins.CecilReflector", StringComparison.Ordinal))
|
||||
Console.WriteLine ("Assembly not found: " + args.Name);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void ScanFile (IProgressStatus monitor, FileToScan fileToScan, AddinScanResult scanResult, bool cleanPreScanFile)
|
||||
{
|
||||
var file = fileToScan.File;
|
||||
var folderInfo = fileToScan.AddinScanFolderInfo;
|
||||
|
||||
if (scanResult.ScanContext.IgnorePath (file)) {
|
||||
// The file must be ignored. Maybe it caused a crash in a previous scan, or it
|
||||
// might be included by a .addin file (in which case it will be scanned when processing
|
||||
// the .addin file).
|
||||
folderInfo.SetLastScanTime (file, null, false, fs.GetLastWriteTime (file), true);
|
||||
folderInfo.SetLastScanTime (file, null, false, database.FileSystem.GetLastWriteTime (file), true);
|
||||
return;
|
||||
}
|
||||
|
||||
string ext = Path.GetExtension (file).ToLower ();
|
||||
if ((ext == ".dll" || ext == ".exe") && !Util.IsManagedAssembly (file)) {
|
||||
// Ignore dlls and exes which are not managed assemblies
|
||||
folderInfo.SetLastScanTime (file, null, false, fs.GetLastWriteTime (file), true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (monitor.LogLevel > 1)
|
||||
monitor.Log ("Scanning file: " + file);
|
||||
|
||||
// Log the file to be scanned, so in case of a process crash the main process
|
||||
// will know what crashed
|
||||
monitor.Log ("plog:scan:" + file);
|
||||
|
||||
string scannedAddinId = null;
|
||||
bool scannedIsRoot = false;
|
||||
bool scanSuccessful = false;
|
||||
bool loadedFromScanDataFile = false;
|
||||
AddinDescription config = null;
|
||||
|
||||
string ext = Path.GetExtension (file).ToLower ();
|
||||
bool isAssembly = ext == ".dll" || ext == ".exe";
|
||||
|
||||
string addinScanDataFileMD5 = null;
|
||||
var scanDataFile = file + ".addindata";
|
||||
|
||||
if (database.FileSystem.FileExists (scanDataFile)) {
|
||||
if (cleanPreScanFile)
|
||||
database.FileSystem.DeleteFile (scanDataFile);
|
||||
else {
|
||||
if (database.ReadAddinDescription (monitor, scanDataFile, out config)) {
|
||||
config.SetBasePath (Path.GetDirectoryName (file));
|
||||
config.AddinFile = file;
|
||||
scanSuccessful = true;
|
||||
loadedFromScanDataFile = true;
|
||||
addinScanDataFileMD5 = fileToScan.ScanDataMD5; // The md5 for this scan data file should be in the index
|
||||
if (monitor.LogLevel > 1)
|
||||
monitor.Log ("Loading add-in scan data file: " + file);
|
||||
} else if (monitor.LogLevel > 1)
|
||||
monitor.Log ("Add-in scan data file could not be loaded, ignoring: " + file);
|
||||
}
|
||||
}
|
||||
if (!loadedFromScanDataFile) {
|
||||
if (isAssembly && !Util.IsManagedAssembly (file)) {
|
||||
// Ignore dlls and exes which are not managed assemblies
|
||||
folderInfo.SetLastScanTime (file, null, false, database.FileSystem.GetLastWriteTime (file), true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (monitor.LogLevel > 1)
|
||||
monitor.Log ("Scanning file: " + file);
|
||||
}
|
||||
|
||||
// Log the file to be scanned, so in case of a process crash the main process
|
||||
// will know what crashed
|
||||
monitor.Log ("plog:scan:" + file);
|
||||
|
||||
try {
|
||||
if (ext == ".dll" || ext == ".exe")
|
||||
scanSuccessful = ScanAssembly (monitor, file, scanResult, out config);
|
||||
else
|
||||
scanSuccessful = ScanConfigAssemblies (monitor, file, scanResult, out config);
|
||||
if (!loadedFromScanDataFile) {
|
||||
if (isAssembly)
|
||||
scanSuccessful = ScanAssembly (monitor, file, scanResult.ScanContext, out config);
|
||||
else
|
||||
scanSuccessful = ScanConfigAssemblies (monitor, file, scanResult.ScanContext, out config);
|
||||
}
|
||||
|
||||
if (config != null) {
|
||||
|
||||
if (scanSuccessful) {
|
||||
// Clean host data from the index. New data will be added.
|
||||
if (scanResult.HostIndex != null)
|
||||
scanResult.HostIndex.RemoveHostData (config.AddinId, config.AddinFile);
|
||||
}
|
||||
|
||||
AddinFileInfo fi = folderInfo.GetAddinFileInfo (file);
|
||||
|
||||
// If version is not specified, make up one
|
||||
|
@ -270,7 +177,7 @@ namespace Mono.Addins.Database
|
|||
}
|
||||
|
||||
// Check errors in the description
|
||||
StringCollection errors = config.Verify (fs);
|
||||
StringCollection errors = config.Verify (database.FileSystem);
|
||||
|
||||
if (database.IsGlobalRegistry && config.AddinId.IndexOf ('.') == -1) {
|
||||
errors.Add ("Add-ins registered in the global registry must have a namespace.");
|
||||
|
@ -357,7 +264,7 @@ namespace Mono.Addins.Database
|
|||
monitor.ReportError ("Unexpected error while scanning file: " + file, ex);
|
||||
}
|
||||
finally {
|
||||
AddinFileInfo ainfo = folderInfo.SetLastScanTime (file, scannedAddinId, scannedIsRoot, fs.GetLastWriteTime (file), !scanSuccessful);
|
||||
AddinFileInfo ainfo = folderInfo.SetLastScanTime (file, scannedAddinId, scannedIsRoot, database.FileSystem.GetLastWriteTime (file), !scanSuccessful, addinScanDataFileMD5);
|
||||
|
||||
if (scanSuccessful && config != null) {
|
||||
// Update the ignore list in the folder info object. To be used in the next scan
|
||||
|
@ -385,9 +292,9 @@ namespace Mono.Addins.Database
|
|||
bool scanSuccessful;
|
||||
|
||||
if (ext == ".dll" || ext == ".exe")
|
||||
scanSuccessful = ScanAssembly (monitor, file, scanResult, out config);
|
||||
scanSuccessful = ScanAssembly (monitor, file, scanResult.ScanContext, out config);
|
||||
else
|
||||
scanSuccessful = ScanConfigAssemblies (monitor, file, scanResult, out config);
|
||||
scanSuccessful = ScanConfigAssemblies (monitor, file, scanResult.ScanContext, out config);
|
||||
|
||||
if (scanSuccessful && config != null) {
|
||||
|
||||
|
@ -408,117 +315,25 @@ namespace Mono.Addins.Database
|
|||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
public void ScanAddinsFile (IProgressStatus monitor, string file, string domain, AddinScanResult scanResult)
|
||||
{
|
||||
XmlTextReader r = null;
|
||||
ArrayList directories = new ArrayList ();
|
||||
ArrayList directoriesWithSubdirs = new ArrayList ();
|
||||
string basePath = Path.GetDirectoryName (file);
|
||||
|
||||
try {
|
||||
r = new XmlTextReader (fs.OpenTextFile (file));
|
||||
r.MoveToContent ();
|
||||
if (r.IsEmptyElement)
|
||||
return;
|
||||
r.ReadStartElement ();
|
||||
r.MoveToContent ();
|
||||
while (r.NodeType != XmlNodeType.EndElement) {
|
||||
if (r.NodeType == XmlNodeType.Element && r.LocalName == "Directory") {
|
||||
string subs = r.GetAttribute ("include-subdirs");
|
||||
string sdom;
|
||||
string share = r.GetAttribute ("shared");
|
||||
if (share == "true")
|
||||
sdom = AddinDatabase.GlobalDomain;
|
||||
else if (share == "false")
|
||||
sdom = null;
|
||||
else
|
||||
sdom = domain; // Inherit the domain
|
||||
|
||||
string path = r.ReadElementString ().Trim ();
|
||||
if (path.Length > 0) {
|
||||
path = Util.NormalizePath (path);
|
||||
if (subs == "true")
|
||||
directoriesWithSubdirs.Add (new string[] {path, sdom});
|
||||
else
|
||||
directories.Add (new string[] {path, sdom});
|
||||
}
|
||||
}
|
||||
else if (r.NodeType == XmlNodeType.Element && r.LocalName == "GacAssembly") {
|
||||
string aname = r.ReadElementString ().Trim ();
|
||||
if (aname.Length > 0) {
|
||||
aname = Util.NormalizePath (aname);
|
||||
aname = Util.GetGacPath (aname);
|
||||
if (aname != null) {
|
||||
// Gac assemblies always use the global domain
|
||||
directories.Add (new string[] {aname, AddinDatabase.GlobalDomain});
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (r.NodeType == XmlNodeType.Element && r.LocalName == "Exclude") {
|
||||
string path = r.ReadElementString ().Trim ();
|
||||
if (path.Length > 0) {
|
||||
path = Util.NormalizePath (path);
|
||||
if (!Path.IsPathRooted (path))
|
||||
path = Path.Combine (basePath, path);
|
||||
scanResult.AddPathToIgnore (Path.GetFullPath (path));
|
||||
}
|
||||
}
|
||||
else
|
||||
r.Skip ();
|
||||
r.MoveToContent ();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
monitor.ReportError ("Could not process addins file: " + file, ex);
|
||||
return;
|
||||
} finally {
|
||||
if (r != null)
|
||||
r.Close ();
|
||||
}
|
||||
|
||||
foreach (string[] d in directories) {
|
||||
string dir = d[0];
|
||||
if (!Path.IsPathRooted (dir))
|
||||
dir = Path.Combine (basePath, dir);
|
||||
ScanFolder (monitor, dir, d[1], scanResult);
|
||||
}
|
||||
foreach (string[] d in directoriesWithSubdirs) {
|
||||
string dir = d[0];
|
||||
if (!Path.IsPathRooted (dir))
|
||||
dir = Path.Combine (basePath, dir);
|
||||
ScanFolderRec (monitor, dir, d[1], scanResult);
|
||||
}
|
||||
}
|
||||
|
||||
public void ScanFolderRec (IProgressStatus monitor, string dir, string domain, AddinScanResult scanResult)
|
||||
{
|
||||
ScanFolder (monitor, dir, domain, scanResult);
|
||||
|
||||
if (!fs.DirectoryExists (dir))
|
||||
return;
|
||||
|
||||
foreach (string sd in fs.GetDirectories (dir))
|
||||
ScanFolderRec (monitor, sd, domain, scanResult);
|
||||
}
|
||||
|
||||
bool ScanConfigAssemblies (IProgressStatus monitor, string filePath, AddinScanResult scanResult, out AddinDescription config)
|
||||
|
||||
public bool ScanConfigAssemblies (IProgressStatus monitor, string filePath, ScanContext scanContext, out AddinDescription config)
|
||||
{
|
||||
config = null;
|
||||
|
||||
IAssemblyReflector reflector = null;
|
||||
try {
|
||||
reflector = GetReflector (monitor, scanResult, filePath);
|
||||
reflector = GetReflector (monitor, assemblyLocator, filePath);
|
||||
|
||||
string basePath = Path.GetDirectoryName (filePath);
|
||||
|
||||
using (var s = fs.OpenFile (filePath)) {
|
||||
using (var s = database.FileSystem.OpenFile (filePath)) {
|
||||
config = AddinDescription.Read (s, basePath);
|
||||
}
|
||||
config.FileName = filePath;
|
||||
config.SetBasePath (basePath);
|
||||
config.AddinFile = filePath;
|
||||
|
||||
return ScanDescription (monitor, reflector, config, null, scanResult);
|
||||
return ScanDescription (monitor, reflector, config, null, scanContext);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
// Something went wrong while scanning the assembly. We'll ignore it for now.
|
||||
|
@ -527,9 +342,9 @@ namespace Mono.Addins.Database
|
|||
}
|
||||
}
|
||||
|
||||
IAssemblyReflector GetReflector (IProgressStatus monitor, AddinScanResult scanResult, string filePath)
|
||||
IAssemblyReflector GetReflector (IProgressStatus monitor, IAssemblyLocator locator, string filePath)
|
||||
{
|
||||
IAssemblyReflector reflector = fs.GetReflectorForFile (scanResult, filePath);
|
||||
IAssemblyReflector reflector = database.FileSystem.GetReflectorForFile (locator, filePath);
|
||||
object coreAssembly;
|
||||
if (!coreAssemblies.TryGetValue (reflector, out coreAssembly)) {
|
||||
if (monitor.LogLevel > 1)
|
||||
|
@ -539,14 +354,14 @@ namespace Mono.Addins.Database
|
|||
return reflector;
|
||||
}
|
||||
|
||||
bool ScanAssembly (IProgressStatus monitor, string filePath, AddinScanResult scanResult, out AddinDescription config)
|
||||
public bool ScanAssembly (IProgressStatus monitor, string filePath, ScanContext scanContext, out AddinDescription config)
|
||||
{
|
||||
config = null;
|
||||
|
||||
IAssemblyReflector reflector = null;
|
||||
object asm = null;
|
||||
try {
|
||||
reflector = GetReflector (monitor, scanResult, filePath);
|
||||
reflector = GetReflector (monitor, assemblyLocator, filePath);
|
||||
asm = reflector.LoadAssembly (filePath);
|
||||
if (asm == null)
|
||||
throw new Exception ("Could not load assembly: " + filePath);
|
||||
|
@ -576,7 +391,7 @@ namespace Mono.Addins.Database
|
|||
if (!config.MainModule.Assemblies.Contains (rasmFile))
|
||||
config.MainModule.Assemblies.Add (rasmFile);
|
||||
|
||||
bool res = ScanDescription (monitor, reflector, config, asm, scanResult);
|
||||
bool res = ScanDescription (monitor, reflector, config, asm, scanContext);
|
||||
if (!res)
|
||||
reflector.UnloadAssembly (asm);
|
||||
return res;
|
||||
|
@ -594,7 +409,7 @@ namespace Mono.Addins.Database
|
|||
{
|
||||
config = null;
|
||||
foreach (string res in reflector.GetResourceNames (asm)) {
|
||||
if (res.EndsWith (".addin") || res.EndsWith (".addin.xml")) {
|
||||
if (res.EndsWith (".addin", StringComparison.Ordinal) || res.EndsWith (".addin.xml", StringComparison.Ordinal)) {
|
||||
using (Stream s = reflector.GetResourceStream (asm, res)) {
|
||||
AddinDescription ad = AddinDescription.Read (s, Path.GetDirectoryName (filePath));
|
||||
if (config != null) {
|
||||
|
@ -613,7 +428,7 @@ namespace Mono.Addins.Database
|
|||
return true;
|
||||
}
|
||||
|
||||
bool ScanDescription (IProgressStatus monitor, IAssemblyReflector reflector, AddinDescription config, object rootAssembly, AddinScanResult scanResult)
|
||||
bool ScanDescription (IProgressStatus monitor, IAssemblyReflector reflector, AddinDescription config, object rootAssembly, ScanContext scanContext)
|
||||
{
|
||||
// First of all scan the main module
|
||||
|
||||
|
@ -623,7 +438,7 @@ namespace Mono.Addins.Database
|
|||
string rootAsmFile = null;
|
||||
|
||||
if (rootAssembly != null) {
|
||||
ScanAssemblyAddinHeaders (reflector, config, rootAssembly, scanResult);
|
||||
ScanAssemblyAddinHeaders (reflector, config, rootAssembly);
|
||||
ScanAssemblyImports (reflector, config.MainModule, rootAssembly);
|
||||
assemblies.Add (rootAssembly);
|
||||
rootAsmFile = Path.GetFileName (config.AddinFile);
|
||||
|
@ -634,12 +449,12 @@ namespace Mono.Addins.Database
|
|||
for (int n=0; n<config.MainModule.Assemblies.Count; n++) {
|
||||
string s = config.MainModule.Assemblies [n];
|
||||
string asmFile = Path.GetFullPath (Path.Combine (config.BasePath, Util.NormalizePath (s)));
|
||||
scanResult.AddPathToIgnore (asmFile);
|
||||
scanContext.AddPathToIgnore (asmFile);
|
||||
if (s == rootAsmFile || config.MainModule.IgnorePaths.Contains (s))
|
||||
continue;
|
||||
object asm = reflector.LoadAssembly (asmFile);
|
||||
assemblies.Add (asm);
|
||||
ScanAssemblyAddinHeaders (reflector, config, asm, scanResult);
|
||||
ScanAssemblyAddinHeaders (reflector, config, asm);
|
||||
ScanAssemblyImports (reflector, config.MainModule, asm);
|
||||
}
|
||||
|
||||
|
@ -647,24 +462,20 @@ namespace Mono.Addins.Database
|
|||
// which are included as 'data' in an add-in.
|
||||
foreach (string df in config.MainModule.DataFiles) {
|
||||
string file = Path.Combine (config.BasePath, Util.NormalizePath (df));
|
||||
scanResult.AddPathToIgnore (Path.GetFullPath (file));
|
||||
scanContext.AddPathToIgnore (Path.GetFullPath (file));
|
||||
}
|
||||
foreach (string df in config.MainModule.IgnorePaths) {
|
||||
string path = Path.Combine (config.BasePath, Util.NormalizePath (df));
|
||||
scanResult.AddPathToIgnore (Path.GetFullPath (path));
|
||||
scanContext.AddPathToIgnore (Path.GetFullPath (path));
|
||||
}
|
||||
|
||||
// The add-in id and version must be already assigned at this point
|
||||
|
||||
// Clean host data from the index. New data will be added.
|
||||
if (scanResult.HostIndex != null)
|
||||
scanResult.HostIndex.RemoveHostData (config.AddinId, config.AddinFile);
|
||||
|
||||
foreach (object asm in assemblies)
|
||||
ScanAssemblyContents (reflector, config, config.MainModule, asm, scanResult);
|
||||
ScanAssemblyContents (reflector, config, config.MainModule, asm);
|
||||
|
||||
} catch (Exception ex) {
|
||||
ReportReflectionException (monitor, ex, config, scanResult);
|
||||
ReportReflectionException (monitor, ex, config);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -694,25 +505,25 @@ namespace Mono.Addins.Database
|
|||
string asmFile = Path.Combine (config.BasePath, Util.NormalizePath (s));
|
||||
object asm = reflector.LoadAssembly (asmFile);
|
||||
asmList.Add (new Tuple<string,object> (asmFile,asm));
|
||||
scanResult.AddPathToIgnore (Path.GetFullPath (asmFile));
|
||||
scanContext.AddPathToIgnore (Path.GetFullPath (asmFile));
|
||||
ScanAssemblyImports (reflector, mod, asm);
|
||||
}
|
||||
// Add all data files to the ignore file list. It avoids scanning assemblies
|
||||
// which are included as 'data' in an add-in.
|
||||
foreach (string df in mod.DataFiles) {
|
||||
string file = Path.Combine (config.BasePath, Util.NormalizePath (df));
|
||||
scanResult.AddPathToIgnore (Path.GetFullPath (file));
|
||||
scanContext.AddPathToIgnore (Path.GetFullPath (file));
|
||||
}
|
||||
foreach (string df in mod.IgnorePaths) {
|
||||
string path = Path.Combine (config.BasePath, Util.NormalizePath (df));
|
||||
scanResult.AddPathToIgnore (Path.GetFullPath (path));
|
||||
scanContext.AddPathToIgnore (Path.GetFullPath (path));
|
||||
}
|
||||
|
||||
foreach (var asm in asmList)
|
||||
ScanSubmodule (monitor, mod, reflector, config, scanResult, asm.Item1, asm.Item2);
|
||||
ScanSubmodule (monitor, mod, reflector, config, asm.Item1, asm.Item2);
|
||||
|
||||
} catch (Exception ex) {
|
||||
ReportReflectionException (monitor, ex, config, scanResult);
|
||||
ReportReflectionException (monitor, ex, config);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -721,7 +532,7 @@ namespace Mono.Addins.Database
|
|||
return true;
|
||||
}
|
||||
|
||||
bool ScanSubmodule (IProgressStatus monitor, ModuleDescription mod, IAssemblyReflector reflector, AddinDescription config, AddinScanResult scanResult, string assemblyName, object asm)
|
||||
bool ScanSubmodule (IProgressStatus monitor, ModuleDescription mod, IAssemblyReflector reflector, AddinDescription config, string assemblyName, object asm)
|
||||
{
|
||||
AddinDescription mconfig;
|
||||
ScanEmbeddedDescription (monitor, assemblyName, reflector, asm, out mconfig);
|
||||
|
@ -748,13 +559,12 @@ namespace Mono.Addins.Database
|
|||
}
|
||||
mod.MergeWith (mconfig.MainModule);
|
||||
}
|
||||
ScanAssemblyContents (reflector, config, mod, asm, scanResult);
|
||||
ScanAssemblyContents (reflector, config, mod, asm);
|
||||
return true;
|
||||
}
|
||||
|
||||
void ReportReflectionException (IProgressStatus monitor, Exception ex, AddinDescription config, AddinScanResult scanResult)
|
||||
void ReportReflectionException (IProgressStatus monitor, Exception ex, AddinDescription config)
|
||||
{
|
||||
scanResult.AddFileToWithFailure (config.AddinFile);
|
||||
monitor.ReportWarning ("[" + config.AddinId + "] Could not load some add-in assemblies: " + ex.Message);
|
||||
if (monitor.LogLevel <= 1)
|
||||
return;
|
||||
|
@ -766,7 +576,7 @@ namespace Mono.Addins.Database
|
|||
}
|
||||
}
|
||||
|
||||
void ScanAssemblyAddinHeaders (IAssemblyReflector reflector, AddinDescription config, object asm, AddinScanResult scanResult)
|
||||
void ScanAssemblyAddinHeaders (IAssemblyReflector reflector, AddinDescription config, object asm)
|
||||
{
|
||||
// Get basic add-in information
|
||||
AddinAttribute att = (AddinAttribute) reflector.GetCustomAttribute (asm, typeof(AddinAttribute), false);
|
||||
|
@ -895,7 +705,7 @@ namespace Mono.Addins.Database
|
|||
}
|
||||
}
|
||||
|
||||
void ScanAssemblyContents (IAssemblyReflector reflector, AddinDescription config, ModuleDescription module, object asm, AddinScanResult scanResult)
|
||||
void ScanAssemblyContents (IAssemblyReflector reflector, AddinDescription config, ModuleDescription module, object asm)
|
||||
{
|
||||
bool isMainModule = module == config.MainModule;
|
||||
|
||||
|
@ -1282,14 +1092,9 @@ namespace Mono.Addins.Database
|
|||
elem.SetAttribute ("insertbefore", eatt.InsertBefore);
|
||||
}
|
||||
|
||||
internal string GetDefaultTypeExtensionPath (AddinDescription desc, string typeFullName)
|
||||
string GetDefaultTypeExtensionPath (AddinDescription desc, string typeFullName)
|
||||
{
|
||||
return "/" + Addin.GetIdName (desc.AddinId) + "/TypeExtensions/" + typeFullName;
|
||||
}
|
||||
|
||||
internal void CleanupReflector()
|
||||
{
|
||||
fs.CleanupReflector ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,127 @@
|
|||
//
|
||||
// AssemblyLocatorVisitor.cs
|
||||
//
|
||||
// Author:
|
||||
// Lluis Sanchez <llsan@microsoft.com>
|
||||
//
|
||||
// Copyright (c) 2018 Microsoft
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Mono.Addins.Database
|
||||
{
|
||||
class AssemblyLocatorVisitor: AddinFolderVisitor, IAssemblyLocator
|
||||
{
|
||||
AddinRegistry registry;
|
||||
AssemblyIndex index;
|
||||
bool usePreScanDataFiles;
|
||||
|
||||
public AssemblyLocatorVisitor (AddinDatabase database, AddinRegistry registry, bool usePreScanDataFiles): base (database)
|
||||
{
|
||||
this.registry = registry;
|
||||
this.usePreScanDataFiles = usePreScanDataFiles;
|
||||
}
|
||||
|
||||
public string GetAssemblyLocation (string fullName)
|
||||
{
|
||||
if (index == null) {
|
||||
index = new AssemblyIndex ();
|
||||
if (registry.StartupDirectory != null)
|
||||
VisitFolder (null, registry.StartupDirectory, null, false);
|
||||
foreach (string dir in registry.GlobalAddinDirectories)
|
||||
VisitFolder (null, dir, AddinDatabase.GlobalDomain, true);
|
||||
}
|
||||
|
||||
return index.GetAssemblyLocation (fullName);
|
||||
}
|
||||
|
||||
protected override void OnVisitFolder (IProgressStatus monitor, string path, string domain, bool recursive)
|
||||
{
|
||||
if (usePreScanDataFiles) {
|
||||
var scanDataIndex = AddinScanDataIndex.LoadFromFolder (monitor, path);
|
||||
if (scanDataIndex != null) {
|
||||
foreach (var file in scanDataIndex.Assemblies)
|
||||
index.AddAssemblyLocation (file);
|
||||
return;
|
||||
}
|
||||
}
|
||||
base.OnVisitFolder (monitor, path, domain, recursive);
|
||||
}
|
||||
|
||||
protected override void OnVisitAssemblyFile (IProgressStatus monitor, string file)
|
||||
{
|
||||
index.AddAssemblyLocation (file);
|
||||
}
|
||||
}
|
||||
|
||||
class AssemblyIndex: IAssemblyLocator
|
||||
{
|
||||
Dictionary<string,List<string>> assemblyLocations = new Dictionary<string, List<string>> ();
|
||||
Dictionary<string,string> assemblyLocationsByFullName = new Dictionary<string, string> ();
|
||||
|
||||
public void AddAssemblyLocation (string file)
|
||||
{
|
||||
string name = Path.GetFileNameWithoutExtension (file);
|
||||
if (!assemblyLocations.TryGetValue (name, out var list)) {
|
||||
list = new List<string> ();
|
||||
assemblyLocations [name] = list;
|
||||
}
|
||||
list.Add (file);
|
||||
}
|
||||
|
||||
public string GetAssemblyLocation (string fullName)
|
||||
{
|
||||
if (assemblyLocationsByFullName.TryGetValue (fullName, out var loc))
|
||||
return loc;
|
||||
|
||||
int i = fullName.IndexOf (',');
|
||||
string name = fullName.Substring (0, i);
|
||||
if (name == "Mono.Addins")
|
||||
return GetType ().Assembly.Location;
|
||||
|
||||
if (!assemblyLocations.TryGetValue (name, out var list))
|
||||
return null;
|
||||
|
||||
string lastAsm = null;
|
||||
foreach (string file in list.ToArray ()) {
|
||||
try {
|
||||
list.Remove (file);
|
||||
AssemblyName aname = AssemblyName.GetAssemblyName (file);
|
||||
lastAsm = file;
|
||||
assemblyLocationsByFullName [aname.FullName] = file;
|
||||
if (aname.FullName == fullName)
|
||||
return file;
|
||||
} catch {
|
||||
// Could not get the assembly name. The file either doesn't exist or it is not a valid assembly.
|
||||
// In this case, just ignore it.
|
||||
}
|
||||
}
|
||||
|
||||
if (lastAsm != null) {
|
||||
// If an exact version is not found, just take any of them
|
||||
return lastAsm;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -30,7 +30,8 @@ namespace Mono.Addins.Database
|
|||
{
|
||||
internal interface ISetupHandler
|
||||
{
|
||||
void Scan (IProgressStatus monitor, AddinRegistry registry, string scanFolder, string[] filesToIgnore);
|
||||
void Scan (IProgressStatus monitor, AddinRegistry registry, string scanFolder, ScanOptions context);
|
||||
void GenerateScanDataFiles (IProgressStatus monitor, AddinRegistry registry, string scanFolder, bool recursive);
|
||||
void GetAddinDescription (IProgressStatus monitor, AddinRegistry registry, string file, string outFile);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
//
|
||||
// ScanContext.cs
|
||||
//
|
||||
// Author:
|
||||
// Lluis Sanchez <llsan@microsoft.com>
|
||||
//
|
||||
// Copyright (c) 2017 Microsoft
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace Mono.Addins.Database
|
||||
{
|
||||
[Serializable]
|
||||
class ScanOptions
|
||||
{
|
||||
public List<string> FilesToIgnore { get; set; } = new List<string> ();
|
||||
public bool CleanGeneratedAddinScanDataFiles { get; set; }
|
||||
public AddinFileSystemExtension FileSystemExtension { get; set; }
|
||||
|
||||
public IEnumerable<string> Write ()
|
||||
{
|
||||
yield return FilesToIgnore.Count.ToString ();
|
||||
foreach (var file in FilesToIgnore)
|
||||
yield return file;
|
||||
|
||||
yield return CleanGeneratedAddinScanDataFiles.ToString ();
|
||||
}
|
||||
|
||||
public void Read (TextReader reader)
|
||||
{
|
||||
int filesToIgnoreCount = int.Parse (reader.ReadLine ());
|
||||
for (int n = 0; n < filesToIgnoreCount; n++)
|
||||
FilesToIgnore.Add (reader.ReadLine ());
|
||||
|
||||
CleanGeneratedAddinScanDataFiles = bool.Parse (Console.In.ReadLine ());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -35,12 +35,26 @@ namespace Mono.Addins.Database
|
|||
RemoteSetupDomain remoteSetupDomain;
|
||||
int useCount;
|
||||
|
||||
public void Scan (IProgressStatus monitor, AddinRegistry registry, string scanFolder, string[] filesToIgnore)
|
||||
public void Scan (IProgressStatus monitor, AddinRegistry registry, string scanFolder, ScanOptions context)
|
||||
{
|
||||
RemoteProgressStatus remMonitor = new RemoteProgressStatus (monitor);
|
||||
try {
|
||||
RemoteSetupDomain rsd = GetDomain ();
|
||||
rsd.Scan (remMonitor, registry.RegistryPath, registry.StartupDirectory, registry.DefaultAddinsFolder, registry.AddinCachePath, scanFolder, filesToIgnore);
|
||||
rsd.Scan (remMonitor, registry.RegistryPath, registry.StartupDirectory, registry.DefaultAddinsFolder, registry.AddinCachePath, scanFolder, context);
|
||||
} catch (Exception ex) {
|
||||
throw new ProcessFailedException (remMonitor.ProgessLog, ex);
|
||||
} finally {
|
||||
System.Runtime.Remoting.RemotingServices.Disconnect (remMonitor);
|
||||
ReleaseDomain ();
|
||||
}
|
||||
}
|
||||
|
||||
public void GenerateScanDataFiles (IProgressStatus monitor, AddinRegistry registry, string scanFolder, bool recursive)
|
||||
{
|
||||
RemoteProgressStatus remMonitor = new RemoteProgressStatus (monitor);
|
||||
try {
|
||||
RemoteSetupDomain rsd = GetDomain ();
|
||||
rsd.PreScan (remMonitor, registry.RegistryPath, registry.StartupDirectory, registry.DefaultAddinsFolder, registry.AddinCachePath, scanFolder, recursive);
|
||||
} catch (Exception ex) {
|
||||
throw new ProcessFailedException (remMonitor.ProgessLog, ex);
|
||||
} finally {
|
||||
|
@ -114,14 +128,20 @@ namespace Mono.Addins.Database
|
|||
return null;
|
||||
}
|
||||
|
||||
public void Scan (IProgressStatus monitor, string registryPath, string startupDir, string addinsDir, string databaseDir, string scanFolder, string[] filesToIgnore)
|
||||
public void Scan (IProgressStatus monitor, string registryPath, string startupDir, string addinsDir, string databaseDir, string scanFolder, ScanOptions context)
|
||||
{
|
||||
AddinDatabase.RunningSetupProcess = true;
|
||||
AddinRegistry reg = new AddinRegistry (registryPath, startupDir, addinsDir, databaseDir);
|
||||
StringCollection files = new StringCollection ();
|
||||
for (int n=0; n<filesToIgnore.Length; n++)
|
||||
files.Add (filesToIgnore[n]);
|
||||
reg.ScanFolders (monitor, scanFolder, files);
|
||||
if (context.FileSystemExtension != null)
|
||||
reg.RegisterExtension (context.FileSystemExtension);
|
||||
reg.ScanFolders (monitor, scanFolder, context);
|
||||
}
|
||||
|
||||
public void PreScan (IProgressStatus monitor, string registryPath, string startupDir, string addinsDir, string databaseDir, string scanFolder, bool recursive)
|
||||
{
|
||||
AddinDatabase.RunningSetupProcess = true;
|
||||
AddinRegistry reg = new AddinRegistry (registryPath, startupDir, addinsDir, databaseDir);
|
||||
reg.GenerateScanDataFilesInProcess (monitor, scanFolder, recursive);
|
||||
}
|
||||
|
||||
public void GetAddinDescription (IProgressStatus monitor, string registryPath, string startupDir, string addinsDir, string databaseDir, string file, string outFile)
|
||||
|
|
|
@ -30,14 +30,18 @@ namespace Mono.Addins.Database
|
|||
{
|
||||
class SetupLocal: ISetupHandler
|
||||
{
|
||||
public void Scan (IProgressStatus monitor, AddinRegistry registry, string scanFolder, string[] filesToIgnore)
|
||||
public void Scan (IProgressStatus monitor, AddinRegistry registry, string scanFolder, ScanOptions context)
|
||||
{
|
||||
AddinRegistry reg = new AddinRegistry (registry.RegistryPath, registry.StartupDirectory, registry.DefaultAddinsFolder, registry.AddinCachePath);
|
||||
reg.CopyExtensionsFrom (registry);
|
||||
StringCollection files = new StringCollection ();
|
||||
for (int n=0; n<filesToIgnore.Length; n++)
|
||||
files.Add (filesToIgnore[n]);
|
||||
reg.ScanFolders (monitor, scanFolder, files);
|
||||
reg.ScanFolders (monitor, scanFolder, context);
|
||||
}
|
||||
|
||||
public void GenerateScanDataFiles (IProgressStatus monitor, AddinRegistry registry, string scanFolder, bool recursive)
|
||||
{
|
||||
AddinRegistry reg = new AddinRegistry (registry.RegistryPath, registry.StartupDirectory, registry.DefaultAddinsFolder, registry.AddinCachePath);
|
||||
reg.CopyExtensionsFrom (registry);
|
||||
reg.GenerateScanDataFilesInProcess (monitor, scanFolder, recursive);
|
||||
}
|
||||
|
||||
public void GetAddinDescription (IProgressStatus monitor, AddinRegistry registry, string file, string outFile)
|
||||
|
|
|
@ -35,22 +35,30 @@ using System.Diagnostics;
|
|||
using System.Runtime.Serialization.Formatters.Binary;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Mono.Addins.Database
|
||||
{
|
||||
class SetupProcess: ISetupHandler
|
||||
{
|
||||
public void Scan (IProgressStatus monitor, AddinRegistry registry, string scanFolder, string[] filesToIgnore)
|
||||
public void Scan (IProgressStatus monitor, AddinRegistry registry, string scanFolder, ScanOptions context)
|
||||
{
|
||||
ExecuteCommand (monitor, registry.RegistryPath, registry.StartupDirectory, registry.DefaultAddinsFolder, registry.AddinCachePath, "scan", scanFolder, filesToIgnore);
|
||||
var data = new List<string> (context.Write ());
|
||||
|
||||
ExecuteCommand (monitor, registry.RegistryPath, registry.StartupDirectory, registry.DefaultAddinsFolder, registry.AddinCachePath, "scan", scanFolder, data);
|
||||
}
|
||||
|
||||
public void GenerateScanDataFiles (IProgressStatus monitor, AddinRegistry registry, string scanFolder, bool recursive)
|
||||
{
|
||||
ExecuteCommand (monitor, registry.RegistryPath, registry.StartupDirectory, registry.DefaultAddinsFolder, registry.AddinCachePath, "pre-scan", scanFolder, new List<string> { recursive.ToString() });
|
||||
}
|
||||
|
||||
public void GetAddinDescription (IProgressStatus monitor, AddinRegistry registry, string file, string outFile)
|
||||
{
|
||||
ExecuteCommand (monitor, registry.RegistryPath, registry.StartupDirectory, registry.DefaultAddinsFolder, registry.AddinCachePath, "get-desc", file, outFile);
|
||||
ExecuteCommand (monitor, registry.RegistryPath, registry.StartupDirectory, registry.DefaultAddinsFolder, registry.AddinCachePath, "get-desc", file, new List<string> { outFile });
|
||||
}
|
||||
|
||||
internal static void ExecuteCommand (IProgressStatus monitor, string registryPath, string startupDir, string addinsDir, string databaseDir, string name, string arg1, params string[] args)
|
||||
internal static void ExecuteCommand (IProgressStatus monitor, string registryPath, string startupDir, string addinsDir, string databaseDir, string name, string arg1, List<string> data)
|
||||
{
|
||||
string verboseParam = monitor.LogLevel.ToString ();
|
||||
|
||||
|
@ -58,9 +66,7 @@ namespace Mono.Addins.Database
|
|||
StringBuilder sb = new StringBuilder ();
|
||||
sb.Append (verboseParam).Append (' ').Append (name);
|
||||
sb.Append (" \"").Append (arg1).Append ("\"");
|
||||
foreach (string arg in args)
|
||||
sb.Append (" \"").Append (arg).Append ("\"");
|
||||
|
||||
|
||||
Process process = new Process ();
|
||||
|
||||
string asm = null;
|
||||
|
@ -85,11 +91,13 @@ namespace Mono.Addins.Database
|
|||
process.StandardInput.WriteLine (startupDir);
|
||||
process.StandardInput.WriteLine (addinsDir);
|
||||
process.StandardInput.WriteLine (databaseDir);
|
||||
|
||||
if (data != null) {
|
||||
foreach (var d in data)
|
||||
process.StandardInput.WriteLine (d);
|
||||
}
|
||||
process.StandardInput.Flush ();
|
||||
|
||||
// string rr = process.StandardOutput.ReadToEnd ();
|
||||
// Console.WriteLine (rr);
|
||||
|
||||
StringCollection progessLog = new StringCollection ();
|
||||
ProcessProgressStatus.MonitorProcessStatus (monitor, process.StandardOutput, progessLog);
|
||||
process.WaitForExit ();
|
||||
|
@ -107,31 +115,40 @@ namespace Mono.Addins.Database
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int Main (string[] args)
|
||||
|
||||
public static int Main (string [] args)
|
||||
{
|
||||
ProcessProgressStatus monitor = new ProcessProgressStatus (int.Parse (args[0]));
|
||||
|
||||
ProcessProgressStatus monitor = new ProcessProgressStatus (int.Parse (args [0]));
|
||||
|
||||
try {
|
||||
string registryPath = Console.In.ReadLine ();
|
||||
string startupDir = Console.In.ReadLine ();
|
||||
string addinsDir = Console.In.ReadLine ();
|
||||
string databaseDir = Console.In.ReadLine ();
|
||||
|
||||
|
||||
AddinDatabase.RunningSetupProcess = true;
|
||||
AddinRegistry reg = new AddinRegistry (registryPath, startupDir, addinsDir, databaseDir);
|
||||
|
||||
|
||||
switch (args [1]) {
|
||||
case "scan":
|
||||
string folder = args.Length > 2 ? args [2] : null;
|
||||
if (folder.Length == 0) folder = null;
|
||||
StringCollection filesToIgnore = new StringCollection ();
|
||||
for (int n=3; n<args.Length; n++)
|
||||
filesToIgnore.Add (args[n]);
|
||||
reg.ScanFolders (monitor, folder, filesToIgnore);
|
||||
break;
|
||||
case "scan": {
|
||||
string folder = args.Length > 2 ? args [2] : null;
|
||||
if (folder.Length == 0) folder = null;
|
||||
|
||||
var context = new ScanOptions ();
|
||||
context.Read (Console.In);
|
||||
reg.ScanFolders (monitor, folder, context);
|
||||
break;
|
||||
}
|
||||
case "pre-scan": {
|
||||
string folder = args.Length > 2 ? args [2] : null;
|
||||
if (folder.Length == 0) folder = null;
|
||||
var recursive = bool.Parse (Console.In.ReadLine ());
|
||||
reg.GenerateScanDataFilesInProcess (monitor, folder, recursive);
|
||||
break;
|
||||
}
|
||||
case "get-desc":
|
||||
reg.ParseAddin (monitor, args[2], args[3]);
|
||||
var outFile = Console.In.ReadLine ();
|
||||
reg.ParseAddin (monitor, args [2], args [3]);
|
||||
break;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
|
|
|
@ -34,6 +34,7 @@ using System.Reflection;
|
|||
using Mono.Addins.Description;
|
||||
using Mono.Addins.Serialization;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Mono.Addins.Database
|
||||
{
|
||||
|
@ -318,5 +319,46 @@ namespace Mono.Addins.Database
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
readonly static char[] separators = { Path.DirectorySeparatorChar, Path.VolumeSeparatorChar, Path.AltDirectorySeparatorChar };
|
||||
public static string AbsoluteToRelativePath (string baseDirectoryPath, string absPath)
|
||||
{
|
||||
if (!Path.IsPathRooted (absPath))
|
||||
return absPath;
|
||||
absPath = Path.GetFullPath (absPath);
|
||||
baseDirectoryPath = Path.GetFullPath (baseDirectoryPath.TrimEnd (Path.DirectorySeparatorChar));
|
||||
string [] bPath = baseDirectoryPath.Split (separators);
|
||||
string [] aPath = absPath.Split (separators);
|
||||
int indx = 0;
|
||||
for (; indx < Math.Min (bPath.Length, aPath.Length); indx++) {
|
||||
if (!bPath [indx].Equals (aPath [indx]))
|
||||
break;
|
||||
}
|
||||
if (indx == 0)
|
||||
return absPath;
|
||||
StringBuilder result = new StringBuilder ();
|
||||
for (int i = indx; i < bPath.Length; i++) {
|
||||
result.Append ("..");
|
||||
if (i + 1 < bPath.Length || aPath.Length - indx > 0)
|
||||
result.Append (Path.DirectorySeparatorChar);
|
||||
}
|
||||
result.Append (String.Join (Path.DirectorySeparatorChar.ToString (), aPath, indx, aPath.Length - indx));
|
||||
if (result.Length == 0)
|
||||
return ".";
|
||||
return result.ToString ();
|
||||
}
|
||||
|
||||
public static string GetMD5 (string file)
|
||||
{
|
||||
using (var md5 = System.Security.Cryptography.MD5.Create ()) {
|
||||
using (var stream = File.OpenRead (file)) {
|
||||
var bytes = md5.ComputeHash (stream);
|
||||
StringBuilder sb = new StringBuilder ();
|
||||
foreach (byte b in bytes)
|
||||
sb.Append (b.ToString ("x"));
|
||||
return sb.ToString ();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -364,7 +364,7 @@ namespace Mono.Addins.Serialization
|
|||
}
|
||||
|
||||
public bool EndOfElement {
|
||||
get { return currentType == TagEndElement; }
|
||||
get { return currentType == TagEndElement || currentType == TagEndOfFile; }
|
||||
}
|
||||
|
||||
public void Skip ()
|
||||
|
|
|
@ -155,6 +155,12 @@
|
|||
<Compile Include="Mono.Addins\AddinCategoryAttribute.cs" />
|
||||
<Compile Include="Mono.Addins\AddinFlagsAttribute.cs" />
|
||||
<Compile Include="Mono.Addins\AddinLocalizerAttribute.cs" />
|
||||
<Compile Include="Mono.Addins.Database\ScanOptions.cs" />
|
||||
<Compile Include="Mono.Addins.Database\AddinScanData.cs" />
|
||||
<Compile Include="Mono.Addins.Database\AddinFolderVisitor.cs" />
|
||||
<Compile Include="Mono.Addins.Database\AddinScanDataFileGenerator.cs" />
|
||||
<Compile Include="Mono.Addins.Database\AddinRegistryUpdater.cs" />
|
||||
<Compile Include="Mono.Addins.Database\AssemblyLocatorVisitor.cs" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
|
|
|
@ -645,13 +645,34 @@ namespace Mono.Addins
|
|||
/// </param>
|
||||
public void Rebuild (IProgressStatus monitor)
|
||||
{
|
||||
database.Repair (monitor, currentDomain);
|
||||
var context = new ScanOptions ();
|
||||
context.CleanGeneratedAddinScanDataFiles = true;
|
||||
database.Repair (monitor, currentDomain, context);
|
||||
|
||||
// A full rebuild may cause the domain to change
|
||||
if (!string.IsNullOrEmpty (startupDirectory))
|
||||
currentDomain = database.GetFolderDomain (null, startupDirectory);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Generates add-in data cache files for add-ins in the provided folder
|
||||
/// and any other directory included through a .addins file.
|
||||
/// If folder is not provided, it scans the startup directory.
|
||||
/// </summary>
|
||||
/// <param name="monitor">
|
||||
/// Progress monitor to keep track of the rebuild operation.
|
||||
/// </param>
|
||||
/// <param name="folder">
|
||||
/// Folder that contains the add-ins to be scanned.
|
||||
/// </param>
|
||||
/// <param name="recursive">
|
||||
/// If true, sub-directories are scanned recursively
|
||||
/// </param>
|
||||
public void GenerateAddinScanDataFiles (IProgressStatus monitor, string folder = null, bool recursive = false)
|
||||
{
|
||||
database.GenerateScanDataFiles (monitor, folder ?? StartupDirectory, recursive);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers an extension. Only AddinFileSystemExtension extensions are supported right now.
|
||||
/// </summary>
|
||||
|
@ -691,9 +712,14 @@ namespace Mono.Addins
|
|||
return database.AddinDependsOn (currentDomain, id1, id2);
|
||||
}
|
||||
|
||||
internal void ScanFolders (IProgressStatus monitor, string folderToScan, StringCollection filesToIgnore)
|
||||
internal void ScanFolders (IProgressStatus monitor, string folderToScan, ScanOptions context)
|
||||
{
|
||||
database.ScanFolders (monitor, currentDomain, folderToScan, filesToIgnore);
|
||||
database.ScanFolders (monitor, currentDomain, folderToScan, context);
|
||||
}
|
||||
|
||||
internal void GenerateScanDataFilesInProcess (IProgressStatus monitor, string folderToScan, bool recursive)
|
||||
{
|
||||
database.GenerateScanDataFilesInProcess (monitor, folderToScan, recursive);
|
||||
}
|
||||
|
||||
internal void ParseAddin (IProgressStatus progressStatus, string file, string outFile)
|
||||
|
|
|
@ -0,0 +1,239 @@
|
|||
//
|
||||
// TestScanDataFileGeneration.cs
|
||||
//
|
||||
// Author:
|
||||
// Lluis Sanchez <llsan@microsoft.com>
|
||||
//
|
||||
// Copyright (c) 2018 Microsoft
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Mono.Addins;
|
||||
using Mono.Addins.Database;
|
||||
using NUnit.Framework;
|
||||
using System.Linq;
|
||||
|
||||
namespace UnitTests
|
||||
{
|
||||
public class TestScanDataFileGeneration
|
||||
{
|
||||
static AddinRegistry GetRegistry (string dir)
|
||||
{
|
||||
return new AddinRegistry (Path.Combine (dir, "Config"), Path.Combine (dir, "App"), Path.Combine (dir, "UserAddins"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void NormalScan ()
|
||||
{
|
||||
var dir = Util.GetSampleDirectory ("ScanDataFilesTest");
|
||||
var registry = GetRegistry (dir);
|
||||
|
||||
var fs = new TestAddinFileSystemExtension ();
|
||||
var scanData = fs.FileList;
|
||||
|
||||
registry.RegisterExtension (fs);
|
||||
registry.Update ();
|
||||
|
||||
Assert.Contains (Path.Combine (dir, "App", "SimpleApp.addins"), scanData.OpenedFiles);
|
||||
Assert.Contains (Path.Combine (dir, "App", "SimpleAddin1.addin.xml"), scanData.OpenedFiles);
|
||||
Assert.Contains (Path.Combine (dir, "App", "SimpleApp.addin.xml"), scanData.OpenedFiles);
|
||||
Assert.Contains (Path.Combine (dir, "Addins", "SimpleAddin2.addin.xml"), scanData.OpenedFiles);
|
||||
Assert.Contains (Path.Combine (dir, "Addins", "SimpleAddin3.addin.xml"), scanData.OpenedFiles);
|
||||
Assert.Contains (Path.Combine (dir, "UserAddins", "SimpleAddin4.addin.xml"), scanData.OpenedFiles);
|
||||
|
||||
var addins = registry.GetAddins ().Select (a => a.Id).ToArray ();
|
||||
Assert.AreEqual (4, addins.Length);
|
||||
Assert.Contains ("SimpleApp.Ext1,0.1.0", addins);
|
||||
Assert.Contains ("SimpleApp.Ext2,0.1.0", addins);
|
||||
Assert.Contains ("SimpleApp.Ext3,0.1.0", addins);
|
||||
Assert.Contains ("SimpleApp.Ext4,0.1.0", addins);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GenerateAndLoadScanDataFiles ()
|
||||
{
|
||||
var dir = Util.GetSampleDirectory ("ScanDataFilesTest");
|
||||
var registry = GetRegistry (dir);
|
||||
|
||||
registry.GenerateAddinScanDataFiles (new ConsoleProgressStatus (true), recursive:true);
|
||||
|
||||
// Check that data files have been generated
|
||||
|
||||
Assert.IsTrue (File.Exists (Path.Combine (dir, "App", "dir.addindata")));
|
||||
Assert.IsTrue (File.Exists (Path.Combine (dir, "App", "SimpleAddin1.addin.xml.addindata")));
|
||||
Assert.IsTrue (File.Exists (Path.Combine (dir, "App", "SimpleApp.addin.xml.addindata")));
|
||||
Assert.IsTrue (File.Exists (Path.Combine (dir, "Addins", "SimpleAddin2.addin.xml.addindata")));
|
||||
Assert.IsTrue (File.Exists (Path.Combine (dir, "Addins", "SimpleAddin3.addin.xml.addindata")));
|
||||
|
||||
// But not for user add-ins
|
||||
Assert.IsFalse (File.Exists (Path.Combine (dir, "UserAddins", "SimpleAddin4.addin.xml.addindata")));
|
||||
|
||||
var fs = new TestAddinFileSystemExtension ();
|
||||
var scanData = fs.FileList;
|
||||
|
||||
registry.RegisterExtension (fs);
|
||||
registry.Update ();
|
||||
|
||||
// Check that add-in files are not loaded, with the exception of user add-ins
|
||||
Assert.AreEqual (1, scanData.OpenedFiles.Count);
|
||||
Assert.Contains (Path.Combine (dir, "UserAddins", "SimpleAddin4.addin.xml"), scanData.OpenedFiles);
|
||||
|
||||
var addins = registry.GetAddins ().Select (a => a.Id).ToArray ();
|
||||
Assert.AreEqual (4, addins.Length);
|
||||
Assert.Contains ("SimpleApp.Ext1,0.1.0", addins);
|
||||
Assert.Contains ("SimpleApp.Ext2,0.1.0", addins);
|
||||
Assert.Contains ("SimpleApp.Ext3,0.1.0", addins);
|
||||
Assert.Contains ("SimpleApp.Ext4,0.1.0", addins);
|
||||
|
||||
AddinEngine engine = new AddinEngine ();
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase (false, TestName ="Without scan data files")]
|
||||
[TestCase (true, TestName = "With scan data files")]
|
||||
public void ModifiedAddin (bool useScanDataFiles)
|
||||
{
|
||||
var dir = Util.GetSampleDirectory ("ScanDataFilesTest");
|
||||
var registry = GetRegistry (dir);
|
||||
|
||||
if (useScanDataFiles)
|
||||
registry.GenerateAddinScanDataFiles (new ConsoleProgressStatus (true), recursive: true);
|
||||
|
||||
registry.Update ();
|
||||
|
||||
var addin = registry.GetAddin ("SimpleApp.Ext2,0.1.0");
|
||||
Assert.AreEqual ("FooValue", addin.Properties.GetPropertyValue ("Origin"));
|
||||
|
||||
var addinFile = Path.Combine (dir, "Addins", "SimpleAddin2.addin.xml");
|
||||
var txt = File.ReadAllText (addinFile).Replace ("FooValue", "BarValue");
|
||||
File.WriteAllText (addinFile, txt);
|
||||
|
||||
if (useScanDataFiles) {
|
||||
// Changing the add-in should not have any effect, since scan data files have not changed
|
||||
registry.Update ();
|
||||
addin = registry.GetAddin ("SimpleApp.Ext2,0.1.0");
|
||||
Assert.AreEqual ("FooValue", addin.Properties.GetPropertyValue ("Origin"));
|
||||
|
||||
registry.GenerateAddinScanDataFiles (new ConsoleProgressStatus (true), recursive: true);
|
||||
}
|
||||
|
||||
registry.Update (new ConsoleProgressStatus (true));
|
||||
addin = registry.GetAddin ("SimpleApp.Ext2,0.1.0");
|
||||
Assert.AreEqual ("BarValue", addin.Properties.GetPropertyValue ("Origin"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase (false, TestName = "Without scan data files")]
|
||||
[TestCase (true, TestName = "With scan data files")]
|
||||
public void RemovedAddin (bool useScanDataFiles)
|
||||
{
|
||||
var dir = Util.GetSampleDirectory ("ScanDataFilesTest");
|
||||
var registry = GetRegistry (dir);
|
||||
|
||||
if (useScanDataFiles)
|
||||
registry.GenerateAddinScanDataFiles (new ConsoleProgressStatus (true), recursive: true);
|
||||
|
||||
registry.Update ();
|
||||
|
||||
var addin = registry.GetAddin ("SimpleApp.Ext2,0.1.0");
|
||||
Assert.IsNotNull (addin);
|
||||
|
||||
var addinFile = Path.Combine (dir, "Addins", "SimpleAddin2.addin.xml");
|
||||
File.Delete (addinFile);
|
||||
|
||||
// Removing the add-in file should result on the add-in being unloaded, no matter if
|
||||
// scan data file is present or not
|
||||
|
||||
registry.Update ();
|
||||
addin = registry.GetAddin ("SimpleApp.Ext2,0.1.0");
|
||||
Assert.IsNull (addin);
|
||||
|
||||
registry.GenerateAddinScanDataFiles (new ConsoleProgressStatus (true), recursive: true);
|
||||
|
||||
registry.Update (new ConsoleProgressStatus (true));
|
||||
addin = registry.GetAddin ("SimpleApp.Ext2,0.1.0");
|
||||
Assert.IsNull (addin);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[TestCase (false, TestName = "Without scan data files")]
|
||||
[TestCase (true, TestName = "With scan data files")]
|
||||
public void AddedAddin (bool useScanDataFiles)
|
||||
{
|
||||
var dir = Util.GetSampleDirectory ("ScanDataFilesTest");
|
||||
var registry = GetRegistry (dir);
|
||||
|
||||
if (useScanDataFiles)
|
||||
registry.GenerateAddinScanDataFiles (new ConsoleProgressStatus (true), recursive: true);
|
||||
|
||||
registry.Update ();
|
||||
|
||||
var addinFile = Path.Combine (dir, "Addins", "SimpleAddin2.addin.xml");
|
||||
var txt = File.ReadAllText (addinFile).Replace ("Ext2", "Ext5");
|
||||
var newAddinFile = Path.Combine (dir, "Addins", "SimpleAddin5.addin.xml");
|
||||
File.WriteAllText (newAddinFile, txt);
|
||||
|
||||
Addin addin;
|
||||
|
||||
if (useScanDataFiles) {
|
||||
// Adding an add-in should not have any effect, since scan data files have not changed
|
||||
registry.Update ();
|
||||
addin = registry.GetAddin ("SimpleApp.Ext5,0.1.0");
|
||||
Assert.IsNull (addin);
|
||||
|
||||
registry.GenerateAddinScanDataFiles (new ConsoleProgressStatus (true), recursive: true);
|
||||
}
|
||||
|
||||
registry.Update (new ConsoleProgressStatus (true));
|
||||
addin = registry.GetAddin ("SimpleApp.Ext5,0.1.0");
|
||||
Assert.IsNotNull (addin);
|
||||
}
|
||||
}
|
||||
|
||||
class FileList: MarshalByRefObject
|
||||
{
|
||||
public List<string> OpenedFiles { get; } = new List<string> ();
|
||||
|
||||
public void AddFile (string file)
|
||||
{
|
||||
if (!OpenedFiles.Contains (file))
|
||||
OpenedFiles.Add (file);
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
class TestAddinFileSystemExtension: AddinFileSystemExtension
|
||||
{
|
||||
public FileList FileList = new FileList ();
|
||||
|
||||
public override StreamReader OpenTextFile (string path)
|
||||
{
|
||||
FileList.AddFile (path);
|
||||
return base.OpenTextFile (path);
|
||||
}
|
||||
|
||||
public override Stream OpenFile (string path)
|
||||
{
|
||||
FileList.AddFile (path);
|
||||
return base.OpenFile (path);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -28,7 +28,7 @@
|
|||
<Version>3.9.0</Version>
|
||||
</PackageReference>
|
||||
<PackageReference Include="NUnit3TestAdapter">
|
||||
<Version>3.9.0</Version>
|
||||
<Version>3.10.0</Version>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
@ -72,6 +72,7 @@
|
|||
<Compile Include="TestSetupService.cs" />
|
||||
<Compile Include="ExtensionModel\GlobalInfoConditionAttribute.cs" />
|
||||
<Compile Include="TestScan.cs" />
|
||||
<Compile Include="TestScanDataFileGeneration.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="SimpleApp.addin.xml">
|
||||
|
|
|
@ -104,11 +104,9 @@ namespace UnitTests
|
|||
public static string GetSampleDirectory (string directoryName)
|
||||
{
|
||||
string srcDir = Path.Combine (TestsRootDir, "test-files", directoryName);
|
||||
string projDir = srcDir;
|
||||
srcDir = Path.GetDirectoryName (srcDir);
|
||||
string tmpDir = CreateTmpDir (Path.GetFileName (projDir));
|
||||
string tmpDir = CreateTmpDir (Path.GetFileName (srcDir));
|
||||
CopyDir (srcDir, tmpDir);
|
||||
return Path.Combine (tmpDir, Path.GetFileName (projDir));
|
||||
return tmpDir;
|
||||
}
|
||||
|
||||
public static string CreateTmpDir (string hint)
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
<Addin id = "Ext2"
|
||||
namespace = "SimpleApp"
|
||||
category = "SimpleApp"
|
||||
version = "0.1.0">
|
||||
|
||||
<Header>
|
||||
<Origin>FooValue</Origin>
|
||||
</Header>
|
||||
|
||||
<Dependencies>
|
||||
<Addin id="Core" version="0.1.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Extension path = "/SimpleApp/SampleExtension">
|
||||
<Class type="test2"/>
|
||||
</Extension>
|
||||
</Addin>
|
|
@ -0,0 +1,14 @@
|
|||
<Addin id = "Ext3"
|
||||
namespace = "SimpleApp"
|
||||
category = "SimpleApp"
|
||||
version = "0.1.0">
|
||||
|
||||
<Dependencies>
|
||||
<Addin id="Core" version="0.1.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Extension path = "/SimpleApp/SampleExtension">
|
||||
<Class type="test3"/>
|
||||
</Extension>
|
||||
|
||||
</Addin>
|
|
@ -0,0 +1,13 @@
|
|||
<Addin id = "Ext1"
|
||||
namespace = "SimpleApp"
|
||||
category = "SimpleApp"
|
||||
version = "0.1.0">
|
||||
|
||||
<Dependencies>
|
||||
<Addin id="Core" version="0.1.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Extension path = "/SimpleApp/SampleExtension">
|
||||
<Class type="test1"/>
|
||||
</Extension>
|
||||
</Addin>
|
|
@ -0,0 +1,8 @@
|
|||
<Addin id = "Core"
|
||||
namespace = "SimpleApp"
|
||||
category = "SimpleApp"
|
||||
isroot = "true"
|
||||
version = "0.1.0">
|
||||
|
||||
<ExtensionPoint path = "/SimpleApp/SampleExtension" />
|
||||
</Addin>
|
|
@ -0,0 +1,3 @@
|
|||
<Addins>
|
||||
<Directory>../Addins</Directory>
|
||||
</Addins>
|
|
@ -0,0 +1,14 @@
|
|||
<Addin id = "Ext4"
|
||||
namespace = "SimpleApp"
|
||||
category = "SimpleApp"
|
||||
version = "0.1.0">
|
||||
|
||||
<Dependencies>
|
||||
<Addin id="Core" version="0.1.0" />
|
||||
</Dependencies>
|
||||
|
||||
<Extension path = "/SimpleApp/SampleExtension">
|
||||
<Class type="test4"/>
|
||||
</Extension>
|
||||
|
||||
</Addin>
|
Загрузка…
Ссылка в новой задаче