Пример #1
0
        private async Task <DeploySummary> UploadDirectoryInternalAsync(
            [NotNull] DirectoryInfo sourceDirectory,
            [NotNull] DirectoryInfo baseDirectory,
            FtpPath basePath,
            CancellationToken cancellationToken)
        {
            FtpPath dir = basePath.Append(new FtpPath(PathHelper.RelativePath(sourceDirectory, baseDirectory),
                                                      FileSystemType.Directory));

            var summary = new DeploySummary();

            bool directoryExists = await DirectoryExistsAsync(dir, cancellationToken);

            if (!directoryExists)
            {
                await CreateDirectoryAsync(dir, cancellationToken);

                summary.CreatedDirectories.Add(dir.Path);
            }

            DeploySummary uploadSummary =
                await UploadFilesAsync(sourceDirectory, baseDirectory, basePath, cancellationToken);

            summary.Add(uploadSummary);

            return(summary);
        }
        public static string ToDisplayValue([NotNull] this DeploySummary summary)
        {
            if (summary is null)
            {
                throw new ArgumentNullException(nameof(summary));
            }

            return(JsonConvert.SerializeObject(summary, Formatting.Indented));
        }
        public async Task PublishFilesShouldSyncFiles()
        {
            var logger = Context.Logger;

            var(source, deployTargetDirectory, temp) = TestDataHelper.CopyTestData(logger);

            var ftpSettings = new FtpSettings(
                new FtpPath("/", FileSystemType.Directory),
                publicRootPath: new FtpPath("/", FileSystemType.Directory),
                isSecure: false);

            FtpHandler handler = await FtpHandler.Create(new Uri("ftp://127.0.0.1:30021"),
                                                         ftpSettings, new NetworkCredential("testuser", "testpw"), logger);

            var sourceDirectory   = new DirectoryInfo(source);
            var ruleConfiguration = new RuleConfiguration {
                AppOfflineEnabled = true
            };

            using var initialCancellationTokenSource =
                      new CancellationTokenSource(TimeSpan.FromSeconds(50));
            DeploySummary initialSummary = await handler.PublishAsync(ruleConfiguration,
                                                                      deployTargetDirectory,
                                                                      initialCancellationTokenSource.Token);

            logger.Information("Initial: {Initial}", initialSummary.ToDisplayValue());

            using var cancellationTokenSource =
                      new CancellationTokenSource(TimeSpan.FromSeconds(50));
            DeploySummary summary = await handler.PublishAsync(ruleConfiguration,
                                                               sourceDirectory,
                                                               cancellationTokenSource.Token);

            logger.Information("Result: {Result}", summary.ToDisplayValue());

            var fileSystemItems = await handler.ListDirectoryAsync(FtpPath.Root, cancellationTokenSource.Token);

            foreach (FtpPath fileSystemItem in fileSystemItems)
            {
                logger.Information("{Item}", fileSystemItem.Path);
            }

            temp.Dispose();
        }
