using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using Raven.Abstractions.Logging; using Raven.Database.Config; using Raven.Database.FileSystem.Storage; using Raven.Database.FileSystem.Synchronization.Multipart; using Raven.Database.FileSystem.Synchronization.Rdc; using Raven.Database.FileSystem.Synchronization.Rdc.Wrapper; using Raven.Database.FileSystem.Util; using Raven.Client.FileSystem; using Raven.Abstractions.FileSystem; using Raven.Abstractions.Data; namespace Raven.Database.FileSystem.Synchronization { public class ContentUpdateWorkItem : SynchronizationWorkItem { private readonly ILog log = LogManager.GetCurrentClassLogger(); private readonly SigGenerator sigGenerator; private readonly RavenConfiguration configuration; private DataInfo fileDataInfo; private SynchronizationMultipartRequest multipartRequest; public ContentUpdateWorkItem(string file, string sourceServerUrl, ITransactionalStorage storage, SigGenerator sigGenerator, RavenConfiguration configuration) : base(file, sourceServerUrl, storage) { this.sigGenerator = sigGenerator; this.configuration = configuration; } public override SynchronizationType SynchronizationType { get { return SynchronizationType.ContentUpdate; } } private DataInfo FileDataInfo { get { return fileDataInfo ?? (fileDataInfo = GetLocalFileDataInfo(FileName)); } } public override void Cancel() { Cts.Cancel(); } public override async Task<SynchronizationReport> PerformAsync(ISynchronizationServerClient synchronizationServerClient) { AssertLocalFileExistsAndIsNotConflicted(FileMetadata); var destinationMetadata = await synchronizationServerClient.GetMetadataForAsync(FileName).ConfigureAwait(false); if (destinationMetadata == null) { // if file doesn't exist on destination server - upload it there return await UploadToAsync(synchronizationServerClient).ConfigureAwait(false); } var destinationServerRdcStats = await synchronizationServerClient.GetRdcStatsAsync().ConfigureAwait(false); if (!IsRemoteRdcCompatible(destinationServerRdcStats)) throw new SynchronizationException("Incompatible RDC version detected on destination server"); var conflict = CheckConflictWithDestination(FileMetadata, destinationMetadata, FileSystemInfo.Url); if (conflict != null) { var report = await HandleConflict(synchronizationServerClient, conflict, log).ConfigureAwait(false); if (report != null) return report; } using (var localSignatureRepository = new StorageSignatureRepository(Storage, FileName, configuration)) using (var remoteSignatureCache = new VolatileSignatureRepository(FileName, configuration)) { var localRdcManager = new LocalRdcManager(localSignatureRepository, Storage, sigGenerator); var destinationRdcManager = new RemoteRdcManager(synchronizationServerClient, localSignatureRepository, remoteSignatureCache); if (log.IsDebugEnabled) log.Debug("Starting to retrieve signatures of a local file '{0}'.", FileName); Cts.Token.ThrowIfCancellationRequested(); // first we need to create a local file signatures before we synchronize with remote ones var localSignatureManifest = await localRdcManager.GetSignatureManifestAsync(FileDataInfo).ConfigureAwait(false); if (log.IsDebugEnabled) log.Debug("Number of a local file '{0}' signatures was {1}.", FileName, localSignatureManifest.Signatures.Count); if (localSignatureManifest.Signatures.Any()) { var destinationSignatureManifest = await destinationRdcManager.SynchronizeSignaturesAsync(FileDataInfo, Cts.Token).ConfigureAwait(false); if (destinationSignatureManifest.Signatures.Any()) { return await SynchronizeTo(synchronizationServerClient, localSignatureRepository, remoteSignatureCache, localSignatureManifest, destinationSignatureManifest).ConfigureAwait(false); } } return await UploadToAsync(synchronizationServerClient).ConfigureAwait(false); } } private bool IsRemoteRdcCompatible(RdcStats destinationServerRdcStats) { using (var versionChecker = new RdcVersionChecker()) { var localRdcVersion = versionChecker.GetRdcVersion(); return destinationServerRdcStats.CurrentVersion >= localRdcVersion.MinimumCompatibleAppVersion; } } private async Task<SynchronizationReport> SynchronizeTo(ISynchronizationServerClient synchronizationServerClient, ISignatureRepository localSignatureRepository, ISignatureRepository remoteSignatureRepository, SignatureManifest sourceSignatureManifest, SignatureManifest destinationSignatureManifest) { var seedSignatureInfo = SignatureInfo.Parse(destinationSignatureManifest.Signatures.Last().Name); var sourceSignatureInfo = SignatureInfo.Parse(sourceSignatureManifest.Signatures.Last().Name); using (var localFile = StorageStream.Reading(Storage, FileName)) { IList<RdcNeed> needList; using (var needListGenerator = new NeedListGenerator(remoteSignatureRepository, localSignatureRepository)) { needList = needListGenerator.CreateNeedsList(seedSignatureInfo, sourceSignatureInfo, Cts.Token); } return await PushByUsingMultipartRequest(synchronizationServerClient, localFile, needList).ConfigureAwait(false); } } public async Task<SynchronizationReport> UploadToAsync(ISynchronizationServerClient synchronizationServerClient) { using (var sourceFileStream = StorageStream.Reading(Storage, FileName)) { var fileSize = sourceFileStream.Length; var onlySourceNeed = new List<RdcNeed> { new RdcNeed { BlockType = RdcNeedType.Source, BlockLength = (ulong) fileSize, FileOffset = 0 } }; return await PushByUsingMultipartRequest(synchronizationServerClient, sourceFileStream, onlySourceNeed).ConfigureAwait(false); } } private Task<SynchronizationReport> PushByUsingMultipartRequest(ISynchronizationServerClient synchronizationServerClient, Stream sourceFileStream, IList<RdcNeed> needList) { Cts.Token.ThrowIfCancellationRequested(); multipartRequest = new SynchronizationMultipartRequest(synchronizationServerClient, FileSystemInfo, FileName, FileMetadata, sourceFileStream, needList); var bytesToTransferCount = needList.Where(x => x.BlockType == RdcNeedType.Source).Sum(x => (double)x.BlockLength); if (log.IsDebugEnabled) log.Debug( "Synchronizing a file '{0}' (ETag {1}) to {2} by using multipart request. Need list length is {3}. Number of bytes that needs to be transfered is {4}", FileName, FileETag, synchronizationServerClient, needList.Count, bytesToTransferCount); return multipartRequest.PushChangesAsync(Cts.Token); } private DataInfo GetLocalFileDataInfo(string fileName) { FileAndPagesInformation fileAndPages = null; try { Storage.Batch(accessor => fileAndPages = accessor.GetFile(fileName, 0, 0)); } catch (FileNotFoundException) { return null; } return new DataInfo { LastModified = fileAndPages.Metadata.Value<DateTime>(Constants.LastModified).ToUniversalTime(), Length = fileAndPages.TotalSize ?? 0, Name = fileAndPages.Name }; } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) return false; if (ReferenceEquals(this, obj)) return true; if (obj.GetType() != typeof(ContentUpdateWorkItem)) return false; return Equals((ContentUpdateWorkItem)obj); } public bool Equals(ContentUpdateWorkItem other) { if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; return Equals(other.FileName, FileName) && Equals(other.FileETag, FileETag); } public override int GetHashCode() { return (FileName != null ? GetType().Name.GetHashCode() ^ FileName.GetHashCode() ^ FileETag.GetHashCode() : 0); } public override string ToString() { return string.Format("Synchronization of a file content '{0}'", FileName); } } }