PageRenderTime 42ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/Raven.Studio/Features/Database/Server.cs

http://github.com/ravendb/ravendb
C# | 350 lines | 290 code | 56 blank | 4 comment | 26 complexity | 66ce42c0042c91d8cfd6429214a624dd MD5 | raw file
Possible License(s): GPL-3.0, LGPL-2.1, MPL-2.0-no-copyleft-exception, Apache-2.0, BSD-3-Clause
  1. using System.Reflection;
  2. using System.Threading.Tasks;
  3. using Raven.Abstractions.Data;
  4. using Raven.Client.Silverlight.Connection;
  5. using Raven.Json.Linq;
  6. using Raven.Studio.Shell;
  7. namespace Raven.Studio.Features.Database
  8. {
  9. using System;
  10. using System.Collections.Generic;
  11. using System.ComponentModel.Composition;
  12. using System.ComponentModel.Composition.Hosting;
  13. using System.Linq;
  14. using System.Net;
  15. using System.Windows.Browser;
  16. using System.Windows.Threading;
  17. using Caliburn.Micro;
  18. using Client;
  19. using Client.Document;
  20. using Client.Extensions;
  21. using Framework.Extensions;
  22. using Messages;
  23. using Newtonsoft.Json.Linq;
  24. using Plugins;
  25. using Plugins.Statistics;
  26. using Statistics;
  27. using Action = System.Action;
  28. [Export(typeof(IServer))]
  29. public class Server : PropertyChangedBase, IServer,
  30. IHandle<StatisticsUpdateRequested>
  31. {
  32. public const string DefaultDatabaseName = "Default Database";
  33. readonly IEventAggregator events;
  34. readonly Dictionary<string, DatabaseStatistics> snapshots = new Dictionary<string, DatabaseStatistics>();
  35. readonly StatisticsViewModel statistics;
  36. readonly AggregateCatalog catalog;
  37. readonly DispatcherTimer timer;
  38. readonly TimeSpan updateFrequency = new TimeSpan(0, 0, 0, 5, 0);
  39. string currentDatabase;
  40. IEnumerable<string> databases;
  41. IEnumerable<ServerError> errors;
  42. string status;
  43. [ImportingConstructor]
  44. public Server(IEventAggregator events, StatisticsViewModel statistics, AggregateCatalog catalog)
  45. {
  46. this.events = events;
  47. this.statistics = statistics;
  48. this.catalog = catalog;
  49. timer = new DispatcherTimer { Interval = updateFrequency };
  50. timer.Tick += delegate { RetrieveStatisticsForCurrentDatabase(); };
  51. events.Subscribe(this);
  52. Status = "Initalizing";
  53. Databases = new string[] { };
  54. }
  55. public bool HasCurrentDatabase { get { return !string.IsNullOrEmpty(CurrentDatabase); } }
  56. public DocumentStore Store { get; private set; }
  57. public string Status
  58. {
  59. get { return status; }
  60. set
  61. {
  62. status = value;
  63. NotifyOfPropertyChange(() => Status);
  64. }
  65. }
  66. void IHandle<StatisticsUpdateRequested>.Handle(StatisticsUpdateRequested message) { RefreshStatistics(false); }
  67. public void Connect(Uri serverAddress, Action callback)
  68. {
  69. Status = "Connecting to server...";
  70. Address = serverAddress.OriginalString;
  71. Store = new DocumentStore { Url = Address };
  72. Store.Initialize();
  73. // We explicitly enable this for the Studio, we rely on SL to actually get us the credentials, and that
  74. // already gives the user a clear warning about the dangers of sending passwords in the clear. I think that
  75. // this is sufficent warning and we don't require an additional step, so we can disable this check safely.
  76. Store.JsonRequestFactory.
  77. EnableBasicAuthenticationOverUnsecureHttpEvenThoughPasswordsWouldBeSentOverTheWireInClearTextToBeStolenByHackers =
  78. true;
  79. LoadPlugins()
  80. .ContinueWith(task1 =>
  81. {
  82. task1.Wait(); // throw on error
  83. using (var session = Store.OpenAsyncSession())
  84. session.Advanced.AsyncDatabaseCommands
  85. .GetDatabaseNamesAsync()
  86. .ContinueWith(t =>
  87. {
  88. if (t.IsFaulted)
  89. return t;
  90. CheckForSystemErrors();
  91. return t;
  92. })
  93. .Unwrap()
  94. .ContinueWith(
  95. task =>
  96. {
  97. Status = "Connected";
  98. var dbs = new List<string>
  99. {
  100. DefaultDatabaseName
  101. };
  102. dbs.AddRange(task.Result);
  103. Databases = dbs;
  104. OpenDatabase(dbs[0], () =>
  105. {
  106. Execute.OnUIThread(() => { if (!timer.IsEnabled) timer.Start(); });
  107. if (callback != null) callback();
  108. });
  109. SetBuildNumber();
  110. },
  111. faulted =>
  112. {
  113. var error = "Unable to connect to " + Address;
  114. Status = error;
  115. events.Publish(new NotificationRaised(error, NotificationLevel.Error));
  116. callback();
  117. });
  118. });
  119. }
  120. private void CheckForSystemErrors()
  121. {
  122. using (var session = this.OpenSession())
  123. {
  124. session.Advanced.AsyncDatabaseCommands.GetAsync("Raven/WarningMessages")
  125. .ContinueWith(task =>
  126. {
  127. if (task.Exception != null)
  128. return;
  129. if (task.Result == null)
  130. return;
  131. var msg = string.Join(Environment.NewLine, task.Result.DataAsJson.Value<RavenJArray>().Values<string>());
  132. IoC.Get<IWindowManager>()
  133. .ShowDialog(new ErrorViewModel
  134. {
  135. CurrentErrorSource = (MethodInfo)MethodBase.GetCurrentMethod(),
  136. Message = "Global system issue!",
  137. Details = msg
  138. });
  139. });
  140. }
  141. }
  142. private Task LoadPlugins()
  143. {
  144. var baseUrl = (Address + "/silverlight/plugins").NoCache();
  145. var request = Store.JsonRequestFactory.CreateHttpJsonRequest(this, baseUrl, "GET", Store.Credentials, Store.Conventions);
  146. var response = request.ReadResponseStringAsync();
  147. return response.ContinueWith(_ => Execute.OnUIThread(() =>
  148. {
  149. {
  150. var urls = from item in JArray.Parse(_.Result)
  151. let url = item.Value<string>()
  152. select url;
  153. var catalogs = from url in urls
  154. let fullUrl = Address + "/silverlight/plugin" + url.Replace('\\', '/')
  155. let uri = new Uri(fullUrl, UriKind.Absolute)
  156. select new DeploymentCatalog(uri);
  157. foreach (var deployment in catalogs)
  158. {
  159. deployment.DownloadCompleted += (s, e) =>
  160. {
  161. if (e.Error != null)
  162. throw e.Error;
  163. };
  164. deployment.DownloadAsync();
  165. catalog.Catalogs.Add(deployment);
  166. }
  167. }
  168. }));
  169. }
  170. public string CurrentDatabase
  171. {
  172. get { return currentDatabase; }
  173. set
  174. {
  175. if (value == currentDatabase) return;
  176. currentDatabase = value;
  177. NotifyOfPropertyChange(() => CurrentDatabase);
  178. NotifyOfPropertyChange(() => HasCurrentDatabase);
  179. }
  180. }
  181. public void OpenDatabase(string name, Action callback)
  182. {
  183. if (callback == null) callback = () => { };
  184. CurrentDatabase = name;
  185. RefreshStatistics(true);
  186. RaiseCurrentDatabaseChanged();
  187. using (var session = OpenSession())
  188. session.Advanced.AsyncDatabaseCommands.EnsureSilverlightStartUpAsync();
  189. callback();
  190. }
  191. private void SetBuildNumber()
  192. {
  193. var request = Store.JsonRequestFactory.CreateHttpJsonRequest(this, Address + "/build/version", "GET", null, Store.Conventions);
  194. request.ReadResponseStringAsync()
  195. .ContinueOnSuccess(task =>
  196. {
  197. var result = RavenJObject.Parse(task.Result);
  198. var ravenJToken = result["BuildVersion"];
  199. BuildNumber = ravenJToken.Value<string>();
  200. NotifyOfPropertyChange(() => BuildNumber);
  201. });
  202. }
  203. public string BuildNumber { get; private set; }
  204. public IEnumerable<string> Databases
  205. {
  206. get { return databases; }
  207. private set
  208. {
  209. databases = value;
  210. NotifyOfPropertyChange(() => Databases);
  211. }
  212. }
  213. public void CreateDatabase(string databaseName, Action callback)
  214. {
  215. Store.AsyncDatabaseCommands
  216. .EnsureDatabaseExistsAsync(databaseName)
  217. .ContinueWith(task =>
  218. {
  219. if (task.Exception != null)
  220. return task;
  221. return Store.AsyncDatabaseCommands
  222. .ForDatabase(databaseName)
  223. .EnsureSilverlightStartUpAsync();
  224. })
  225. .ContinueOnSuccess(create =>
  226. {
  227. if (callback != null) callback();
  228. databases = databases.Union(new[] { databaseName });
  229. NotifyOfPropertyChange(() => Databases);
  230. CurrentDatabase = databaseName;
  231. });
  232. }
  233. public string Address { get; private set; }
  234. public string CurrentDatabaseAddress
  235. {
  236. get
  237. {
  238. return (CurrentDatabase == "Default Database")
  239. ? Address
  240. : Address + "/databases/" + HttpUtility.UrlEncode(CurrentDatabase);
  241. }
  242. }
  243. public string Name { get; private set; }
  244. public IAsyncDocumentSession OpenSession()
  245. {
  246. return (CurrentDatabase == DefaultDatabaseName || CurrentDatabase == null)
  247. ? Store.OpenAsyncSession()
  248. : Store.OpenAsyncSession(CurrentDatabase);
  249. }
  250. public IStatisticsSet Statistics { get { return statistics; } }
  251. public event EventHandler CurrentDatabaseChanged = delegate { };
  252. public IEnumerable<ServerError> Errors
  253. {
  254. get { return errors; }
  255. private set
  256. {
  257. errors = value;
  258. NotifyOfPropertyChange(() => Errors);
  259. }
  260. }
  261. void RaiseCurrentDatabaseChanged() { CurrentDatabaseChanged(this, EventArgs.Empty); }
  262. void RefreshStatistics(bool clear)
  263. {
  264. //if (clear) statistics = new StatisticsViewModel();
  265. if (snapshots.ContainsKey(CurrentDatabase))
  266. {
  267. ProcessStatistics(snapshots[CurrentDatabase]);
  268. }
  269. RetrieveStatisticsForCurrentDatabase();
  270. }
  271. void RetrieveStatisticsForCurrentDatabase()
  272. {
  273. if (!HasCurrentDatabase) return;
  274. using (var session = OpenSession())
  275. {
  276. session.Advanced.AsyncDatabaseCommands
  277. .GetStatisticsAsync()
  278. .ContinueOnSuccess(x => ProcessStatistics(x.Result));
  279. }
  280. }
  281. void ProcessStatistics(DatabaseStatistics mostRecent)
  282. {
  283. bool docsChanged = false;
  284. if (snapshots.ContainsKey(CurrentDatabase))
  285. {
  286. docsChanged = (snapshots[CurrentDatabase].CountOfDocuments != mostRecent.CountOfDocuments);
  287. }
  288. snapshots[CurrentDatabase] = mostRecent;
  289. statistics.Accept(mostRecent);
  290. Errors = mostRecent.Errors.OrderByDescending(error => error.Timestamp);
  291. events.Publish(new StatisticsUpdated(mostRecent) { HasDocumentCountChanged = docsChanged });
  292. }
  293. }
  294. }