Free cookie consent management tool by TermsFeed Policy Generator

source: branches/HeuristicLab.Hive-3.4/sources/HeuristicLab.Clients.Hive/3.3/HiveClient.cs @ 6743

Last change on this file since 6743 was 6743, checked in by ascheibe, 13 years ago

#1233

  • fixed a bug in the Slave UI
  • finished renaming Webservice and Dao methods to be consistent with Job/Task naming
  • some cosmetic changes and project dependencies cleanups
File size: 21.5 KB
RevLine 
[6373]1#region License Information
2/* HeuristicLab
3 * Copyright (C) 2002-2011 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
4 *
5 * This file is part of HeuristicLab.
6 *
7 * HeuristicLab is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * HeuristicLab is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
19 */
20#endregion
21
22using System;
23using System.Collections.Generic;
24using System.Configuration;
25using System.IO;
26using System.Linq;
[6407]27using System.Security.Cryptography;
[6373]28using System.Threading;
[6444]29using System.Threading.Tasks;
[6373]30using HeuristicLab.Common;
31using HeuristicLab.Core;
32using HeuristicLab.PluginInfrastructure;
[6721]33using TS = System.Threading.Tasks;
[6373]34
35namespace HeuristicLab.Clients.Hive {
36  [Item("HiveClient", "Hive client.")]
37  public sealed class HiveClient : IContent {
38    private static HiveClient instance;
39    public static HiveClient Instance {
40      get {
41        if (instance == null) instance = new HiveClient();
42        return instance;
43      }
44    }
45
46    #region Properties
[6725]47    private ItemCollection<RefreshableJob> jobs;
48    public ItemCollection<RefreshableJob> Jobs {
49      get { return jobs; }
[6373]50      set {
[6725]51        if (value != jobs) {
52          jobs = value;
[6373]53          OnHiveExperimentsChanged();
54        }
55      }
56    }
57
58    private List<Plugin> onlinePlugins;
59    public List<Plugin> OnlinePlugins {
60      get { return onlinePlugins; }
61      set { onlinePlugins = value; }
62    }
63
64    private List<Plugin> alreadyUploadedPlugins;
65    public List<Plugin> AlreadyUploadedPlugins {
66      get { return alreadyUploadedPlugins; }
67      set { alreadyUploadedPlugins = value; }
68    }
[6479]69
70    private bool isAllowedPrivileged;
71    public bool IsAllowedPrivileged {
72      get { return isAllowedPrivileged; }
73      set { isAllowedPrivileged = value; }
74    }
[6373]75    #endregion
76
77    public HiveClient() { }
78
79    #region Refresh
80    public void Refresh() {
81      OnRefreshing();
82
83      try {
[6479]84        this.IsAllowedPrivileged = ServiceLocator.Instance.CallHiveService((s) => s.IsAllowedPrivileged());
85
[6725]86        var oldExperiments = jobs ?? new ItemCollection<RefreshableJob>();
87        jobs = new HiveItemCollection<RefreshableJob>();
[6743]88        var experimentsLoaded = ServiceLocator.Instance.CallHiveService<IEnumerable<Job>>(s => s.GetJobs());
[6479]89
90        foreach (var he in experimentsLoaded) {
[6743]91          var job = oldExperiments.SingleOrDefault(x => x.Id == he.Id);
92          if (job == null) {
[6479]93            // new
[6725]94            jobs.Add(new RefreshableJob(he) { IsAllowedPrivileged = this.isAllowedPrivileged });
[6479]95          } else {
96            // update
[6743]97            job.Job = he;
98            job.IsAllowedPrivileged = this.isAllowedPrivileged;
99            jobs.Add(job);
[6479]100          }
101        }
102        // remove those which were not in the list of loaded hiveexperiments
103        foreach (var experiment in oldExperiments) {
104          if (experiment.Id == Guid.Empty) {
105            // experiment not uploaded... keep
[6725]106            jobs.Add(experiment);
[6479]107          } else {
108            experiment.RefreshAutomatically = false; // stop results polling
109          }
110        }
[6373]111      }
112      catch {
[6725]113        jobs = null;
[6373]114        throw;
115      }
116      finally {
117        OnRefreshed();
118      }
119    }
120    public void RefreshAsync(Action<Exception> exceptionCallback) {
121      var call = new Func<Exception>(delegate() {
122        try {
123          Refresh();
124        }
125        catch (Exception ex) {
126          return ex;
127        }
128        return null;
129      });
130      call.BeginInvoke(delegate(IAsyncResult result) {
131        Exception ex = call.EndInvoke(result);
132        if (ex != null) exceptionCallback(ex);
133      }, null);
134    }
135    #endregion
136
137    #region Store
[6444]138    public static void Store(IHiveItem item, CancellationToken cancellationToken) {
[6373]139      if (item.Id == Guid.Empty) {
[6725]140        if (item is RefreshableJob) {
[6743]141          HiveClient.Instance.UploadJob((RefreshableJob)item, cancellationToken);
[6373]142        }
[6723]143        if (item is JobPermission) {
144          var hep = (JobPermission)item;
[6463]145          hep.GrantedUserId = ServiceLocator.Instance.CallHiveService((s) => s.GetUserIdByUsername(hep.GrantedUserName));
146          if (hep.GrantedUserId == Guid.Empty) {
147            throw new ArgumentException(string.Format("The user {0} was not found.", hep.GrantedUserName));
148          }
[6723]149          ServiceLocator.Instance.CallHiveService((s) => s.GrantPermission(hep.JobId, hep.GrantedUserId, hep.Permission));
[6463]150        }
[6373]151      } else {
[6723]152        if (item is Job)
[6743]153          ServiceLocator.Instance.CallHiveService(s => s.UpdateJob((Job)item));
[6373]154      }
155    }
[6444]156    public static void StoreAsync(Action<Exception> exceptionCallback, IHiveItem item, CancellationToken cancellationToken) {
[6373]157      var call = new Func<Exception>(delegate() {
158        try {
[6444]159          Store(item, cancellationToken);
[6373]160        }
161        catch (Exception ex) {
162          return ex;
163        }
164        return null;
165      });
166      call.BeginInvoke(delegate(IAsyncResult result) {
167        Exception ex = call.EndInvoke(result);
168        if (ex != null) exceptionCallback(ex);
169      }, null);
170    }
171    #endregion
172
173    #region Delete
174    public static void Delete(IHiveItem item) {
[6483]175      if (item.Id == Guid.Empty)
176        return;
177
[6723]178      if (item is Job)
[6743]179        ServiceLocator.Instance.CallHiveService(s => s.DeleteJob(item.Id));
[6725]180      if (item is RefreshableJob)
[6743]181        ServiceLocator.Instance.CallHiveService(s => s.DeleteJob(item.Id));
[6723]182      if (item is JobPermission) {
183        var hep = (JobPermission)item;
184        ServiceLocator.Instance.CallHiveService(s => s.RevokePermission(hep.JobId, hep.GrantedUserId));
[6463]185      }
[6373]186      item.Id = Guid.Empty;
187    }
188    #endregion
189
190    #region Events
191    public event EventHandler Refreshing;
192    private void OnRefreshing() {
193      EventHandler handler = Refreshing;
194      if (handler != null) handler(this, EventArgs.Empty);
195    }
196    public event EventHandler Refreshed;
197    private void OnRefreshed() {
198      var handler = Refreshed;
199      if (handler != null) handler(this, EventArgs.Empty);
200    }
201    public event EventHandler HiveExperimentsChanged;
202    private void OnHiveExperimentsChanged() {
203      var handler = HiveExperimentsChanged;
204      if (handler != null) handler(this, EventArgs.Empty);
205    }
206    #endregion
207
[6743]208    public static void StartJob(Action<Exception> exceptionCallback, RefreshableJob refreshableJob, CancellationToken cancellationToken) {
[6373]209      HiveClient.StoreAsync(
210        new Action<Exception>((Exception ex) => {
[6725]211          refreshableJob.ExecutionState = ExecutionState.Prepared;
[6373]212          exceptionCallback(ex);
[6725]213        }), refreshableJob, cancellationToken);
214      refreshableJob.ExecutionState = ExecutionState.Started;
[6373]215    }
216
[6743]217    public static void PauseJob(RefreshableJob refreshableJob) {
[6373]218      ServiceLocator.Instance.CallHiveService(service => {
[6743]219        foreach (HiveTask task in refreshableJob.GetAllHiveTasks()) {
220          if (task.Task.State != TaskState.Finished && task.Task.State != TaskState.Aborted && task.Task.State != TaskState.Failed)
221            service.PauseTask(task.Task.Id);
[6373]222        }
223      });
[6743]224      refreshableJob.ExecutionState = ExecutionState.Paused;
[6373]225    }
226
[6743]227    public static void StopJob(RefreshableJob refreshableJob) {
[6373]228      ServiceLocator.Instance.CallHiveService(service => {
[6743]229        foreach (HiveTask task in refreshableJob.GetAllHiveTasks()) {
230          if (task.Task.State != TaskState.Finished && task.Task.State != TaskState.Aborted && task.Task.State != TaskState.Failed)
231            service.StopTask(task.Task.Id);
[6373]232        }
233      });
234      // execution state does not need to be set. it will be set to Stopped, when all jobs have been downloaded
235    }
236
[6743]237    #region Upload Job
[6444]238    private Semaphore jobUploadSemaphore = new Semaphore(4, 4); // todo: take magic number into config
239    private static object jobCountLocker = new object();
[6452]240    private static object pluginLocker = new object();
[6743]241    private void UploadJob(RefreshableJob refreshableJob, CancellationToken cancellationToken) {
[6373]242      try {
[6725]243        refreshableJob.Progress = new Progress("Connecting to server...");
244        refreshableJob.IsProgressing = true;
[6373]245
[6725]246        IEnumerable<string> resourceNames = ToResourceNameList(refreshableJob.Job.ResourceNames);
[6444]247        var resourceIds = new List<Guid>();
248        foreach (var resourceName in resourceNames) {
249          Guid resourceId = ServiceLocator.Instance.CallHiveService((s) => s.GetResourceId(resourceName));
250          if (resourceId == Guid.Empty) {
251            throw new ResourceNotFoundException(string.Format("Could not find the resource '{0}'", resourceName));
[6373]252          }
[6444]253          resourceIds.Add(resourceId);
254        }
[6373]255
[6743]256        foreach (OptimizerHiveTask hiveJob in refreshableJob.HiveTasks.OfType<OptimizerHiveTask>()) {
[6444]257          hiveJob.SetIndexInParentOptimizerList(null);
258        }
[6373]259
[6725]260        // upload Task
261        refreshableJob.Progress.Status = "Uploading Task...";
[6743]262        refreshableJob.Job.Id = ServiceLocator.Instance.CallHiveService((s) => s.AddJob(refreshableJob.Job));
[6725]263        bool isPrivileged = refreshableJob.Job.IsPrivileged;
[6743]264        refreshableJob.Job = ServiceLocator.Instance.CallHiveService((s) => s.GetJob(refreshableJob.Job.Id)); // update owner and permissions
[6725]265        refreshableJob.Job.IsPrivileged = isPrivileged;
[6444]266        cancellationToken.ThrowIfCancellationRequested();
[6373]267
[6743]268        int totalJobCount = refreshableJob.GetAllHiveTasks().Count();
[6444]269        int[] jobCount = new int[1]; // use a reference type (int-array) instead of value type (int) in order to pass the value via a delegate to task-parallel-library
270        cancellationToken.ThrowIfCancellationRequested();
[6373]271
[6444]272        // upload plugins
[6725]273        refreshableJob.Progress.Status = "Uploading plugins...";
[6444]274        this.OnlinePlugins = ServiceLocator.Instance.CallHiveService((s) => s.GetPlugins());
275        this.AlreadyUploadedPlugins = new List<Plugin>();
276        Plugin configFilePlugin = ServiceLocator.Instance.CallHiveService((s) => UploadConfigurationFile(s, onlinePlugins));
277        this.alreadyUploadedPlugins.Add(configFilePlugin);
278        cancellationToken.ThrowIfCancellationRequested();
[6373]279
[6725]280        if (refreshableJob.RefreshAutomatically) refreshableJob.StartResultPolling();
[6373]281
[6444]282        // upload jobs
[6725]283        refreshableJob.Progress.Status = "Uploading jobs...";
[6444]284
[6721]285        var tasks = new List<TS.Task>();
[6743]286        foreach (HiveTask hiveJob in refreshableJob.HiveTasks) {
[6721]287          tasks.Add(TS.Task.Factory.StartNew((hj) => {
[6743]288            UploadTaskWithChildren(refreshableJob.Progress, (HiveTask)hj, null, resourceIds, jobCount, totalJobCount, configFilePlugin.Id, refreshableJob.Job.Id, refreshableJob.Log, refreshableJob.Job.IsPrivileged, cancellationToken);
[6444]289          }, hiveJob)
[6725]290          .ContinueWith((x) => refreshableJob.Log.LogException(x.Exception), TaskContinuationOptions.OnlyOnFaulted));
[6444]291        }
[6463]292        try {
[6721]293          TS.Task.WaitAll(tasks.ToArray());
[6463]294        }
295        catch (AggregateException ae) {
296          if (!ae.InnerExceptions.All(e => e is TaskCanceledException)) throw ae; // for some reason the WaitAll throws a AggregateException containg a TaskCanceledException. i don't know where it comes from, however the tasks all finish properly, so for now just ignore it
297        }
[6725]298        refreshableJob.Job.Modified = false;
[6373]299      }
300      finally {
[6725]301        refreshableJob.IsProgressing = false;
[6373]302      }
303    }
304
305    /// <summary>
306    /// Uploads the local configuration file as plugin
307    /// </summary>
[6426]308    private static Plugin UploadConfigurationFile(IHiveService service, List<Plugin> onlinePlugins) {
[6373]309      string exeFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "HeuristicLab 3.3.exe");
310      string configFileName = Path.GetFileName(ConfigurationManager.OpenExeConfiguration(exeFilePath).FilePath);
311      string configFilePath = ConfigurationManager.OpenExeConfiguration(exeFilePath).FilePath;
[6407]312      byte[] hash;
[6373]313
[6407]314      byte[] data = File.ReadAllBytes(configFilePath);
315      using (SHA1 sha1 = SHA1.Create()) {
316        hash = sha1.ComputeHash(data);
317      }
318
[6426]319      Plugin configPlugin = new Plugin() { Name = "Configuration", Version = new Version(), Hash = hash };
[6407]320      PluginData configFile = new PluginData() { FileName = configFileName, Data = data };
[6426]321
322      IEnumerable<Plugin> onlineConfig = onlinePlugins.Where(p => p.Hash.SequenceEqual(hash));
323
324      if (onlineConfig.Count() > 0) {
325        return onlineConfig.First();
326      } else {
327        configPlugin.Id = service.AddPlugin(configPlugin, new List<PluginData> { configFile });
328        return configPlugin;
329      }
[6373]330    }
331
332    /// <summary>
[6725]333    /// Uploads the given task and all its child-jobs while setting the proper parentJobId values for the childs
[6373]334    /// </summary>
[6725]335    /// <param name="parentHiveTask">shall be null if its the root task</param>
[6743]336    private void UploadTaskWithChildren(IProgress progress, HiveTask hiveTask, HiveTask parentHiveJob, IEnumerable<Guid> groups, int[] taskCount, int totalJobCount, Guid configPluginId, Guid jobId, ILog log, bool isPrivileged, CancellationToken cancellationToken) {
[6444]337      jobUploadSemaphore.WaitOne();
[6463]338      bool semaphoreReleased = false;
[6444]339      try {
340        cancellationToken.ThrowIfCancellationRequested();
341        lock (jobCountLocker) {
[6743]342          taskCount[0]++;
[6444]343        }
[6721]344        TaskData jobData;
[6452]345        List<IPluginDescription> plugins;
[6373]346
[6743]347        if (hiveTask.ItemTask.ComputeInParallel && (hiveTask.ItemTask.Item is Optimization.Experiment || hiveTask.ItemTask.Item is Optimization.BatchRun)) {
348          hiveTask.Task.IsParentTask = true;
349          hiveTask.Task.FinishWhenChildJobsFinished = true;
350          jobData = hiveTask.GetAsTaskData(true, out plugins);
[6452]351        } else {
[6743]352          hiveTask.Task.IsParentTask = false;
353          hiveTask.Task.FinishWhenChildJobsFinished = false;
354          jobData = hiveTask.GetAsTaskData(false, out plugins);
[6452]355        }
[6444]356        cancellationToken.ThrowIfCancellationRequested();
[6373]357
[6444]358        TryAndRepeat(() => {
359          if (!cancellationToken.IsCancellationRequested) {
[6452]360            lock (pluginLocker) {
[6743]361              ServiceLocator.Instance.CallHiveService((s) => hiveTask.Task.PluginsNeededIds = PluginUtil.GetPluginDependencies(s, this.onlinePlugins, this.alreadyUploadedPlugins, plugins));
[6452]362            }
[6444]363          }
364        }, -1, "Failed to upload plugins");
365        cancellationToken.ThrowIfCancellationRequested();
[6743]366        hiveTask.Task.PluginsNeededIds.Add(configPluginId);
367        hiveTask.Task.JobId = jobId;
368        hiveTask.Task.IsPrivileged = isPrivileged;
[6373]369
[6743]370        log.LogMessage(string.Format("Uploading task ({0} kb, {1} objects)", jobData.Data.Count() / 1024, hiveTask.ItemTask.GetObjectGraphObjects().Count()));
[6444]371        TryAndRepeat(() => {
372          if (!cancellationToken.IsCancellationRequested) {
373            if (parentHiveJob != null) {
[6743]374              hiveTask.Task.Id = ServiceLocator.Instance.CallHiveService((s) => s.AddChildTask(parentHiveJob.Task.Id, hiveTask.Task, jobData));
[6444]375            } else {
[6743]376              hiveTask.Task.Id = ServiceLocator.Instance.CallHiveService((s) => s.AddTask(hiveTask.Task, jobData, groups.ToList()));
[6444]377            }
378          }
[6725]379        }, 50, "Failed to add task", log);
[6444]380        cancellationToken.ThrowIfCancellationRequested();
[6373]381
[6444]382        lock (jobCountLocker) {
[6743]383          progress.ProgressValue = (double)taskCount[0] / totalJobCount;
384          progress.Status = string.Format("Uploaded task ({0} of {1})", taskCount[0], totalJobCount);
[6373]385        }
386
[6721]387        var tasks = new List<TS.Task>();
[6743]388        foreach (HiveTask child in hiveTask.ChildHiveTasks) {
[6721]389          tasks.Add(TS.Task.Factory.StartNew((tuple) => {
[6725]390            var arguments = (Tuple<HiveTask, HiveTask>)tuple;
[6743]391            UploadTaskWithChildren(progress, arguments.Item1, arguments.Item2, groups, taskCount, totalJobCount, configPluginId, jobId, log, isPrivileged, cancellationToken);
392          }, new Tuple<HiveTask, HiveTask>(child, hiveTask))
[6444]393          .ContinueWith((x) => log.LogException(x.Exception), TaskContinuationOptions.OnlyOnFaulted));
394        }
[6463]395        jobUploadSemaphore.Release(); semaphoreReleased = true; // the semaphore has to be release before waitall!
396        try {
[6721]397          TS.Task.WaitAll(tasks.ToArray());
[6463]398        }
399        catch (AggregateException ae) {
400          if (!ae.InnerExceptions.All(e => e is TaskCanceledException)) throw ae; // for some reason the WaitAll throws a AggregateException containg a TaskCanceledException. i don't know where it comes from, however the tasks all finish properly, so for now just ignore it
401        }
[6373]402      }
[6444]403      finally {
[6479]404        if (!semaphoreReleased) jobUploadSemaphore.Release();
[6444]405      }
[6373]406    }
407    #endregion
408
409    #region Download Experiment
[6743]410    public static void LoadJob(RefreshableJob refreshableJob) {
[6725]411      var hiveExperiment = refreshableJob.Job;
412      refreshableJob.Progress = new Progress();
[6479]413
[6373]414      try {
[6725]415        refreshableJob.IsProgressing = true;
[6373]416        int totalJobCount = 0;
[6743]417        IEnumerable<LightweightTask> allTasks;
[6373]418
[6725]419        refreshableJob.Progress.Status = "Connecting to Server...";
420        // fetch all Task objects to create the full tree of tree of HiveTask objects
421        refreshableJob.Progress.Status = "Downloading list of jobs...";
[6743]422        allTasks = ServiceLocator.Instance.CallHiveService(s => s.GetLightweightJobTasks(hiveExperiment.Id));
423        totalJobCount = allTasks.Count();
[6373]424
[6743]425        TaskDownloader downloader = new TaskDownloader(allTasks.Select(x => x.Id));
[6373]426        downloader.StartAsync();
427
428        while (!downloader.IsFinished) {
[6725]429          refreshableJob.Progress.ProgressValue = downloader.FinishedCount / (double)totalJobCount;
430          refreshableJob.Progress.Status = string.Format("Downloading/deserializing jobs... ({0}/{1} finished)", downloader.FinishedCount, totalJobCount);
[6373]431          Thread.Sleep(500);
432
433          if (downloader.IsFaulted) {
434            throw downloader.Exception;
435          }
436        }
[6743]437        IDictionary<Guid, HiveTask> allHiveTasks = downloader.Results;
[6373]438
[6743]439        refreshableJob.HiveTasks = new ItemCollection<HiveTask>(allHiveTasks.Values.Where(x => !x.Task.ParentTaskId.HasValue));
[6373]440
[6725]441        if (refreshableJob.IsFinished()) {
442          refreshableJob.ExecutionState = Core.ExecutionState.Stopped;
[6373]443        } else {
[6725]444          refreshableJob.ExecutionState = Core.ExecutionState.Started;
[6373]445        }
446
[6725]447        // build child-task tree
[6743]448        foreach (HiveTask hiveTask in refreshableJob.HiveTasks) {
449          BuildHiveJobTree(hiveTask, allTasks, allHiveTasks);
[6373]450        }
451
[6725]452        refreshableJob.OnLoaded();
[6373]453      }
454      finally {
[6725]455        refreshableJob.IsProgressing = false;
[6373]456      }
457    }
458
[6725]459    private static void BuildHiveJobTree(HiveTask parentHiveJob, IEnumerable<LightweightTask> allJobs, IDictionary<Guid, HiveTask> allHiveJobs) {
[6721]460      IEnumerable<LightweightTask> childJobs = from job in allJobs
[6725]461                                               where job.ParentTaskId.HasValue && job.ParentTaskId.Value == parentHiveJob.Task.Id
[6721]462                                               orderby job.DateCreated ascending
463                                               select job;
464      foreach (LightweightTask job in childJobs) {
[6725]465        HiveTask childHiveJob = allHiveJobs[job.Id];
[6373]466        parentHiveJob.AddChildHiveJob(childHiveJob);
467        BuildHiveJobTree(childHiveJob, allJobs, allHiveJobs);
468      }
469    }
470    #endregion
471
472    /// <summary>
473    /// Converts a string which can contain Ids separated by ';' to a enumerable
474    /// </summary>
475    private static IEnumerable<string> ToResourceNameList(string resourceNames) {
476      if (!string.IsNullOrEmpty(resourceNames)) {
477        return resourceNames.Split(';');
478      } else {
479        return new List<string>();
480      }
481    }
482
[6725]483    public static ItemTask LoadItemJob(Guid jobId) {
[6743]484      TaskData jobData = ServiceLocator.Instance.CallHiveService(s => s.GetTaskData(jobId));
[6373]485      try {
[6725]486        return PersistenceUtil.Deserialize<ItemTask>(jobData.Data);
[6373]487      }
488      catch {
489        return null;
490      }
491    }
492
493    /// <summary>
494    /// Executes the action. If it throws an exception it is repeated until repetition-count is reached.
495    /// If repetitions is -1, it is repeated infinitely.
496    /// </summary>
[6419]497    public static void TryAndRepeat(Action action, int repetitions, string errorMessage, ILog log = null) {
[6373]498      while (true) {
499        try { action(); return; }
500        catch (Exception e) {
[6419]501          if (repetitions == 0) throw new HiveException(errorMessage, e);
502          if (log != null) log.LogMessage(string.Format("{0}: {1} - will try again!", errorMessage, e.ToString()));
[6373]503          repetitions--;
504        }
505      }
506    }
[6463]507
[6743]508    public static HiveItemCollection<JobPermission> GetJobPermissions(Guid jobId) {
[6463]509      return ServiceLocator.Instance.CallHiveService((service) => {
[6743]510        IEnumerable<JobPermission> jps = service.GetJobPermissions(jobId);
511        foreach (var hep in jps) {
[6463]512          hep.GrantedUserName = service.GetUsernameByUserId(hep.GrantedUserId);
513        }
[6743]514        return new HiveItemCollection<JobPermission>(jps);
[6463]515      });
516    }
[6373]517  }
518}
Note: See TracBrowser for help on using the repository browser.