Merge pull request #101 from mono/gen-data-files

Adds support for generating add-in data cache files
This commit is contained in:
Lluis Sanchez 2018-03-13 14:29:06 +01:00 коммит произвёл GitHub
Родитель b59a8d08dd 4472a6e513
Коммит 041854b7aa
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
29 изменённых файлов: 1696 добавлений и 642 удалений

Просмотреть файл

@ -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>