#region License Information /* HeuristicLab * Copyright (C) 2002-2008 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.Runtime.Serialization.Formatters.Binary; using System.Threading; using System.Transactions; using HeuristicLab.Hive.Contracts; using HeuristicLab.Hive.Contracts.BusinessObjects; using HeuristicLab.Hive.Contracts.Interfaces; using HeuristicLab.Hive.Server.Core.InternalInterfaces; using HeuristicLab.PluginInfrastructure; using HeuristicLab.Tracing; using HeuristicLab.Hive.Contracts.ResponseObjects; using System.Security.Permissions; using System.Web.Security; using System.Security.Principal; using System.ServiceModel; using System.Security; namespace HeuristicLab.Hive.Server.Core { /// /// The SlaveCommunicator manages the whole communication with the slave /// public class SlaveCommunicator : ISlaveCommunicator, IInternalSlaveCommunicator { private static Dictionary lastHeartbeats = new Dictionary(); private static Dictionary newAssignedJobs = new Dictionary(); private static Dictionary pendingJobs = new Dictionary(); private static ReaderWriterLockSlim heartbeatLock = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion); //private ISessionFactory factory; private ILifecycleManager lifecycleManager; private IInternalJobManager jobManager; private IScheduler scheduler; private static int PENDING_TIMEOUT = 100; /// /// Initialization of the Adapters to the database /// Initialization of Eventhandler for the lifecycle management /// Initialization of lastHearbeats Dictionary /// public SlaveCommunicator() { //factory = ServiceLocator.GetSessionFactory(); lifecycleManager = ServiceLocator.GetLifecycleManager(); jobManager = ServiceLocator.GetJobManager() as IInternalJobManager; scheduler = ServiceLocator.GetScheduler(); lifecycleManager.RegisterHeartbeat(new EventHandler(lifecycleManager_OnServerHeartbeat)); } /// /// Check if online slaves send their hearbeats /// if not -> set them offline and check if they where calculating a job /// /// /// void lifecycleManager_OnServerHeartbeat(object sender, EventArgs e) { Logger.Debug("Server Heartbeat ticked"); // [chn] why is transaction management done here using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = ApplicationConstants.ISOLATION_LEVEL_SCOPE })) { List allSlaves = new List(DaoLocator.SlaveDao.FindAll()); foreach (SlaveDto slave in allSlaves) { if (slave.State != SlaveState.Offline && slave.State != SlaveState.NullState) { heartbeatLock.EnterUpgradeableReadLock(); if (!lastHeartbeats.ContainsKey(slave.Id)) { Logger.Info("Slave " + slave.Id + " wasn't offline but hasn't sent heartbeats - setting offline"); slave.State = SlaveState.Offline; DaoLocator.SlaveDao.Update(slave); Logger.Info("Slave " + slave.Id + " wasn't offline but hasn't sent heartbeats - Resetting all his jobs"); foreach (JobDto job in DaoLocator.JobDao.FindActiveJobsOfSlave(slave)) { //maybe implementa n additional Watchdog? Till then, just set them offline.. DaoLocator.JobDao.SetJobOffline(job); } } else { DateTime lastHbOfSlave = lastHeartbeats[slave.Id]; TimeSpan dif = DateTime.Now.Subtract(lastHbOfSlave); // check if time between last hearbeat and now is greather than HEARTBEAT_MAX_DIF if (dif.TotalSeconds > ApplicationConstants.HEARTBEAT_MAX_DIF) { // if slave calculated jobs, the job must be reset Logger.Info("Slave timed out and is on RESET"); foreach (JobDto job in DaoLocator.JobDao.FindActiveJobsOfSlave(slave)) { DaoLocator.JobDao.SetJobOffline(job); lock (newAssignedJobs) { if (newAssignedJobs.ContainsKey(job.Id)) newAssignedJobs.Remove(job.Id); } } Logger.Debug("setting slave offline"); // slave must be set offline slave.State = SlaveState.Offline; //slaveAdapter.Update(slave); DaoLocator.SlaveDao.Update(slave); Logger.Debug("removing it from the heartbeats list"); heartbeatLock.EnterWriteLock(); lastHeartbeats.Remove(slave.Id); heartbeatLock.ExitWriteLock(); } } heartbeatLock.ExitUpgradeableReadLock(); } else { //TODO: RLY neccesary? //HiveLogger.Info(this.ToString() + ": Slave " + slave.Id + " has wrong state: Shouldn't have offline or nullstate, has " + slave.State); heartbeatLock.EnterWriteLock(); //HiveLogger.Info(this.ToString() + ": Slave " + slave.Id + " has wrong state: Resetting all his jobs"); if (lastHeartbeats.ContainsKey(slave.Id)) lastHeartbeats.Remove(slave.Id); foreach (JobDto job in DaoLocator.JobDao.FindActiveJobsOfSlave(slave)) { DaoLocator.JobDao.SetJobOffline(job); } heartbeatLock.ExitWriteLock(); } } CheckForPendingJobs(); // DaoLocator.DestroyContext(); scope.Complete(); } } private void CheckForPendingJobs() { IList pendingJobsInDB = new List(DaoLocator.JobDao.GetJobsByState(JobState.Pending)); foreach (JobDto currJob in pendingJobsInDB) { lock (pendingJobs) { if (pendingJobs.ContainsKey(currJob.Id)) { if (pendingJobs[currJob.Id] <= 0) { currJob.State = JobState.Offline; DaoLocator.JobDao.Update(currJob); } else { pendingJobs[currJob.Id]--; } } } } } #region ISlaveCommunicator Members /// /// Login process for the slave /// A hearbeat entry is created as well (login is the first hearbeat) /// /// /// public Response Login(SlaveDto slave) { Response response = new Response(); heartbeatLock.EnterWriteLock(); if (lastHeartbeats.ContainsKey(slave.Id)) { lastHeartbeats[slave.Id] = DateTime.Now; } else { lastHeartbeats.Add(slave.Id, DateTime.Now); } heartbeatLock.ExitWriteLock(); SlaveDto dbSlave = DaoLocator.SlaveDao.FindById(slave.Id); slave.CalendarSyncStatus = dbSlave != null ? dbSlave.CalendarSyncStatus : CalendarState.NotAllowedToFetch; slave.State = SlaveState.Idle; if (dbSlave == null) DaoLocator.SlaveDao.Insert(slave); else { DaoLocator.SlaveDao.Update(slave); } return response; } public ResponseCalendar GetCalendar(Guid slaveId) { ResponseCalendar response = new ResponseCalendar(); SlaveDto slave = DaoLocator.SlaveDao.FindById(slaveId); if (slave == null) { //response.Success = false; response.StatusMessage = ResponseStatus.GetCalendar_ResourceNotFound; return response; } response.ForceFetch = (slave.CalendarSyncStatus == CalendarState.ForceFetch); IEnumerable appointments = DaoLocator.UptimeCalendarDao.GetCalendarForSlave(slave); if (appointments.Count() == 0) { response.StatusMessage = ResponseStatus.GetCalendar_NoCalendarFound; //response.Success = false; } else { //response.Success = true; response.Appointments = appointments; } slave.CalendarSyncStatus = CalendarState.Fetched; DaoLocator.SlaveDao.Update(slave); return response; } public Response SetCalendarStatus(Guid slaveId, CalendarState state) { Response response = new Response(); SlaveDto slave = DaoLocator.SlaveDao.FindById(slaveId); if (slave == null) { //response.Success = false; response.StatusMessage = ResponseStatus.GetCalendar_ResourceNotFound; return response; } slave.CalendarSyncStatus = state; DaoLocator.SlaveDao.Update(slave); return response; } /// /// The slave has to send regulary heartbeats /// this hearbeats will be stored in the heartbeats dictionary /// check if there is work for the slave and send the slave a response if he should pull a job /// /// /// public ResponseHeartBeat ProcessHeartBeat(HeartBeatData heartbeatData) { Logger.Debug("BEGIN Processing Heartbeat for Slave " + heartbeatData.SlaveId); ResponseHeartBeat response = new ResponseHeartBeat(); response.ActionRequest = new List(); Logger.Debug("BEGIN Started Slave Fetching"); SlaveDto slave = DaoLocator.SlaveDao.FindById(heartbeatData.SlaveId); Logger.Debug("END Finished Slave Fetching"); slave.NrOfFreeCores = heartbeatData.FreeCores; slave.FreeMemory = heartbeatData.FreeMemory; slave.IsAllowedToCalculate = heartbeatData.IsAllowedToCalculate; // check if the slave is logged in if (slave.State == SlaveState.Offline || slave.State == SlaveState.NullState) { response.StatusMessage = ResponseStatus.ProcessHeartBeat_UserNotLoggedIn; response.ActionRequest.Add(new MessageContainer(MessageContainer.MessageType.NoMessage)); Logger.Error("ProcessHeartBeat: Slave state null or offline: " + slave); return response; } // save timestamp of this heartbeat Logger.Debug("BEGIN Locking for Heartbeats"); heartbeatLock.EnterWriteLock(); Logger.Debug("END Locked for Heartbeats"); if (lastHeartbeats.ContainsKey(heartbeatData.SlaveId)) { lastHeartbeats[heartbeatData.SlaveId] = DateTime.Now; } else { lastHeartbeats.Add(heartbeatData.SlaveId, DateTime.Now); } heartbeatLock.ExitWriteLock(); Logger.Debug("BEGIN Processing Heartbeat Jobs"); ProcessJobProcess(heartbeatData, response); Logger.Debug("END Processed Heartbeat Jobs"); //check if new Cal must be loaded if (slave.CalendarSyncStatus == CalendarState.Fetch || slave.CalendarSyncStatus == CalendarState.ForceFetch) { response.ActionRequest.Add(new MessageContainer(MessageContainer.MessageType.FetchOrForceFetchCalendar)); //slave.CalendarSyncStatus = CalendarState.Fetching; Logger.Info("fetch or forcefetch sent"); } // check if slave has a free core for a new job // if true, ask scheduler for a new job for this slave Logger.Debug(" BEGIN Looking for Slave Jobs"); if (slave.IsAllowedToCalculate && heartbeatData.FreeCores > 0 && scheduler.ExistsJobForSlave(heartbeatData)) { response.ActionRequest.Add(new MessageContainer(MessageContainer.MessageType.FetchJob)); } else { response.ActionRequest.Add(new MessageContainer(MessageContainer.MessageType.NoMessage)); } Logger.Debug(" END Looked for Slave Jobs"); DaoLocator.SlaveDao.Update(slave); //tx.Commit(); Logger.Debug(" END Processed Heartbeat for Slave " + heartbeatData.SlaveId); return response; } /// /// Process the Job progress sent by a slave /// [chn] this method needs to be refactored, because its a performance hog /// /// what it does: /// (1) find out if the jobs that should be calculated by this slave (from db) and compare if they are consistent with what the joblist the slave sent /// (2) find out if every job from the joblist really should be calculated by this slave /// (3) checks if a job should be aborted and issues Message /// (4) update job-progress and write to db /// (5) if snapshot is requested, issue Message /// /// (6) for each job from DB, check if there is a job from slave (again). /// (7) if job matches, it is removed from newAssigneJobs /// (8) if job !matches, job's TTL is reduced by 1, /// (9) if TTL==0, job is set to Abort (save to DB), and Message to Abort job is issued to slave /// /// /// /// quirks: /// (1) the response-object is modified during the foreach-loop (only last element counts) /// (2) state Abort results in Finished. This should be: AbortRequested, Aborted. /// /// /// /// /// private void ProcessJobProcess(HeartBeatData hbData, ResponseHeartBeat response) { Logger.Debug("Started for Slave " + hbData.SlaveId); List jobsOfSlave = new List(DaoLocator.JobDao.FindActiveJobsOfSlave(DaoLocator.SlaveDao.FindById(hbData.SlaveId))); if (hbData.JobProgress != null && hbData.JobProgress.Count > 0) { if (jobsOfSlave == null || jobsOfSlave.Count == 0) { foreach (Guid jobId in hbData.JobProgress.Keys) { response.ActionRequest.Add(new MessageContainer(MessageContainer.MessageType.AbortJob, jobId)); } Logger.Error("There is no job calculated by this user " + hbData.SlaveId + ", advise him to abort all"); return; } foreach (KeyValuePair jobProgress in hbData.JobProgress) { JobDto curJob = DaoLocator.JobDao.FindById(jobProgress.Key); if (curJob == null) { response.StatusMessage = ResponseStatus.ProcessJobResult_JobDoesNotExist; response.ActionRequest.Add(new MessageContainer(MessageContainer.MessageType.AbortJob, jobProgress.Key)); Logger.Error("Job does not exist in DB: " + jobProgress.Key); return; } curJob.Slave = DaoLocator.SlaveDao.GetSlaveForJob(curJob.Id); if (curJob.Slave == null || curJob.Slave.Id != hbData.SlaveId) { response.ActionRequest.Add(new MessageContainer(MessageContainer.MessageType.AbortJob, curJob.Id)); Logger.Error("There is no job calculated by this user " + hbData.SlaveId + " Job: " + curJob); } else if (curJob.State == JobState.Aborted) { // a request to abort the job has been set response.ActionRequest.Add(new MessageContainer(MessageContainer.MessageType.AbortJob, curJob.Id)); curJob.State = JobState.Finished; } else { // save job progress curJob.Percentage = jobProgress.Value; if (curJob.State == JobState.SnapshotRequested) { // a request for a snapshot has been set response.ActionRequest.Add(new MessageContainer(MessageContainer.MessageType.RequestSnapshot, curJob.Id)); curJob.State = JobState.SnapshotSent; } } DaoLocator.JobDao.Update(curJob); } } foreach (JobDto currJob in jobsOfSlave) { bool found = false; if (hbData.JobProgress != null) { foreach (Guid jobId in hbData.JobProgress.Keys) { if (jobId == currJob.Id) { found = true; break; } } } if (!found) { lock (newAssignedJobs) { if (newAssignedJobs.ContainsKey(currJob.Id)) { newAssignedJobs[currJob.Id]--; Logger.Error("Job TTL Reduced by one for job: " + currJob + "and is now: " + newAssignedJobs[currJob.Id] + ". User that sucks: " + currJob.Slave); if (newAssignedJobs[currJob.Id] <= 0) { Logger.Error("Job TTL reached Zero, Job gets removed: " + currJob + " and set back to offline. User that sucks: " + currJob.Slave); currJob.State = JobState.Offline; DaoLocator.JobDao.Update(currJob); response.ActionRequest.Add(new MessageContainer(MessageContainer.MessageType.AbortJob, currJob.Id)); newAssignedJobs.Remove(currJob.Id); } } else { Logger.Error("Job ID wasn't with the heartbeats: " + currJob); currJob.State = JobState.Offline; DaoLocator.JobDao.Update(currJob); } } // lock } else { lock (newAssignedJobs) { if (newAssignedJobs.ContainsKey(currJob.Id)) { Logger.Info("Job is sending a heart beat, removing it from the newAssignedJobList: " + currJob); newAssignedJobs.Remove(currJob.Id); } } } } } /// /// if the slave was told to pull a job he calls this method /// the server selects a job and sends it to the slave /// /// /// public ResponseObject GetJob(Guid slaveId) { ResponseObject response = new ResponseObject(); JobDto job2Calculate = scheduler.GetNextJobForSlave(slaveId); if (job2Calculate != null) { response.Obj = job2Calculate; response.Obj.PluginsNeeded = DaoLocator.PluginInfoDao.GetPluginDependenciesForJob(response.Obj); Logger.Info("Job pulled: " + job2Calculate + " for user " + slaveId); lock (newAssignedJobs) { if (!newAssignedJobs.ContainsKey(job2Calculate.Id)) newAssignedJobs.Add(job2Calculate.Id, ApplicationConstants.JOB_TIME_TO_LIVE); } } else { //response.Success = false; response.Obj = null; response.StatusMessage = ResponseStatus.GetJob_NoJobsAvailable; Logger.Info("No more Jobs left for " + slaveId); } return response; } public ResponseResultReceived ProcessJobResult(Stream stream, bool finished) { Logger.Info("BEGIN Job received for Storage - main method:"); //Stream jobResultStream = null; //Stream jobStream = null; //try { BinaryFormatter formatter = new BinaryFormatter(); JobResult result = (JobResult)formatter.Deserialize(stream); //important - repeatable read isolation level is required here, //otherwise race conditions could occur when writing the stream into the DB //just removed TransactionIsolationLevel.RepeatableRead //tx = session.BeginTransaction(); ResponseResultReceived response = ProcessJobResult(result.SlaveId, result.JobId, new byte[] { }, result.Percentage, result.Exception, finished); if (response.StatusMessage == ResponseStatus.Ok) { Logger.Debug("Trying to aquire WCF Job Stream"); //jobStream = DaoLocator.JobDao.GetSerializedJobStream(result.JobId); //Logger.Debug("Job Stream Aquired"); byte[] buffer = new byte[3024]; List serializedJob = new List(); int read = 0; int i = 0; while ((read = stream.Read(buffer, 0, buffer.Length)) > 0) { for (int j = 0; j < read; j++) { serializedJob.Add(buffer[j]); } if (i % 100 == 0) Logger.Debug("Writing to stream: " + i); //jobStream.Write(buffer, 0, read); i++; } Logger.Debug("Done Writing, closing the stream!"); //jobStream.Close(); DaoLocator.JobDao.SetBinaryJobFile(result.JobId, serializedJob.ToArray()); } Logger.Info("END Job received for Storage:"); stream.Dispose(); return response; } private ResponseResultReceived ProcessJobResult(Guid slaveId, Guid jobId, byte[] result, double? percentage, string exception, bool finished) { Logger.Info("BEGIN Job received for Storage - SUB method: " + jobId); ResponseResultReceived response = new ResponseResultReceived(); SlaveDto slave = DaoLocator.SlaveDao.FindById(slaveId); SerializedJob job = new SerializedJob(); if (job != null) { job.JobInfo = DaoLocator.JobDao.FindById(jobId); if (job.JobInfo != null) { job.JobInfo.Slave = job.JobInfo.Slave = DaoLocator.SlaveDao.GetSlaveForJob(jobId); } } if (job != null && job.JobInfo == null) { //response.Success = false; response.StatusMessage = ResponseStatus.ProcessJobResult_JobDoesNotExist; response.JobId = jobId; Logger.Error("No job with Id " + jobId); //tx.Rollback(); return response; } if (job.JobInfo.State == JobState.Aborted) { //response.Success = false; response.StatusMessage = ResponseStatus.ProcessJobResult_JobAborted; Logger.Error("Job was aborted! " + job.JobInfo); //tx.Rollback(); return response; } if (job.JobInfo.Slave == null) { //response.Success = false; response.StatusMessage = ResponseStatus.ProcessJobResult_JobIsNotBeeingCalculated; response.JobId = jobId; Logger.Error("Job is not being calculated (slave = null)! " + job.JobInfo); //tx.Rollback(); return response; } if (job.JobInfo.Slave.Id != slaveId) { //response.Success = false; response.StatusMessage = ResponseStatus.ProcessJobResult_WrongSlaveForJob; response.JobId = jobId; Logger.Error("Wrong Slave for this Job! " + job.JobInfo + ", Sending Slave is: " + slaveId); //tx.Rollback(); return response; } if (job.JobInfo.State == JobState.Finished) { response.StatusMessage = ResponseStatus.Ok; response.JobId = jobId; Logger.Error("Job already finished! " + job.JobInfo + ", Sending Slave is: " + slaveId); //tx.Rollback(); return response; } //Todo: RequestsnapshotSent => calculating? if (job.JobInfo.State == JobState.SnapshotSent) { job.JobInfo.State = JobState.Calculating; } if (job.JobInfo.State != JobState.Calculating && job.JobInfo.State != JobState.Pending) { //response.Success = false; response.StatusMessage = ResponseStatus.ProcessJobResult_InvalidJobState; response.JobId = jobId; Logger.Error("Wrong Job State, job is: " + job.JobInfo); //tx.Rollback(); return response; } job.JobInfo.Percentage = percentage; if (!string.IsNullOrEmpty(exception)) { job.JobInfo.State = JobState.Failed; job.JobInfo.Exception = exception; job.JobInfo.DateFinished = DateTime.Now; } else if (finished) { job.JobInfo.State = JobState.Finished; job.JobInfo.DateFinished = DateTime.Now; } job.SerializedJobData = result; DaoLocator.JobDao.Update(job.JobInfo); response.StatusMessage = ResponseStatus.Ok; response.JobId = jobId; response.Finished = finished; Logger.Info("END Job received for Storage - SUB method: " + jobId); return response; } /// /// the slave can send job results during calculating /// and will send a final job result when he finished calculating /// these job results will be stored in the database /// /// /// /// /// /// /// public ResponseResultReceived StoreFinishedJobResult(Guid slaveId, Guid jobId, byte[] result, double percentage, string exception) { return ProcessJobResult(slaveId, jobId, result, percentage, exception, true); } public ResponseResultReceived ProcessSnapshot(Guid slaveId, Guid jobId, byte[] result, double percentage, string exception) { return ProcessJobResult(slaveId, jobId, result, percentage, exception, false); } /// /// when a slave logs out the state will be set /// and the entry in the last hearbeats dictionary will be removed /// /// /// public Response Logout(Guid slaveId) { Logger.Info("Slave logged out " + slaveId); Response response = new Response(); heartbeatLock.EnterWriteLock(); if (lastHeartbeats.ContainsKey(slaveId)) lastHeartbeats.Remove(slaveId); heartbeatLock.ExitWriteLock(); SlaveDto slave = DaoLocator.SlaveDao.FindById(slaveId); if (slave == null) { //response.Success = false; response.StatusMessage = ResponseStatus.Logout_SlaveNotRegistered; return response; } if (slave.State == SlaveState.Calculating) { // check wich job the slave was calculating and reset it IEnumerable jobsOfSlave = DaoLocator.JobDao.FindActiveJobsOfSlave(slave); foreach (JobDto job in jobsOfSlave) { if (job.State != JobState.Finished) DaoLocator.JobDao.SetJobOffline(job); } } slave.State = SlaveState.Offline; DaoLocator.SlaveDao.Update(slave); return response; } /// /// If a slave goes offline and restores a job he was calculating /// he can ask the slave if he still needs the job result /// /// /// public Response IsJobStillNeeded(Guid jobId) { Response response = new Response(); JobDto job = DaoLocator.JobDao.FindById(jobId); if (job == null) { //response.Success = false; response.StatusMessage = ResponseStatus.IsJobStillNeeded_JobDoesNotExist; Logger.Error("Job doesn't exist (anymore)! " + jobId); return response; } if (job.State == JobState.Finished) { //response.Success = true; response.StatusMessage = ResponseStatus.IsJobStillNeeded_JobAlreadyFinished; Logger.Error("already finished! " + job); return response; } job.State = JobState.Pending; lock (pendingJobs) { pendingJobs.Add(job.Id, PENDING_TIMEOUT); } DaoLocator.JobDao.Update(job); return response; } public ResponseList GetPlugins(List pluginList) { ResponseList response = new ResponseList(); response.List = new List(); foreach (HivePluginInfoDto pluginInfo in pluginList) { if (pluginInfo.Update) { //check if there is a newer version IPluginDescription ipd = ApplicationManager.Manager.Plugins.Where(pd => pd.Name == pluginInfo.Name && pd.Version.Major == pluginInfo.Version.Major && pd.Version.Minor == pluginInfo.Version.Minor && pd.Version.Revision > pluginInfo.Version.Revision).SingleOrDefault(); if (ipd != null) { response.List.Add(ConvertPluginDescriptorToDto(ipd)); } } else { IPluginDescription ipd = ApplicationManager.Manager.Plugins.Where(pd => pd.Name == pluginInfo.Name && pd.Version.Major == pluginInfo.Version.Major && pd.Version.Minor == pluginInfo.Version.Minor && pd.Version.Revision >= pluginInfo.Version.Revision).SingleOrDefault(); if (ipd != null) { response.List.Add(ConvertPluginDescriptorToDto(ipd)); } else { //response.Success = false; response.StatusMessage = ResponseStatus.GetPlugins_PluginsNotAvailable; return response; } } } return response; } private CachedHivePluginInfoDto ConvertPluginDescriptorToDto(IPluginDescription currPlugin) { CachedHivePluginInfoDto currCachedPlugin = new CachedHivePluginInfoDto { Name = currPlugin.Name, Version = currPlugin.Version }; foreach (string fileName in from file in currPlugin.Files select file.Name) { currCachedPlugin.PluginFiles.Add(new HivePluginFile(File.ReadAllBytes(fileName), fileName)); } return currCachedPlugin; } #endregion } }