Пример #4
0
        private async Task <DeploySummary> PublishInternalAsync(
            [NotNull] RuleConfiguration ruleConfiguration,
            [NotNull] DirectoryInfo sourceDirectory,
            CancellationToken cancellationToken)
        {
            var deploymentChangeSummary = new DeploySummary();

            var stopwatch = Stopwatch.StartNew();

            try
            {
                FtpPath basePath = _ftpSettings.BasePath ?? FtpPath.Root;

                if (!await DirectoryExistsAsync(basePath, cancellationToken))
                {
                    await CreateDirectoryAsync(basePath, cancellationToken);
                }

                _logger.Debug("Listing files in remote path '{Path}'", basePath.Path);

                var fileSystemItems =
                    await ListDirectoryAsync(basePath, cancellationToken);

                FtpPath[] sourceFiles = sourceDirectory
                                        .GetFiles("*", SearchOption.AllDirectories)
                                        .Select(s =>
                                                basePath.Append(new FtpPath(PathHelper.RelativePath(s, sourceDirectory), FileSystemType.File)))
                                        .ToArray();

                FtpPath[] filesToKeep = fileSystemItems
                                        .Where(s => KeepFile(s, ruleConfiguration))
                                        .Where(s => s.Type == FileSystemType.File)
                                        .ToArray();

                var filesToRemove = fileSystemItems
                                    .Except(sourceFiles)
                                    .Except(filesToKeep)
                                    .Where(s => s.Type == FileSystemType.File)
                                    .ToImmutableArray();

                IEnumerable <FtpPath> updated = fileSystemItems.Except(filesToRemove)
                                                .Where(s => s.Type == FileSystemType.File);

                if (ruleConfiguration.AppOfflineEnabled)
                {
                    using var tempFile = TempFile.CreateTempFile("App_Offline", ".htm");
                    var appOfflinePath = new FtpPath($"/{tempFile.File.Name}", FileSystemType.File);

                    FtpPath appOfflineFullPath =
                        (_ftpSettings.PublicRootPath ?? _ftpSettings.BasePath ?? FtpPath.Root).Append(
                            appOfflinePath);

                    await UploadFileAsync(appOfflineFullPath, tempFile.File, cancellationToken);

                    _logger.Debug("Uploaded file '{App_Offline}'", appOfflineFullPath.Path);
                }

                DeploySummary deleteFiles = await DeleteFilesAsync(ruleConfiguration, filesToRemove, cancellationToken);

                deploymentChangeSummary.Add(deleteFiles);

                foreach (FtpPath ftpPath in filesToRemove)
                {
                    deploymentChangeSummary.DeletedFiles.Add(ftpPath.Path);
                }

                foreach (FtpPath ftpPath in updated)
                {
                    deploymentChangeSummary.UpdatedFiles.Add(ftpPath.Path);
                }

                DeploySummary uploadDirectoryAsync =
                    await UploadDirectoryAsync(ruleConfiguration,
                                               sourceDirectory,
                                               sourceDirectory,
                                               basePath,
                                               cancellationToken);

                deploymentChangeSummary.Add(uploadDirectoryAsync);

                if (ruleConfiguration.AppOfflineEnabled)
                {
                    FtpPath[] appOfflineFiles = sourceFiles.Intersect(fileSystemItems)
                                                .Where(file => file.Path is {} &&
                                                       Path.GetFileName(file.Path)
                                                       .Equals(DeploymentConstants.AppOfflineHtm,
                                                               StringComparison.OrdinalIgnoreCase))
                                                .Select(file => file.Path)
                                                .Distinct(StringComparer.OrdinalIgnoreCase)
                                                .Select(file => new FtpPath(file, FileSystemType.File))
                                                .ToArray();

                    foreach (FtpPath appOfflineFile in appOfflineFiles)
                    {
                        bool fileExists = await FileExistsAsync(appOfflineFile, cancellationToken);

                        if (fileExists)
                        {
                            await DeleteFileAsync(appOfflineFile, cancellationToken);

                            _logger.Debug("Deleted {App_Offline}", appOfflineFile.Path);
                        }
                    }
                }
Пример #5
0
        private async Task <DeploySummary> UploadFilesAsync(
            DirectoryInfo sourceDirectory,
            DirectoryInfo baseDirectory,
            FtpPath basePath,
            CancellationToken cancellationToken)
        {
            var summary = new DeploySummary();

            string[] localPaths = sourceDirectory
                                  .GetFiles()
                                  .Select(f => f.FullName)
                                  .ToArray();

            int totalCount = localPaths.Length;

            if (totalCount == 0)
            {
                return(summary);
            }

            FtpPath relativeDir = basePath.Append(new FtpPath(PathHelper.RelativePath(sourceDirectory, baseDirectory),
                                                              FileSystemType.Directory));

            _logger.Verbose("Uploading {Files} files", localPaths.Length);

            int batchSize = _ftpSettings.BatchSize;

            var totalTime = Stopwatch.StartNew();

            try
            {
                int batches = (int)Math.Ceiling(totalCount / (double)batchSize);

                int uploaded = 0;

                for (int i = 0; i < batches; i++)
                {
                    bool batchSuccessful = false;

                    int batchNumber = i + 1;

                    string[] files = localPaths.Skip(i * batchSize).Take(batchSize).ToArray();

                    var stopwatch = new Stopwatch();

                    for (int j = 0; j < _ftpSettings.MaxAttempts; j++)
                    {
                        try
                        {
                            stopwatch.Restart();

                            int uploadedFiles = await _ftpClient.UploadFilesAsync(files,
                                                                                  relativeDir.Path,
                                                                                  FtpRemoteExists.Overwrite,
                                                                                  true,
                                                                                  FtpVerify.Delete | FtpVerify.Retry,
                                                                                  token : cancellationToken);

                            if (uploadedFiles == files.Length)
                            {
                                batchSuccessful = true;
                                break;
                            }

                            _logger.Warning(
                                "The expected number of uploaded files was {Expected} but result was {Actual}, retrying batch {Batch}",
                                files.Length,
                                uploadedFiles,
                                batchNumber);
                        }
                        catch (Exception ex) when(!ex.IsFatal())
                        {
                            _logger.Warning(ex, "FTP ERROR in batch {Batch}", batchNumber);
                        }
                        finally
                        {
                            stopwatch.Stop();
                        }
                    }

                    if (!batchSuccessful)
                    {
                        string message = batches > 1
                            ? $"The batch {batchNumber} failed"
                            : $"Failed to upload files {files}";

                        throw new InvalidOperationException(message);
                    }

                    uploaded += files.Length;
                    string elapsed = $"{stopwatch.Elapsed.TotalSeconds:F2}";

                    string percentage = $"{(100.0D * uploaded) / totalCount:F1}";

                    string paddedPercentage = new string(' ', 5 - percentage.Length) + percentage;

                    double averageTime = totalTime.Elapsed.TotalSeconds / uploaded;

                    string average = averageTime.ToString("F2", CultureInfo.InvariantCulture);

                    string timeLeft = $"{(totalCount - uploaded) * averageTime:F2}s";

                    string paddedBatch =
                        $"{new string(' ', batches.ToString(CultureInfo.InvariantCulture).Length - batchNumber.ToString(CultureInfo.InvariantCulture).Length)}{batchNumber}";

                    string paddedUploaded =
                        $"{new string(' ', totalCount.ToString(CultureInfo.InvariantCulture).Length - uploaded.ToString(CultureInfo.InvariantCulture).Length)}{uploaded}";

                    string totalElapsed = totalTime.Elapsed.TotalSeconds.ToString("F2", CultureInfo.InvariantCulture);

                    if (batches > 1)
                    {
                        _logger.Information(
                            "Uploaded batch {BatchNumber} of {BatchCount} using batch size {Size}, {Uploaded}/{Total} {Percentage}%, average {Average}s per file, time left: ~{TimeLeft}, took {ElapsedTime}s, total time {TotalTime}s",
                            paddedBatch,
                            batches,
                            batchSize,
                            paddedUploaded,
                            totalCount,
                            paddedPercentage,
                            average,
                            timeLeft,
                            elapsed,
                            totalElapsed);
                    }
                    else
                    {
                        _logger.Information("Uploaded files {Files}, took {TotalElapsed}s", files, totalElapsed);
                    }
                }
            }
            finally
            {
                totalTime.Stop();
            }

            foreach (string path in localPaths)
            {
                summary.CreatedFiles.Add(PathHelper.RelativePath(new FileInfo(path), baseDirectory));
            }

            foreach (DirectoryInfo directoryInfo in sourceDirectory.GetDirectories())
            {
                DeploySummary subSummary =
                    await UploadFilesAsync(directoryInfo, baseDirectory, basePath, cancellationToken);

                summary.Add(subSummary);
            }

            return(summary);
        }