#region License Information /* HeuristicLab * Copyright (C) 2002-2010 Heuristic and Evolutionary Algorithms Laboratory (HEAL) * * This file is part of HeuristicLab. * * HeuristicLab is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * HeuristicLab is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with HeuristicLab. If not, see . */ #endregion using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Binary; using System.Threading; namespace HeuristicLab.Clients.Hive.SlaveCore { public class PluginCache { private static object locker = new object(); private const string lastUsedFileName = "lastUsed.dat"; //maximum number of days after which a plugin gets deleted if not used private const int maxAge = 5; private static PluginCache instance = null; public string PluginCacheDir { get; set; } public string PluginTempBaseDir { get; set; } private List cachedPluginsGuids = new List(); public static PluginCache Instance { get { if (instance == null) instance = new PluginCache(); return instance; } } public PluginCache() { PluginCacheDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "PluginCache"); PluginTempBaseDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "PluginTemp"); DoUpdateRun(); } private string GetFilenameFromPath(string path) { string[] dirParts = path.Split(Path.DirectorySeparatorChar); if (dirParts.Length > 0) { string fileGuid = dirParts[dirParts.Length - 1]; return fileGuid; } else return ""; } private void DoUpdateRun() { if (!Directory.Exists(PluginCacheDir)) { Directory.CreateDirectory(PluginCacheDir); } foreach (string dir in Directory.EnumerateDirectories(PluginCacheDir)) { cachedPluginsGuids.Add(Guid.Parse(GetFilenameFromPath(dir))); // Todo: cleaner solution to getFilenameFromPath } } [MethodImpl(MethodImplOptions.Synchronized)] public void CopyPluginsForJob(List requests, Guid jobId, out string configFileName) { lock (locker) { configFileName = string.Empty; String targetDir = Path.Combine(PluginTempBaseDir, jobId.ToString()); if (Directory.Exists(targetDir)) { Directory.Delete(targetDir, true); } Directory.CreateDirectory(targetDir); foreach (Plugin requestedPlugin in requests) { var filePaths = GetPluginFilePaths(requestedPlugin.Id); foreach (string filePath in filePaths) { File.Copy(filePath, Path.Combine(targetDir, Path.GetFileName(filePath))); } if (requestedPlugin.Name == "Configuration") { configFileName = Path.Combine(targetDir, Path.GetFileName(filePaths.SingleOrDefault())); // configuration plugin consists only of 1 file (usually the "HeuristicLab X.X.exe.config") } } // copy files from PluginInfrastructure (which are not declared in any plugins) string baseDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); CopyFile(baseDir, targetDir, "HeuristicLab.PluginInfrastructure-3.3.dll"); CopyFile(baseDir, targetDir, "ICSharpCode.SharpZipLib.dll"); CopyFile(baseDir, targetDir, "ICSharpCode.SharpZipLib License.txt"); // copy slave plugins, otherwise its not possible to register the UnhandledException handler to the appdomain CopyFile(baseDir, targetDir, "HeuristicLab.Clients.Hive.SlaveCore-3.4.dll"); CopyFile(baseDir, targetDir, "HeuristicLab.Clients.Hive-3.4.dll"); //CopyFile(baseDir, targetDir, "HeuristicLab.Services.Hive.Common-3.4.dll"); CopyFile(baseDir, targetDir, "HeuristicLab.Hive-3.4.dll"); CopyFile(baseDir, targetDir, "HeuristicLab.Clients.Common-3.3.dll"); //TODO: remove, just needed for unit tests CopyFile(baseDir, targetDir, "HeuristicLab.Clients.Hive.Slave.Tests-3.4.dll"); } } private void CopyFile(string baseDir, string targetDir, string fileName) { if (!File.Exists(Path.Combine(targetDir, fileName))) File.Copy(Path.Combine(baseDir, fileName), Path.Combine(targetDir, fileName)); } [MethodImpl(MethodImplOptions.Synchronized)] internal void PreparePlugins(Job myJob, out string configFileName) { lock (locker) { SlaveClientCom.Instance.ClientCom.LogMessage("Fetching plugins for job"); IEnumerable pluginDescriptions = WcfService.Instance.GetPlugins(); List requiredPlugins = new List(); foreach (Guid pluginId in myJob.PluginsNeededIds) { Plugin pl = pluginDescriptions.FirstOrDefault(p => p.Id == pluginId); if (pl != null) requiredPlugins.Add(pl); } List localPlugins = new List(); List missingPlugins = new List(); List missingGuids = new List(); bool found = false; foreach (Plugin info in requiredPlugins) { foreach (Guid cachedPlugin in cachedPluginsGuids) { if (info.Id == cachedPlugin) { localPlugins.Add(new Plugin() { Id = cachedPlugin, Name = info.Name, Version = info.Version }); found = true; break; } } if (!found) { SlaveClientCom.Instance.ClientCom.LogMessage("Plugin NOT found " + info.Name + ", " + info.Version); missingPlugins.Add(info); missingGuids.Add(info.Id); } found = false; } SlaveClientCom.Instance.ClientCom.LogMessage("First run - Update the plugins in the cache"); localPlugins.AddRange(missingPlugins); IEnumerable pluginDatas = WcfService.Instance.GetPluginDatas(missingGuids); foreach (PluginData pluginData in pluginDatas) { string pluginDir = Path.Combine(PluginCacheDir, pluginData.PluginId.ToString()); //put all files belonging to a plugin in the same directory if (!Directory.Exists(pluginDir)) { DirectoryInfo di = Directory.CreateDirectory(pluginDir); } File.WriteAllBytes(Path.Combine(pluginDir, Path.GetFileName(pluginData.FileName)), pluginData.Data); } DoUpdateRun(); CopyPluginsForJob(requiredPlugins, myJob.Id, out configFileName); } } /// /// Returns a list of files which belong to a plugin from the plugincache /// private IEnumerable GetPluginFilePaths(Guid pluginId) { string pluginPath = Path.Combine(PluginCacheDir, pluginId.ToString()); if (Directory.Exists(pluginPath)) { WriteDateLastUsed(pluginPath); foreach (string filePath in Directory.GetFiles(pluginPath)) { string fn = Path.GetFileName(filePath); if (fn != lastUsedFileName) yield return filePath; } } } /// /// creates a file in path with the current date; /// this can later be used to find plugins which are outdated /// private void WriteDateLastUsed(string path) { FileStream fs = new FileStream(Path.Combine(path, lastUsedFileName), FileMode.Create); BinaryFormatter formatter = new BinaryFormatter(); try { formatter.Serialize(fs, DateTime.Now); } catch (SerializationException) { //rethrow... throw; } finally { fs.Close(); } } /// /// checks the pluginCacheDirectory and deletes plugin folders which are not used anymore /// /// private void CleanPluginCache() { FileStream fs = null; DateTime luDate; bool changed = false; if (Directory.Exists(PluginCacheDir)) { foreach (string dir in Directory.EnumerateDirectories(PluginCacheDir)) { try { fs = new FileStream(Path.Combine(dir, lastUsedFileName), FileMode.Open); BinaryFormatter formatter = new BinaryFormatter(); luDate = (DateTime)formatter.Deserialize(fs); fs.Close(); if (luDate.AddDays(maxAge) < DateTime.Now) { Directory.Delete(dir, true); changed = true; } } catch (SerializationException) { fs.Close(); throw; } catch (System.IO.FileNotFoundException) { //nerver used Directory.Delete(dir, true); changed = true; } catch (Exception) { throw; } } } if (changed) DoUpdateRun(); } internal void DeletePluginsForJob(Guid id) { try { SlaveClientCom.Instance.ClientCom.LogMessage("unloading..."); int tries = 5; while (tries > 0) { try { Directory.Delete(Path.Combine(PluginTempBaseDir, id.ToString()), true); tries = 0; } catch (Exception e) { Thread.Sleep(1000); tries--; if (tries == 0) throw;// TODO: don't know what do do } } } catch (Exception ex) { SlaveClientCom.Instance.ClientCom.LogMessage("failed while unloading " + id + " with exception " + ex); } CleanPluginCache(); } } }