PageRenderTime 49ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/Raven.Database/Server/RavenFS/Synchronization/SynchronizationTask.cs

https://github.com/nwendel/ravendb
C# | 764 lines | 624 code | 135 blank | 5 comment | 56 complexity | 2f8da888718416c0d82222419d869c48 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, BSD-3-Clause, CC-BY-SA-3.0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.Specialized;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Net;
  7. using System.Reactive.Linq;
  8. using System.Threading.Tasks;
  9. using NLog;
  10. using Newtonsoft.Json;
  11. using Raven.Abstractions;
  12. using Raven.Abstractions.Util;
  13. using Raven.Database.Config;
  14. using Raven.Database.Server.RavenFS.Extensions;
  15. using Raven.Database.Server.RavenFS.Notifications;
  16. using Raven.Database.Server.RavenFS.Storage;
  17. using Raven.Database.Server.RavenFS.Storage.Esent;
  18. using Raven.Database.Server.RavenFS.Synchronization.Rdc.Wrapper;
  19. using Raven.Database.Server.RavenFS.Util;
  20. using Raven.Abstractions.Extensions;
  21. using Raven.Json.Linq;
  22. using Raven.Client.FileSystem;
  23. using Raven.Abstractions.FileSystem;
  24. using Raven.Abstractions.FileSystem.Notifications;
  25. using Raven.Client.FileSystem.Connection;
  26. using System.Collections.Concurrent;
  27. using Raven.Abstractions.Data;
  28. using System.Threading;
  29. namespace Raven.Database.Server.RavenFS.Synchronization
  30. {
  31. public class SynchronizationTask
  32. {
  33. private const int DefaultLimitOfConcurrentSynchronizations = 5;
  34. private static readonly Logger Log = LogManager.GetCurrentClassLogger();
  35. private readonly NotificationPublisher publisher;
  36. private readonly ITransactionalStorage storage;
  37. private readonly SynchronizationQueue synchronizationQueue;
  38. private readonly SynchronizationStrategy synchronizationStrategy;
  39. private readonly InMemoryRavenConfiguration systemConfiguration;
  40. private readonly ConcurrentDictionary<string, ConcurrentDictionary<string, SynchronizationDetails>> activeIncomingSynchronizations =
  41. new ConcurrentDictionary<string, ConcurrentDictionary<string, SynchronizationDetails>>();
  42. private readonly IObservable<long> timer;
  43. private int failedAttemptsToGetDestinationsConfig;
  44. public SynchronizationTask(ITransactionalStorage storage, SigGenerator sigGenerator, NotificationPublisher publisher,
  45. InMemoryRavenConfiguration systemConfiguration)
  46. {
  47. this.storage = storage;
  48. this.publisher = publisher;
  49. this.systemConfiguration = systemConfiguration;
  50. this.timer = Observable.Interval(systemConfiguration.FileSystem.MaximumSynchronizationInterval);
  51. synchronizationQueue = new SynchronizationQueue();
  52. synchronizationStrategy = new SynchronizationStrategy(storage, sigGenerator);
  53. LastSuccessfulSynchronizationTime = DateTime.MinValue;
  54. InitializeTimer();
  55. }
  56. public DateTime LastSuccessfulSynchronizationTime { get; private set; }
  57. public string FileSystemUrl
  58. {
  59. get { return string.Format("{0}/fs/{1}", systemConfiguration.ServerUrl.TrimEnd('/'), systemConfiguration.FileSystemName); }
  60. }
  61. public SynchronizationQueue Queue
  62. {
  63. get { return synchronizationQueue; }
  64. }
  65. public void IncomingSynchronizationStarted(string fileName, ServerInfo sourceServerInfo, Guid sourceFileETag, SynchronizationType type)
  66. {
  67. var activeForDestination = activeIncomingSynchronizations.GetOrAdd(sourceServerInfo.FileSystemUrl,
  68. new ConcurrentDictionary<string, SynchronizationDetails>());
  69. var syncDetails = new SynchronizationDetails()
  70. {
  71. DestinationUrl = sourceServerInfo.FileSystemUrl,
  72. FileETag = sourceFileETag,
  73. FileName = fileName,
  74. Type = type
  75. };
  76. if (activeForDestination.TryAdd(fileName, syncDetails))
  77. {
  78. Log.Debug("File '{0}' with ETag {1} was added to an incoming active synchronization queue for a destination {2}",
  79. fileName,
  80. sourceFileETag, sourceServerInfo.FileSystemUrl);
  81. }
  82. }
  83. public void IncomingSynchronizationFinished(string fileName, ServerInfo sourceServerInfo, Guid sourceFileETag)
  84. {
  85. ConcurrentDictionary<string, SynchronizationDetails> activeSourceTasks;
  86. if (activeIncomingSynchronizations.TryGetValue(sourceServerInfo.FileSystemUrl, out activeSourceTasks) == false)
  87. {
  88. Log.Warn("Could not get an active synchronization queue for {0}", sourceServerInfo.FileSystemUrl);
  89. return;
  90. }
  91. SynchronizationDetails removingItem;
  92. if (activeSourceTasks.TryRemove(fileName, out removingItem))
  93. {
  94. Log.Debug("File '{0}' with ETag {1} was removed from an active synchronization queue for a destination {2}",
  95. fileName, sourceFileETag, sourceServerInfo);
  96. }
  97. }
  98. public IEnumerable<SynchronizationDetails> IncomingQueue
  99. {
  100. get
  101. {
  102. return from destinationActive in activeIncomingSynchronizations
  103. from activeFile in destinationActive.Value
  104. select activeFile.Value;
  105. }
  106. }
  107. private void InitializeTimer()
  108. {
  109. timer.Subscribe(tick => StartSynchronizeDestinationsInBackground());
  110. }
  111. private void StartSynchronizeDestinationsInBackground()
  112. {
  113. Task.Factory.StartNew(async () => await SynchronizeDestinationsAsync(), CancellationToken.None, TaskCreationOptions.None, TaskScheduler.Default);
  114. }
  115. public async Task<DestinationSyncResult> SynchronizeDestinationAsync(string filesystemDestination, bool forceSyncingContinuation = true)
  116. {
  117. foreach (var destination in GetSynchronizationDestinations())
  118. {
  119. // If the destination is disabled, we skip it.
  120. if (!destination.Enabled)
  121. continue;
  122. if (string.Compare(filesystemDestination, destination.Url, StringComparison.OrdinalIgnoreCase) == 0)
  123. {
  124. Log.Debug("Starting to synchronize a destination server {0}", destination.Url);
  125. if (!CanSynchronizeTo(destination.Url))
  126. {
  127. Log.Debug("Could not synchronize to {0} because no synchronization request was available", destination.Url);
  128. throw new SynchronizationException(string.Format("No synchronization request was available for filesystem '{0}'", destination.FileSystem));
  129. }
  130. return await SynchronizeDestinationAsync(destination, forceSyncingContinuation);
  131. }
  132. }
  133. Log.Debug("Could not synchronize to {0} because no destination was configured for that url", filesystemDestination);
  134. throw new ArgumentException("Filesystem destination does not exist", "filesystemDestination");
  135. }
  136. public async Task<DestinationSyncResult[]> SynchronizeDestinationsAsync(bool forceSyncingContinuation = true)
  137. {
  138. var destinationSyncTasks = new List<Task<DestinationSyncResult>>();
  139. foreach (var destination in GetSynchronizationDestinations())
  140. {
  141. // If the destination is disabled, we skip it.
  142. if (!destination.Enabled)
  143. continue;
  144. Log.Debug("Starting to synchronize a destination server {0}", destination.Url);
  145. if (!CanSynchronizeTo(destination.Url))
  146. {
  147. Log.Debug("Could not synchronize to {0} because no synchronization request was available", destination.Url);
  148. continue;
  149. }
  150. destinationSyncTasks.Add(SynchronizeDestinationAsync(destination, forceSyncingContinuation));
  151. }
  152. return await Task.WhenAll(destinationSyncTasks);
  153. }
  154. public async Task<SynchronizationReport> SynchronizeFileToAsync(string fileName, SynchronizationDestination destination)
  155. {
  156. ICredentials credentials = null;
  157. if (string.IsNullOrEmpty(destination.Username) == false)
  158. {
  159. credentials = string.IsNullOrEmpty(destination.Domain)
  160. ? new NetworkCredential(destination.Username, destination.Password)
  161. : new NetworkCredential(destination.Username, destination.Password, destination.Domain);
  162. }
  163. var destinationClient = new AsyncFilesServerClient(destination.ServerUrl, destination.FileSystem, apiKey: destination.ApiKey, credentials: credentials).Synchronization;
  164. RavenJObject destinationMetadata;
  165. try
  166. {
  167. destinationMetadata = await destinationClient.Commands.GetMetadataForAsync(fileName);
  168. }
  169. catch (Exception ex)
  170. {
  171. var exceptionMessage = "Could not get metadata details for " + fileName + " from " + destination.Url;
  172. Log.WarnException(exceptionMessage, ex);
  173. return new SynchronizationReport(fileName, Guid.Empty, SynchronizationType.Unknown)
  174. {
  175. Exception = new SynchronizationException(exceptionMessage, ex)
  176. };
  177. }
  178. RavenJObject localMetadata = GetLocalMetadata(fileName);
  179. NoSyncReason reason;
  180. SynchronizationWorkItem work = synchronizationStrategy.DetermineWork(fileName, localMetadata, destinationMetadata, FileSystemUrl, out reason);
  181. if (work == null)
  182. {
  183. Log.Debug("File '{0}' was not synchronized to {1}. {2}", fileName, destination.Url, reason.GetDescription());
  184. return new SynchronizationReport(fileName, Guid.Empty, SynchronizationType.Unknown)
  185. {
  186. Exception = new SynchronizationException(reason.GetDescription())
  187. };
  188. }
  189. return await PerformSynchronizationAsync(destinationClient, work);
  190. }
  191. private async Task<DestinationSyncResult> SynchronizeDestinationAsync(SynchronizationDestination destination,
  192. bool forceSyncingContinuation)
  193. {
  194. try
  195. {
  196. ICredentials credentials = null;
  197. if (string.IsNullOrEmpty(destination.Username) == false)
  198. {
  199. credentials = string.IsNullOrEmpty(destination.Domain)
  200. ? new NetworkCredential(destination.Username, destination.Password)
  201. : new NetworkCredential(destination.Username, destination.Password, destination.Domain);
  202. }
  203. var destinationClient = new AsyncFilesServerClient(destination.ServerUrl, destination.FileSystem,
  204. apiKey: destination.ApiKey, credentials: credentials).Synchronization;
  205. var lastETag = await destinationClient.GetLastSynchronizationFromAsync(storage.Id);
  206. var activeTasks = synchronizationQueue.Active;
  207. var filesNeedConfirmation = GetSyncingConfigurations(destination).Where(sync => activeTasks.All(x => x.FileName != sync.FileName)).ToList();
  208. var confirmations = await ConfirmPushedFiles(filesNeedConfirmation, destinationClient);
  209. var needSyncingAgain = new List<FileHeader>();
  210. foreach (var confirmation in confirmations)
  211. {
  212. if (confirmation.Status == FileStatus.Safe)
  213. {
  214. Log.Debug("Destination server {0} said that file '{1}' is safe", destination, confirmation.FileName);
  215. RemoveSyncingConfiguration(confirmation.FileName, destination.Url);
  216. }
  217. else
  218. {
  219. storage.Batch(accessor =>
  220. {
  221. var fileHeader = accessor.ReadFile(confirmation.FileName);
  222. if (fileHeader != null)
  223. {
  224. needSyncingAgain.Add(fileHeader);
  225. Log.Debug("Destination server {0} said that file '{1}' is {2}.", destination, confirmation.FileName, confirmation.Status);
  226. }
  227. });
  228. }
  229. }
  230. await EnqueueMissingUpdatesAsync(destinationClient, lastETag, needSyncingAgain);
  231. var reports = await Task.WhenAll(SynchronizePendingFilesAsync(destinationClient, forceSyncingContinuation));
  232. var destinationSyncResult = new DestinationSyncResult
  233. {
  234. DestinationServer = destination.ServerUrl,
  235. DestinationFileSystem = destination.FileSystem
  236. };
  237. if (reports.Length > 0)
  238. {
  239. var successfulSynchronizationsCount = reports.Count(x => x.Exception == null);
  240. var failedSynchronizationsCount = reports.Count(x => x.Exception != null);
  241. if (successfulSynchronizationsCount > 0 || failedSynchronizationsCount > 0)
  242. {
  243. Log.Debug(
  244. "Synchronization to a destination {0} has completed. {1} file(s) were synchronized successfully, {2} synchronization(s) were failed",
  245. destination.Url, successfulSynchronizationsCount, failedSynchronizationsCount);
  246. }
  247. destinationSyncResult.Reports = reports;
  248. }
  249. return destinationSyncResult;
  250. }
  251. catch (Exception ex)
  252. {
  253. Log.WarnException(string.Format("Failed to perform a synchronization to a destination {0}", destination), ex);
  254. return new DestinationSyncResult
  255. {
  256. DestinationServer = destination.ServerUrl,
  257. DestinationFileSystem = destination.FileSystem,
  258. Exception = ex
  259. };
  260. }
  261. }
  262. private async Task EnqueueMissingUpdatesAsync(IAsyncFilesSynchronizationCommands destination,
  263. SourceSynchronizationInformation lastEtag,
  264. IList<FileHeader> needSyncingAgain)
  265. {
  266. LogFilesInfo("There were {0} file(s) that needed synchronization because the previous one went wrong: {1}",
  267. needSyncingAgain);
  268. var commands = (IAsyncFilesCommandsImpl)destination.Commands;
  269. var filesToSynchronization = new HashSet<FileHeader>(GetFilesToSynchronization(lastEtag, 100),
  270. new FileHeaderNameEqualityComparer());
  271. LogFilesInfo("There were {0} file(s) that needed synchronization because of greater ETag value: {1}",
  272. filesToSynchronization);
  273. foreach (FileHeader needSyncing in needSyncingAgain)
  274. {
  275. filesToSynchronization.Add(needSyncing);
  276. }
  277. var filteredFilesToSynchronization =
  278. filesToSynchronization.Where(
  279. x => synchronizationStrategy.Filter(x, lastEtag.DestinationServerId, filesToSynchronization)).ToList();
  280. if (filesToSynchronization.Count > 0)
  281. LogFilesInfo("There were {0} file(s) that needed synchronization after filtering: {1}", filteredFilesToSynchronization);
  282. // Early break. There are no files to synchronize to the selected destination.
  283. if (!filteredFilesToSynchronization.Any())
  284. return;
  285. var baseUrl = commands.UrlFor();
  286. foreach (var fileHeader in filteredFilesToSynchronization)
  287. {
  288. var file = fileHeader.Name;
  289. var localMetadata = GetLocalMetadata(file);
  290. RavenJObject destinationMetadata;
  291. try
  292. {
  293. destinationMetadata = await destination.Commands.GetMetadataForAsync(file);
  294. }
  295. catch (Exception ex)
  296. {
  297. Log.WarnException(
  298. string.Format("Could not retrieve a metadata of a file '{0}' from {1} in order to determine needed synchronization type", file,
  299. baseUrl), ex);
  300. continue;
  301. }
  302. NoSyncReason reason;
  303. var work = synchronizationStrategy.DetermineWork(file, localMetadata, destinationMetadata, FileSystemUrl, out reason);
  304. if (work == null)
  305. {
  306. Log.Debug("File '{0}' were not synchronized to {1}. {2}", file, baseUrl, reason.GetDescription());
  307. if (reason == NoSyncReason.ContainedInDestinationHistory)
  308. {
  309. var etag = localMetadata.Value<Guid>(Constants.MetadataEtagField);
  310. await destination.IncrementLastETagAsync(storage.Id, baseUrl, etag);
  311. RemoveSyncingConfiguration(file, baseUrl);
  312. }
  313. continue;
  314. }
  315. if (synchronizationQueue.EnqueueSynchronization(baseUrl, work))
  316. {
  317. publisher.Publish(new SynchronizationUpdateNotification
  318. {
  319. FileName = work.FileName,
  320. DestinationFileSystemUrl = baseUrl,
  321. SourceServerId = storage.Id,
  322. SourceFileSystemUrl = FileSystemUrl,
  323. Type = work.SynchronizationType,
  324. Action = SynchronizationAction.Enqueue,
  325. Direction = SynchronizationDirection.Outgoing
  326. });
  327. }
  328. }
  329. }
  330. private IEnumerable<Task<SynchronizationReport>> SynchronizePendingFilesAsync(IAsyncFilesSynchronizationCommands destination, bool forceSyncingContinuation)
  331. {
  332. var commands = (IAsyncFilesCommandsImpl)destination.Commands;
  333. var destinationUrl = commands.UrlFor();
  334. for (var i = 0; i < AvailableSynchronizationRequestsTo(destinationUrl); i++)
  335. {
  336. SynchronizationWorkItem work;
  337. if (!synchronizationQueue.TryDequePendingSynchronization(destinationUrl, out work))
  338. break;
  339. if (synchronizationQueue.IsDifferentWorkForTheSameFileBeingPerformed(work, destinationUrl))
  340. {
  341. Log.Debug("There was an already being performed synchronization of a file '{0}' to {1}", work.FileName,
  342. destination);
  343. if (synchronizationQueue.EnqueueSynchronization(destinationUrl, work)) // add it again at the end of the queue
  344. {
  345. // add it again at the end of the queue
  346. publisher.Publish(new SynchronizationUpdateNotification
  347. {
  348. FileName = work.FileName,
  349. DestinationFileSystemUrl = destinationUrl,
  350. SourceServerId = storage.Id,
  351. SourceFileSystemUrl = FileSystemUrl,
  352. Type = work.SynchronizationType,
  353. Action = SynchronizationAction.Enqueue,
  354. Direction = SynchronizationDirection.Outgoing
  355. });
  356. }
  357. }
  358. else
  359. {
  360. var workTask = PerformSynchronizationAsync(destination, work);
  361. if (forceSyncingContinuation)
  362. workTask.ContinueWith(t => SynchronizePendingFilesAsync(destination, true).ToArray());
  363. yield return workTask;
  364. }
  365. }
  366. }
  367. private async Task<SynchronizationReport> PerformSynchronizationAsync(IAsyncFilesSynchronizationCommands destination,
  368. SynchronizationWorkItem work)
  369. {
  370. var commands = (IAsyncFilesCommandsImpl)destination.Commands;
  371. string destinationUrl = commands.UrlFor();
  372. Log.Debug("Starting to perform {0} for a file '{1}' and a destination server {2}",
  373. work.GetType().Name, work.FileName, destinationUrl);
  374. if (!CanSynchronizeTo(destinationUrl))
  375. {
  376. Log.Debug("The limit of active synchronizations to {0} server has been achieved. Cannot process a file '{1}'.",
  377. destinationUrl, work.FileName);
  378. if (synchronizationQueue.EnqueueSynchronization(destinationUrl, work))
  379. {
  380. publisher.Publish(new SynchronizationUpdateNotification
  381. {
  382. FileName = work.FileName,
  383. DestinationFileSystemUrl = destinationUrl,
  384. SourceServerId = storage.Id,
  385. SourceFileSystemUrl = FileSystemUrl,
  386. Type = work.SynchronizationType,
  387. Action = SynchronizationAction.Enqueue,
  388. Direction = SynchronizationDirection.Outgoing
  389. });
  390. }
  391. return new SynchronizationReport(work.FileName, work.FileETag, work.SynchronizationType)
  392. {
  393. Exception = new SynchronizationException(string.Format(
  394. "The limit of active synchronizations to {0} server has been achieved. Cannot process a file '{1}'.",
  395. destinationUrl, work.FileName))
  396. };
  397. }
  398. string fileName = work.FileName;
  399. synchronizationQueue.SynchronizationStarted(work, destinationUrl);
  400. publisher.Publish(new SynchronizationUpdateNotification
  401. {
  402. FileName = work.FileName,
  403. DestinationFileSystemUrl = destinationUrl,
  404. SourceServerId = storage.Id,
  405. SourceFileSystemUrl = FileSystemUrl,
  406. Type = work.SynchronizationType,
  407. Action = SynchronizationAction.Start,
  408. Direction = SynchronizationDirection.Outgoing
  409. });
  410. SynchronizationReport report;
  411. try
  412. {
  413. report = await work.PerformAsync(destination);
  414. }
  415. catch (Exception ex)
  416. {
  417. report = new SynchronizationReport(work.FileName, work.FileETag, work.SynchronizationType)
  418. {
  419. Exception = ex,
  420. };
  421. }
  422. var synchronizationCancelled = false;
  423. if (report.Exception == null)
  424. {
  425. var moreDetails = string.Empty;
  426. if (work.SynchronizationType == SynchronizationType.ContentUpdate)
  427. {
  428. moreDetails = string.Format(". {0} bytes were transfered and {1} bytes copied. Need list length was {2}",
  429. report.BytesTransfered, report.BytesCopied, report.NeedListLength);
  430. }
  431. UpdateSuccessfulSynchronizationTime();
  432. Log.Debug("{0} to {1} has finished successfully{2}", work.ToString(), destinationUrl, moreDetails);
  433. }
  434. else
  435. {
  436. if (work.IsCancelled || report.Exception is TaskCanceledException)
  437. {
  438. synchronizationCancelled = true;
  439. Log.DebugException(string.Format("{0} to {1} was canceled", work, destinationUrl), report.Exception);
  440. }
  441. else
  442. {
  443. Log.WarnException(string.Format("{0} to {1} has finished with the exception", work, destinationUrl), report.Exception);
  444. }
  445. }
  446. Queue.SynchronizationFinished(work, destinationUrl);
  447. if (!synchronizationCancelled)
  448. CreateSyncingConfiguration(fileName, work.FileETag, destinationUrl, work.SynchronizationType);
  449. publisher.Publish(new SynchronizationUpdateNotification
  450. {
  451. FileName = work.FileName,
  452. DestinationFileSystemUrl = destinationUrl,
  453. SourceServerId = storage.Id,
  454. SourceFileSystemUrl = FileSystemUrl,
  455. Type = work.SynchronizationType,
  456. Action = SynchronizationAction.Finish,
  457. Direction = SynchronizationDirection.Outgoing
  458. });
  459. return report;
  460. }
  461. private IEnumerable<FileHeader> GetFilesToSynchronization(SourceSynchronizationInformation destinationsSynchronizationInformationForSource, int take)
  462. {
  463. var filesToSynchronization = new List<FileHeader>();
  464. Log.Debug("Getting files to synchronize with ETag greater than {0} [parameter take = {1}]",
  465. destinationsSynchronizationInformationForSource.LastSourceFileEtag, take);
  466. try
  467. {
  468. storage.Batch(
  469. accessor =>
  470. filesToSynchronization =
  471. accessor.GetFilesAfter(destinationsSynchronizationInformationForSource.LastSourceFileEtag, take).ToList());
  472. }
  473. catch (Exception e)
  474. {
  475. Log.WarnException(
  476. string.Format("Could not get files to synchronize after: " +
  477. destinationsSynchronizationInformationForSource.LastSourceFileEtag), e);
  478. }
  479. return filesToSynchronization;
  480. }
  481. private async Task<SynchronizationConfirmation[]> ConfirmPushedFiles(IEnumerable<SynchronizationDetails> filesNeedConfirmation, IAsyncFilesSynchronizationCommands commands)
  482. {
  483. if (!filesNeedConfirmation.Any())
  484. return new SynchronizationConfirmation[0];
  485. return await commands.GetConfirmationForFilesAsync(filesNeedConfirmation.Select(x => new Tuple<string, Guid>(x.FileName, x.FileETag)));
  486. }
  487. private IEnumerable<SynchronizationDetails> GetSyncingConfigurations(SynchronizationDestination destination)
  488. {
  489. var configObjects = new List<SynchronizationDetails>();
  490. try
  491. {
  492. storage.Batch(
  493. accessor =>
  494. {
  495. configObjects = accessor.GetConfigsStartWithPrefix(RavenFileNameHelper.SyncNamePrefix + Uri.EscapeUriString(destination.Url), 0, 100)
  496. .Select(config => config.JsonDeserialization<SynchronizationDetails>())
  497. .ToList();
  498. });
  499. }
  500. catch (Exception e)
  501. {
  502. Log.WarnException(string.Format("Could not get syncing configurations for a destination {0}", destination), e);
  503. }
  504. return configObjects;
  505. }
  506. private void CreateSyncingConfiguration(string fileName, Guid etag, string destinationFileSystemUrl, SynchronizationType synchronizationType)
  507. {
  508. try
  509. {
  510. var name = RavenFileNameHelper.SyncNameForFile(fileName, destinationFileSystemUrl);
  511. var details = new SynchronizationDetails
  512. {
  513. DestinationUrl = destinationFileSystemUrl,
  514. FileName = fileName,
  515. FileETag = etag,
  516. Type = synchronizationType
  517. };
  518. storage.Batch(accessor => accessor.SetConfig(name, JsonExtensions.ToJObject(details)));
  519. }
  520. catch (Exception e)
  521. {
  522. Log.WarnException(
  523. string.Format("Could not create syncing configurations for a file {0} and destination {1}", fileName, destinationFileSystemUrl),
  524. e);
  525. }
  526. }
  527. private void RemoveSyncingConfiguration(string fileName, string destination)
  528. {
  529. try
  530. {
  531. var name = RavenFileNameHelper.SyncNameForFile(fileName, destination);
  532. storage.Batch(accessor => accessor.DeleteConfig(name));
  533. }
  534. catch (Exception e)
  535. {
  536. Log.WarnException(
  537. string.Format("Could not remove syncing configurations for a file {0} and a destination {1}", fileName, destination),
  538. e);
  539. }
  540. }
  541. private RavenJObject GetLocalMetadata(string fileName)
  542. {
  543. RavenJObject result = null;
  544. try
  545. {
  546. storage.Batch(accessor => { result = accessor.GetFile(fileName, 0, 0).Metadata; });
  547. }
  548. catch (FileNotFoundException)
  549. {
  550. return null;
  551. }
  552. // TODO Check if the call to GetFile is needed.
  553. FileAndPagesInformation fileAndPages = null;
  554. {
  555. try
  556. {
  557. storage.Batch(accessor => fileAndPages = accessor.GetFile(fileName, 0, 0));
  558. }
  559. catch (FileNotFoundException)
  560. {
  561. }
  562. }
  563. return result;
  564. }
  565. private IEnumerable<SynchronizationDestination> GetSynchronizationDestinations()
  566. {
  567. var destinationsConfigExists = false;
  568. storage.Batch(accessor => destinationsConfigExists = accessor.ConfigExists(SynchronizationConstants.RavenSynchronizationDestinations));
  569. if (!destinationsConfigExists)
  570. {
  571. if (failedAttemptsToGetDestinationsConfig < 3 || failedAttemptsToGetDestinationsConfig % 10 == 0)
  572. {
  573. Log.Debug("Configuration " + SynchronizationConstants.RavenSynchronizationDestinations + " does not exist");
  574. }
  575. failedAttemptsToGetDestinationsConfig++;
  576. yield break;
  577. }
  578. failedAttemptsToGetDestinationsConfig = 0;
  579. var destinationsConfig = new RavenJObject();
  580. storage.Batch(accessor => destinationsConfig = accessor.GetConfig(SynchronizationConstants.RavenSynchronizationDestinations));
  581. var destinationsStrings = destinationsConfig.Value<RavenJArray>("Destinations");
  582. if (destinationsStrings == null)
  583. {
  584. Log.Warn("Empty " + SynchronizationConstants.RavenSynchronizationDestinations + " configuration");
  585. yield break;
  586. }
  587. if (destinationsStrings.Count() == 0)
  588. {
  589. Log.Warn("Configuration " + SynchronizationConstants.RavenSynchronizationDestinations + " does not contain any destination");
  590. yield break;
  591. }
  592. foreach ( var token in destinationsStrings )
  593. {
  594. yield return JsonExtensions.JsonDeserialization<SynchronizationDestination>((RavenJObject)token);
  595. }
  596. }
  597. private bool CanSynchronizeTo(string destinationFileSystemUrl)
  598. {
  599. return LimitOfConcurrentSynchronizations() > synchronizationQueue.NumberOfActiveSynchronizationTasksFor(destinationFileSystemUrl);
  600. }
  601. private int AvailableSynchronizationRequestsTo(string destinationFileSystemUrl)
  602. {
  603. return LimitOfConcurrentSynchronizations() - synchronizationQueue.NumberOfActiveSynchronizationTasksFor(destinationFileSystemUrl);
  604. }
  605. private int LimitOfConcurrentSynchronizations()
  606. {
  607. bool limit = false;
  608. int configuredLimit = 0;
  609. storage.Batch(
  610. accessor =>
  611. limit = accessor.TryGetConfigurationValue(SynchronizationConstants.RavenSynchronizationLimit, out configuredLimit));
  612. return limit ? configuredLimit : DefaultLimitOfConcurrentSynchronizations;
  613. }
  614. public void Cancel(string fileName)
  615. {
  616. Log.Debug("Cancellation of active synchronizations of a file '{0}'", fileName);
  617. Queue.CancelActiveSynchronizations(fileName);
  618. }
  619. private void UpdateSuccessfulSynchronizationTime()
  620. {
  621. LastSuccessfulSynchronizationTime = SystemTime.UtcNow;
  622. }
  623. private static void LogFilesInfo(string message, ICollection<FileHeader> files)
  624. {
  625. Log.Debug(message, files.Count,
  626. string.Join(",", files.Select(x => string.Format("{0} [ETag {1}]", x.Name, x.Metadata.Value<Guid>(Constants.MetadataEtagField)))));
  627. }
  628. }
  629. }