public async Task PostExport() { DocumentsOperationContext context; using (ContextPool.AllocateOperationContext(out context)) using (context.OpenReadTransaction()) { var operationId = GetIntValueQueryString("operationId", required: false); var stream = TryGetRequestFormStream("DownloadOptions") ?? RequestBodyStream(); var blittableJson = await context.ReadForMemoryAsync(stream, "DownloadOptions"); var options = JsonDeserializationServer.DatabaseSmugglerOptions(blittableJson); var exporter = new SmugglerExporter(Database, options); var token = CreateOperationToken(); var fileName = exporter.Options.FileName; if (string.IsNullOrEmpty(fileName)) { fileName = $"Dump of {context.DocumentDatabase.Name} {SystemTime.UtcNow.ToString("yyyy-MM-dd HH-mm", CultureInfo.InvariantCulture)}"; } var contentDisposition = "attachment; filename=" + Uri.EscapeDataString(fileName) + ".ravendbdump"; HttpContext.Response.Headers["Content-Disposition"] = contentDisposition; if (operationId.HasValue) { try { await Database.Operations.AddOperation("Export database: " + Database.Name, DatabaseOperations.PendingOperationType.DatabaseExport, onProgress => Task.Run(() => ExportDatabaseInternal(context, exporter, onProgress, token), token.Token), operationId.Value, token); } catch (Exception) { HttpContext.Abort(); } } else { ExportDatabaseInternal(context, exporter, null, token); } } }
private void StreamToClient(Stream stream, ExportOptions options, Lazy <NameValueCollection> headers, IPrincipal user) { var old = CurrentOperationContext.Headers.Value; var oldUser = CurrentOperationContext.User.Value; try { CurrentOperationContext.Headers.Value = headers; CurrentOperationContext.User.Value = user; Database.TransactionalStorage.Batch(accessor => { var bufferStream = new BufferedStream(stream, 1024 * 64); using (var cts = new CancellationTokenSource()) using (var timeout = cts.TimeoutAfter(DatabasesLandlord.SystemConfiguration.DatabaseOperationTimeout)) using (var streamWriter = new StreamWriter(bufferStream)) using (var writer = new JsonTextWriter(streamWriter)) { writer.WriteStartObject(); writer.WritePropertyName("Results"); writer.WriteStartArray(); var exporter = new SmugglerExporter(Database, options); exporter.Export(item => WriteToStream(writer, item, timeout), cts.Token); writer.WriteEndArray(); writer.WriteEndObject(); writer.Flush(); bufferStream.Flush(); } }); } finally { CurrentOperationContext.Headers.Value = old; CurrentOperationContext.User.Value = oldUser; } }
private async Task RunPeriodicExport(bool fullExport) { if (_cancellationToken.IsCancellationRequested) { return; } try { DocumentsOperationContext context; using (_database.DocumentsStorage.ContextPool.AllocateOperationContext(out context)) { var sp = Stopwatch.StartNew(); using (var tx = context.OpenReadTransaction()) { var exportDirectory = _configuration.LocalFolderName ?? Path.Combine(_database.Configuration.Core.DataDirectory, "PeriodicExport-Temp"); if (Directory.Exists(exportDirectory) == false) { Directory.CreateDirectory(exportDirectory); } var now = SystemTime.UtcNow.ToString("yyyy-MM-dd-HH-mm", CultureInfo.InvariantCulture); if (_status.LastFullExportDirectory == null || IsDirectoryExistsOrContainsFiles() == false || fullExport) { fullExport = true; _status.LastFullExportDirectory = Path.Combine(exportDirectory, $"{now}.ravendb-{_database.Name}-backup"); Directory.CreateDirectory(_status.LastFullExportDirectory); } if (_logger.IsInfoEnabled) { _logger.Info($"Exporting a {(fullExport ? "full" : "incremental")} export"); } if (fullExport == false) { var currentLastEtag = DocumentsStorage.ReadLastEtag(tx.InnerTransaction); // No-op if nothing has changed if (currentLastEtag == _status.LastDocsEtag) { return; } } var dataExporter = new SmugglerExporter(_database) { Options = new DatabaseSmugglerOptions { RevisionDocumentsLimit = _exportLimit } }; string exportFilePath; string fileName; if (fullExport) { // create filename for full export fileName = $"{now}.ravendb-full-export"; exportFilePath = Path.Combine(_status.LastFullExportDirectory, fileName); if (File.Exists(exportFilePath)) { var counter = 1; while (true) { fileName = $"{now} - {counter}.${Constants.PeriodicExport.FullExportExtension}"; exportFilePath = Path.Combine(_status.LastFullExportDirectory, fileName); if (File.Exists(exportFilePath) == false) { break; } counter++; } } } else { // create filename for incremental export fileName = $"{now}-0.${Constants.PeriodicExport.IncrementalExportExtension}"; exportFilePath = Path.Combine(_status.LastFullExportDirectory, fileName); if (File.Exists(exportFilePath)) { var counter = 1; while (true) { fileName = $"{now}-{counter}.${Constants.PeriodicExport.IncrementalExportExtension}"; exportFilePath = Path.Combine(_status.LastFullExportDirectory, fileName); if (File.Exists(exportFilePath) == false) { break; } counter++; } } dataExporter.StartDocsEtag = _status.LastDocsEtag; if (dataExporter.StartDocsEtag == null) { IncrementalExport.ReadLastEtagsFromFile(_status.LastFullExportDirectory, context, dataExporter); } } var exportResult = dataExporter.Export(context, exportFilePath); if (fullExport == false) { // No-op if nothing has changed if (exportResult.LastDocsEtag == _status.LastDocsEtag) { if (_logger.IsInfoEnabled) { _logger.Info("Periodic export returned prematurely, nothing has changed since last export"); } return; } } try { await UploadToServer(exportFilePath, fileName, fullExport); } finally { // if user did not specify local folder we delete temporary file. if (string.IsNullOrEmpty(_configuration.LocalFolderName)) { IOExtensions.DeleteFile(exportFilePath); } } _status.LastDocsEtag = exportResult.LastDocsEtag; if (fullExport) { _status.LastFullExportAt = SystemTime.UtcNow; } else { _status.LastExportAt = SystemTime.UtcNow; } WriteStatus(); } if (_logger.IsInfoEnabled) { _logger.Info($"Successfully exported {(fullExport ? "full" : "incremental")} export in {sp.ElapsedMilliseconds:#,#;;0} ms."); } _exportLimit = null; } } catch (OperationCanceledException) { // shutting down, probably } catch (ObjectDisposedException) { // shutting down, probably } catch (Exception e) { _exportLimit = 100; if (_logger.IsOperationsEnabled) { _logger.Operations("Error when performing periodic export", e); } _database.Alerts.AddAlert(new Alert { Type = AlertType.PeriodicExport, Message = "Error in Periodic Export", CreatedAt = SystemTime.UtcNow, Severity = AlertSeverity.Error, Content = new ExceptionAlertContent { Message = e.Message, Exception = e.ToString() } }); } }
private IOperationResult ExportDatabaseInternal(DocumentsOperationContext context, SmugglerExporter exporter, Action <IOperationProgress> onProgress, OperationCancelToken token) { try { return(exporter.Export(context, ResponseBodyStream(), onProgress)); } finally { token.Dispose(); } }
public static void ReadLastEtagsFromFile(string exportDirectory, DocumentsOperationContext context, SmugglerExporter smugglerExporter) { var etagFileLocation = Path.Combine(exportDirectory, IncrementalExportStateFile); if (File.Exists(etagFileLocation) == false) { return; } using (var fileStream = new FileStream(etagFileLocation, FileMode.Open)) { var reader = context.ReadForMemory(fileStream, IncrementalExportStateFile); reader.TryGet("LastDocsEtag", out smugglerExporter.StartDocsEtag); } }