7
// Copyright (C) 2007 Novell, Inc (http://www.novell.com)
9
// Permission is hereby granted, free of charge, to any person obtaining
10
// a copy of this software and associated documentation files (the
11
// "Software"), to deal in the Software without restriction, including
12
// without limitation the rights to use, copy, modify, merge, publish,
13
// distribute, sublicense, and/or sell copies of the Software, and to
14
// permit persons to whom the Software is furnished to do so, subject to
15
// the following conditions:
17
// The above copyright notice and this permission notice shall be
18
// included in all copies or substantial portions of the Software.
20
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33
using System.Collections;
34
using System.Collections.Generic;
35
using ICSharpCode.SharpZipLib.Zip;
36
using Mono.Addins.Description;
37
using Mono.Addins.Setup.ProgressMonitoring;
38
using Microsoft.Win32;
39
using System.Diagnostics;
42
namespace Mono.Addins.Setup
45
/// Provides tools for managing add-ins
48
/// This class can be used to manage the add-ins of an application. It allows installing and uninstalling
49
/// add-ins, taking into account add-in dependencies. It provides methods for installing add-ins from on-line
50
/// repositories and tools for generating those repositories.
52
public class SetupService
54
RepositoryRegistry repositories;
55
string applicationNamespace;
56
string installDirectory;
58
AddinSystemConfiguration config;
59
const string addinFilesDir = "_addin_files";
61
AddinRegistry registry;
64
/// Initializes a new instance
67
/// If the add-in manager is initialized (AddinManager.Initialize has been called), then this instance
68
/// will manage the add-in registry of the initialized engine.
70
public SetupService ()
72
if (AddinManager.IsInitialized)
73
registry = AddinManager.Registry;
75
registry = AddinRegistry.GetGlobalRegistry ();
77
repositories = new RepositoryRegistry (this);
78
store = new AddinStore (this);
82
/// Initializes a new instance
84
/// <param name="registry">
85
/// Add-in registry to manage
87
public SetupService (AddinRegistry registry)
89
this.registry = registry;
90
repositories = new RepositoryRegistry (this);
91
store = new AddinStore (this);
95
/// The add-in registry being managed
97
public AddinRegistry Registry {
98
get { return registry; }
101
internal string RepositoryCachePath {
102
get { return Path.Combine (registry.RegistryPath, "repository-cache"); }
105
string RootConfigFile {
106
get { return Path.Combine (registry.RegistryPath, "addins-setup.config"); }
110
/// Default add-in namespace of the application (optional). If set, only add-ins that belong to that namespace
111
/// will be shown in add-in lists.
113
public string ApplicationNamespace {
114
get { return applicationNamespace; }
115
set { applicationNamespace = value; }
119
/// Directory where to install add-ins. If not specified, the 'addins' subdirectory of the
120
/// registry location is used.
122
public string InstallDirectory {
124
if (installDirectory != null && installDirectory.Length > 0)
125
return installDirectory;
127
return registry.DefaultAddinsFolder;
129
set { installDirectory = value; }
133
/// Returns a RepositoryRegistry which can be used to manage on-line repository references
135
public RepositoryRegistry Repositories {
136
get { return repositories; }
139
internal AddinStore Store {
140
get { return store; }
144
/// Resolves add-in dependencies.
146
/// <param name="statusMonitor">
147
/// Progress monitor where to show progress status
149
/// <param name="addins">
150
/// List of add-ins to check
152
/// <param name="resolved">
153
/// Packages that need to be installed.
155
/// <param name="toUninstall">
156
/// Packages that need to be uninstalled.
158
/// <param name="unresolved">
159
/// Add-in dependencies that could not be resolved.
162
/// True if all dependencies could be resolved.
165
/// This method can be used to get a list of all packages that have to be installed in order to install
166
/// an add-in or set of add-ins. The list of packages to install will include the package that provides the
167
/// add-in, and all packages that provide the add-in dependencies. In some cases, packages may need to
168
/// be installed (for example, when an installed add-in needs to be upgraded).
170
public bool ResolveDependencies (IProgressStatus statusMonitor, AddinRepositoryEntry[] addins, out PackageCollection resolved, out PackageCollection toUninstall, out DependencyCollection unresolved)
172
return store.ResolveDependencies (statusMonitor, addins, out resolved, out toUninstall, out unresolved);
176
/// Resolves add-in dependencies.
178
/// <param name="statusMonitor">
179
/// Progress monitor where to show progress status
181
/// <param name="packages">
182
/// Packages that need to be installed.
184
/// <param name="toUninstall">
185
/// Packages that need to be uninstalled.
187
/// <param name="unresolved">
188
/// Add-in dependencies that could not be resolved.
191
/// True if all dependencies could be resolved.
194
/// This method can be used to get a list of all packages that have to be installed in order to satisfy
195
/// the dependencies of a package or set of packages. The 'packages' argument must have the list of packages
196
/// to be resolved. When resolving dependencies, if there is any additional package that needs to be installed,
197
/// it will be added to the same 'packages' collection. In some cases, packages may need to
198
/// be installed (for example, when an installed add-in needs to be upgraded). Those packages will be added
199
/// to the 'toUninstall' collection. Packages that could not be resolved are added to the 'unresolved'
202
public bool ResolveDependencies (IProgressStatus statusMonitor, PackageCollection packages, out PackageCollection toUninstall, out DependencyCollection unresolved)
204
return store.ResolveDependencies (statusMonitor, packages, out toUninstall, out unresolved);
208
/// Installs add-in packages
210
/// <param name="statusMonitor">
211
/// Progress monitor where to show progress status
213
/// <param name="files">
214
/// Paths to the packages to install
217
/// True if the installation succeeded
219
public bool Install (IProgressStatus statusMonitor, params string[] files)
221
return store.Install (statusMonitor, files);
225
/// Installs add-in packages from on-line repositories
227
/// <param name="statusMonitor">
228
/// Progress monitor where to show progress status
230
/// <param name="addins">
231
/// References to the add-ins to be installed
234
/// True if the installation succeeded
236
public bool Install (IProgressStatus statusMonitor, params AddinRepositoryEntry[] addins)
238
return store.Install (statusMonitor, addins);
242
/// Installs add-in packages
244
/// <param name="statusMonitor">
245
/// Progress monitor where to show progress status
247
/// <param name="packages">
248
/// Packages to install
251
/// True if the installation succeeded
253
public bool Install (IProgressStatus statusMonitor, PackageCollection packages)
255
return store.Install (statusMonitor, packages);
259
/// Uninstalls an add-in.
261
/// <param name="statusMonitor">
262
/// Progress monitor where to show progress status
264
/// <param name="id">
265
/// Full identifier of the add-in to uninstall.
267
public void Uninstall (IProgressStatus statusMonitor, string id)
269
store.Uninstall (statusMonitor, id);
273
/// Uninstalls a set of add-ins
275
/// <param name='statusMonitor'>
276
/// Progress monitor where to show progress status
278
/// <param name='ids'>
279
/// Full identifiers of the add-ins to uninstall.
281
public void Uninstall (IProgressStatus statusMonitor, IEnumerable<string> ids)
283
store.Uninstall (statusMonitor, ids);
287
/// Gets information about an add-in
289
/// <param name="addin">
293
/// Add-in header data
295
public static AddinHeader GetAddinHeader (Addin addin)
297
return AddinInfo.ReadFromDescription (addin.Description);
301
/// Gets a list of add-ins which depend on an add-in
303
/// <param name="id">
304
/// Full identifier of an add-in.
306
/// <param name="recursive">
307
/// When set to True, dependencies will be gathered recursivelly
310
/// List of dependent add-ins.
313
/// This methods returns a list of add-ins which have the add-in identified by 'id' as a direct
314
/// (or indirect if recursive=True) dependency.
316
public Addin[] GetDependentAddins (string id, bool recursive)
318
return store.GetDependentAddins (id, recursive);
322
/// Packages an add-in
324
/// <param name="statusMonitor">
325
/// Progress monitor where to show progress status
327
/// <param name="targetDirectory">
328
/// Directory where to generate the package
330
/// <param name="filePaths">
331
/// Paths to the add-ins to be packaged. Paths can be either the main assembly of an add-in, or an add-in
332
/// manifest (.addin or .addin.xml).
335
/// This method can be used to create a package for an add-in, which can then be pushed to an on-line
336
/// repository. The package will include the main assembly or manifest of the add-in and any external
337
/// file declared in the add-in metadata.
339
public string[] BuildPackage (IProgressStatus statusMonitor, string targetDirectory, params string[] filePaths)
341
List<string> outFiles = new List<string> ();
342
foreach (string file in filePaths) {
343
string f = BuildPackageInternal (statusMonitor, targetDirectory, file);
347
return outFiles.ToArray ();
350
string BuildPackageInternal (IProgressStatus monitor, string targetDirectory, string filePath)
352
AddinDescription conf = registry.GetAddinDescription (monitor, filePath);
354
monitor.ReportError ("Could not read add-in file: " + filePath, null);
358
string basePath = Path.GetDirectoryName (filePath);
360
if (targetDirectory == null)
361
targetDirectory = basePath;
363
// Generate the file name
366
if (conf.LocalId.Length == 0)
367
name = Path.GetFileNameWithoutExtension (filePath);
370
name = Addin.GetFullId (conf.Namespace, name, conf.Version);
371
name = name.Replace (',','_').Replace (".__", ".");
373
string outFilePath = Path.Combine (targetDirectory, name) + ".mpack";
375
ZipOutputStream s = new ZipOutputStream (File.Create (outFilePath));
378
// Generate a stripped down description of the add-in in a file, since the complete
379
// description may be declared as assembly attributes
381
XmlDocument doc = new XmlDocument ();
382
doc.PreserveWhitespace = false;
383
doc.LoadXml (conf.SaveToXml ().OuterXml);
384
CleanDescription (doc.DocumentElement);
385
MemoryStream ms = new MemoryStream ();
386
XmlTextWriter tw = new XmlTextWriter (ms, System.Text.Encoding.UTF8);
387
tw.Formatting = Formatting.Indented;
390
byte[] data = ms.ToArray ();
392
ZipEntry infoEntry = new ZipEntry ("addin.info");
393
s.PutNextEntry (infoEntry);
394
s.Write (data, 0, data.Length);
396
// Now add the add-in files
398
ArrayList list = new ArrayList ();
399
if (!conf.AllFiles.Contains (Path.GetFileName (filePath)))
400
list.Add (Path.GetFileName (filePath));
401
foreach (string f in conf.AllFiles) {
405
foreach (var prop in conf.Properties) {
407
if (File.Exists (Path.Combine (basePath, prop.Value)))
408
list.Add (prop.Value);
414
monitor.Log ("Creating package " + Path.GetFileName (outFilePath));
416
foreach (string file in list) {
417
string fp = Path.Combine (basePath, file);
418
using (FileStream fs = File.OpenRead (fp)) {
419
byte[] buffer = new byte [fs.Length];
420
fs.Read (buffer, 0, buffer.Length);
422
ZipEntry entry = new ZipEntry (file);
423
s.PutNextEntry (entry);
424
s.Write (buffer, 0, buffer.Length);
433
void CleanDescription (XmlElement parent)
435
ArrayList todelete = new ArrayList ();
437
foreach (XmlNode nod in parent.ChildNodes) {
438
XmlElement elem = nod as XmlElement;
443
if (elem.LocalName == "Module")
444
CleanDescription (elem);
445
else if (elem.LocalName != "Dependencies" && elem.LocalName != "Runtime" && elem.LocalName != "Header")
448
foreach (XmlNode e in todelete)
449
parent.RemoveChild (e);
453
/// Generates an on-line repository
455
/// <param name="statusMonitor">
456
/// Progress monitor where to show progress status
458
/// <param name="path">
459
/// Path to the directory that contains the add-ins and that is going to be published
462
/// This method generates the index files required to publish a directory as an online repository
465
public void BuildRepository (IProgressStatus statusMonitor, string path)
467
string mainPath = Path.Combine (path, "main.mrep");
468
ArrayList allAddins = new ArrayList ();
470
Repository rootrep = (Repository) AddinStore.ReadObject (mainPath, typeof(Repository));
472
rootrep = new Repository ();
474
IProgressMonitor monitor = ProgressStatusMonitor.GetProgressMonitor (statusMonitor);
475
BuildRepository (monitor, rootrep, path, "root.mrep", allAddins);
476
AddinStore.WriteObject (mainPath, rootrep);
477
GenerateIndexPage (rootrep, allAddins, path);
478
monitor.Log.WriteLine ("Updated main.mrep");
481
void BuildRepository (IProgressMonitor monitor, Repository rootrep, string rootPath, string relFilePath, ArrayList allAddins)
483
DateTime lastModified = DateTime.MinValue;
485
string mainFile = Path.Combine (rootPath, relFilePath);
486
string mainPath = Path.GetDirectoryName (mainFile);
487
string supportFileDir = Path.Combine (mainPath, addinFilesDir);
489
if (File.Exists (mainFile))
490
lastModified = File.GetLastWriteTime (mainFile);
492
Repository mainrep = (Repository) AddinStore.ReadObject (mainFile, typeof(Repository));
493
if (mainrep == null) {
494
mainrep = new Repository ();
497
ReferenceRepositoryEntry repEntry = (ReferenceRepositoryEntry) rootrep.FindEntry (relFilePath);
498
DateTime rootLastModified = repEntry != null ? repEntry.LastModified : DateTime.MinValue;
500
bool modified = false;
502
monitor.Log.WriteLine ("Checking directory: " + mainPath);
503
foreach (string file in Directory.GetFiles (mainPath, "*.mpack")) {
505
DateTime date = File.GetLastWriteTime (file);
506
string fname = Path.GetFileName (file);
507
PackageRepositoryEntry entry = (PackageRepositoryEntry) mainrep.FindEntry (fname);
509
if (entry != null && date > rootLastModified) {
510
mainrep.RemoveEntry (entry);
511
DeleteSupportFiles (supportFileDir, entry.Addin);
516
entry = new PackageRepositoryEntry ();
517
AddinPackage p = (AddinPackage) Package.FromFile (file);
518
entry.Addin = (AddinInfo) p.Addin;
520
entry.Addin.Properties.SetPropertyValue ("DownloadSize", new FileInfo (file).Length.ToString ());
521
ExtractSupportFiles (supportFileDir, file, entry.Addin);
522
mainrep.AddEntry (entry);
524
monitor.Log.WriteLine ("Added addin: " + fname);
526
allAddins.Add (entry);
529
ArrayList toRemove = new ArrayList ();
530
foreach (PackageRepositoryEntry entry in mainrep.Addins) {
531
if (!File.Exists (Path.Combine (mainPath, entry.Url))) {
532
toRemove.Add (entry);
537
foreach (PackageRepositoryEntry entry in toRemove) {
538
DeleteSupportFiles (supportFileDir, entry.Addin);
539
mainrep.RemoveEntry (entry);
543
AddinStore.WriteObject (mainFile, mainrep);
544
monitor.Log.WriteLine ("Updated " + relFilePath);
545
lastModified = File.GetLastWriteTime (mainFile);
548
if (repEntry != null) {
549
if (repEntry.LastModified < lastModified)
550
repEntry.LastModified = lastModified;
551
} else if (modified) {
552
repEntry = new ReferenceRepositoryEntry ();
553
repEntry.LastModified = lastModified;
554
repEntry.Url = relFilePath;
555
rootrep.AddEntry (repEntry);
558
foreach (string dir in Directory.GetDirectories (mainPath)) {
559
if (Path.GetFileName (dir) == addinFilesDir)
561
string based = dir.Substring (rootPath.Length + 1);
562
BuildRepository (monitor, rootrep, rootPath, Path.Combine (based, "main.mrep"), allAddins);
566
void DeleteSupportFiles (string targetDir, AddinInfo ainfo)
568
foreach (var prop in ainfo.Properties) {
569
if (prop.Value.StartsWith (addinFilesDir + Path.DirectorySeparatorChar)) {
570
string file = Path.Combine (targetDir, Path.GetFileName (prop.Value));
571
if (File.Exists (file))
575
if (Directory.Exists (targetDir) && Directory.GetFileSystemEntries (targetDir).Length == 0)
576
Directory.Delete (targetDir, true);
579
void ExtractSupportFiles (string targetDir, string file, AddinInfo ainfo)
581
Random r = new Random ();
582
ZipFile zfile = new ZipFile (file);
583
foreach (var prop in ainfo.Properties) {
584
ZipEntry ze = zfile.GetEntry (prop.Value);
588
fname = Path.Combine (targetDir, r.Next().ToString ("x") + Path.GetExtension (prop.Value));
589
} while (File.Exists (fname));
591
if (!Directory.Exists (targetDir))
592
Directory.CreateDirectory (targetDir);
594
using (var f = File.OpenWrite (fname)) {
595
using (Stream s = zfile.GetInputStream (ze)) {
596
byte[] buffer = new byte [8092];
598
while ((nr = s.Read (buffer, 0, buffer.Length)) > 0)
599
f.Write (buffer, 0, nr);
602
prop.Value = Path.Combine (addinFilesDir, Path.GetFileName (fname));
607
void GenerateIndexPage (Repository rep, ArrayList addins, string basePath)
609
StreamWriter sw = new StreamWriter (Path.Combine (basePath, "index.html"));
610
sw.WriteLine ("<html><body>");
611
sw.WriteLine ("<h1>Add-in Repository</h1>");
612
if (rep.Name != null && rep.Name != "")
613
sw.WriteLine ("<h2>" + rep.Name + "</h2>");
614
sw.WriteLine ("<p>This is a list of add-ins available in this repository.</p>");
615
sw.WriteLine ("<table border=1><thead><tr><th>Add-in</th><th>Version</th><th>Description</th></tr></thead>");
617
foreach (PackageRepositoryEntry entry in addins) {
618
sw.WriteLine ("<tr><td>" + entry.Addin.Name + "</td><td>" + entry.Addin.Version + "</td><td>" + entry.Addin.Description + "</td></tr>");
621
sw.WriteLine ("</table>");
622
sw.WriteLine ("</body></html>");
626
internal AddinSystemConfiguration Configuration {
628
if (config == null) {
629
config = (AddinSystemConfiguration) AddinStore.ReadObject (RootConfigFile, typeof(AddinSystemConfiguration));
631
config = new AddinSystemConfiguration ();
637
internal void SaveConfiguration ()
639
if (config != null) {
640
AddinStore.WriteObject (RootConfigFile, config);
644
internal void ResetConfiguration ()
646
if (File.Exists (RootConfigFile))
647
File.Delete (RootConfigFile);
651
internal void ResetAddinInfo ()
653
if (Directory.Exists (RepositoryCachePath))
654
Directory.Delete (RepositoryCachePath, true);
658
/// Gets a reference to an extensible application
660
/// <param name="name">
661
/// Name of the application
664
/// The Application object. Null if not found.
666
public static Application GetExtensibleApplication (string name)
668
return GetExtensibleApplication (name, null);
672
/// Gets a reference to an extensible application
674
/// <param name="name">
675
/// Name of the application
677
/// <param name="searchPaths">
678
/// Custom paths where to look for the application.
681
/// The Application object. Null if not found.
683
public static Application GetExtensibleApplication (string name, IEnumerable<string> searchPaths)
685
AddinsPcFileCache pcc = GetAddinsPcFileCache (searchPaths);
686
PackageInfo pi = pcc.GetPackageInfoByName (name, searchPaths);
688
return new Application (pi);
694
/// Gets a lis of all known extensible applications
697
/// A list of applications.
699
public static Application[] GetExtensibleApplications ()
701
return GetExtensibleApplications (null);
705
/// Gets a lis of all known extensible applications
707
/// <param name="searchPaths">
708
/// Custom paths where to look for applications.
711
/// A list of applications.
713
public static Application[] GetExtensibleApplications (IEnumerable<string> searchPaths)
715
List<Application> list = new List<Application> ();
717
AddinsPcFileCache pcc = GetAddinsPcFileCache (searchPaths);
718
foreach (PackageInfo pinfo in pcc.GetPackages (searchPaths)) {
719
if (pinfo.IsValidPackage)
720
list.Add (new Application (pinfo));
722
return list.ToArray ();
725
static AddinsPcFileCache pcFileCache;
727
static AddinsPcFileCache GetAddinsPcFileCache (IEnumerable<string> searchPaths)
729
if (pcFileCache == null) {
730
pcFileCache = new AddinsPcFileCache ();
731
if (searchPaths != null)
732
pcFileCache.Update (searchPaths);
734
pcFileCache.Update ();
740
class AddinsPcFileCacheContext: IPcFileCacheContext
742
public bool IsCustomDataComplete (string pcfile, PackageInfo pkg)
747
public void StoreCustomData (Mono.PkgConfig.PcFile pcfile, PackageInfo pkg)
751
public void ReportError (string message, System.Exception ex)
753
Console.WriteLine (message);
754
Console.WriteLine (ex);
758
class AddinsPcFileCache: PcFileCache
760
public AddinsPcFileCache (): base (new AddinsPcFileCacheContext ())
764
protected override string CacheDirectory {
766
string path = Environment.GetFolderPath (Environment.SpecialFolder.ApplicationData);
767
path = Path.Combine (path, "mono.addins");
772
protected override void ParsePackageInfo (PcFile file, PackageInfo pinfo)
774
string rootPath = file.GetVariable ("MonoAddinsRoot");
775
string regPath = file.GetVariable ("MonoAddinsRegistry");
776
string addinsPath = file.GetVariable ("MonoAddinsInstallPath");
777
string databasePath = file.GetVariable ("MonoAddinsCachePath");
778
string testCmd = file.GetVariable ("MonoAddinsTestCommand");
779
if (string.IsNullOrEmpty (rootPath) || string.IsNullOrEmpty (regPath))
781
pinfo.SetData ("MonoAddinsRoot", rootPath);
782
pinfo.SetData ("MonoAddinsRegistry", regPath);
783
pinfo.SetData ("MonoAddinsInstallPath", addinsPath);
784
pinfo.SetData ("MonoAddinsCachePath", databasePath);
785
pinfo.SetData ("MonoAddinsTestCommand", testCmd);
790
/// A registered extensible application
792
public class Application
794
AddinRegistry registry;
803
internal Application (PackageInfo pinfo)
806
description = pinfo.Description;
807
startupPath = pinfo.GetData ("MonoAddinsRoot");
808
registryPath = pinfo.GetData ("MonoAddinsRegistry");
809
addinsPath = pinfo.GetData ("MonoAddinsInstallPath");
810
databasePath = pinfo.GetData ("MonoAddinsCachePath");
811
testCommand = pinfo.GetData ("MonoAddinsTestCommand");
815
/// Add-in registry of the application
817
public AddinRegistry Registry {
819
if (registry == null)
820
registry = new AddinRegistry (RegistryPath, StartupPath, AddinsPath, AddinCachePath);
826
/// Description of the application
828
public string Description {
835
/// Name of the application
844
/// Path to the add-in registry
846
public string RegistryPath {
853
/// Path to the directory that contains the main executable assembly of the application
855
public string StartupPath {
862
/// Command to be used to execute the application in add-in development mode.
864
public string TestCommand {
871
/// Path to the default add-ins directory for the aplpication
873
public string AddinsPath {
880
/// Path to the add-in cache for the application
882
public string AddinCachePath {