protected override void WriteFile(HttpResponseBase response) { // By default, response is fully buffered in memory and sent once completed. On big zipped content, this would cause troubles. // If un-buffering response is required (<c>response.BufferOutput = false;</c>), beware, it may emit very small packets, // causing download time to dramatically raise. To avoid this, it would then required to use a BufferedStream with a // reasonnable buffer size (256kb for instance). http://stackoverflow.com/q/26010915/1178314 // The BufferedStream should encapsulate response.OutputStream. PositionWrapperStream must then Dispose it (current // implementation will not), so long for this causing OutputStream to get closed (BufferedStream do not have any option for // telling it not to close its underlying stream, and it is sealed...). using (var outputStream = new PositionWrapperStream(response.OutputStream)) using (var zip = new ZipArchive(outputStream, ZipArchiveMode.Create, true)) { if (_files != null) { var archiveDir = ZipFolder ?? (_files.Length <= 1 ? string.Empty : string.IsNullOrEmpty(FileDownloadName) ? "files" : Path.ChangeExtension(FileDownloadName, null)); foreach (var file in _files) { if (file == null) { continue; } file.WriteEntry(zip, archiveDir); } } } }
public Task InitializeAsync(FileSystemSmugglerOptions options, FileSystemSmugglerNotifications notifications, CancellationToken cancellationToken) { // We use PositionWrapperStream due to: // http://connect.microsoft.com/VisualStudio/feedbackdetail/view/816411/ziparchive-shouldnt-read-the-position-of-non-seekable-streams positionStream = new PositionWrapperStream(stream, leaveOpen: true); archive = new ZipArchive(positionStream, ZipArchiveMode.Create, leaveOpen: true); return(new CompletedTask()); }
public static long WriteDirectoryToStream(Stream stream, string pathToDirectory) { using (PositionWrapperStream wrappedStream = new PositionWrapperStream(stream)) using (ZipArchive zip = new ZipArchive(wrappedStream, ZipArchiveMode.Create)) { // track how much we've sent (ignoring zip overhead) long bytesSent = 0; // use URIs to calculate relative paths, as discussed in // https://stackoverflow.com/questions/9042861/how-to-make-an-absolute-path-relative-to-a-particular-folder Uri relativeRoot = new Uri(pathToDirectory.Last() == '\\' ? pathToDirectory : pathToDirectory + '\\', UriKind.Absolute); // get all entries in the path var entries = (new DirectoryInfo(pathToDirectory)).EnumerateFileSystemInfos("*", SearchOption.AllDirectories); // add entries to the zip file foreach (var entry in entries) { string entryName = relativeRoot.MakeRelativeUri(new Uri(entry.FullName, UriKind.Absolute)).ToString(); // if we're handling a directory, add a blank entry if (entry.Attributes.HasFlag(FileAttributes.Directory)) { // calculate the relative path as described here: zip.CreateEntry(entryName + "/"); } // we're handling a file else { zip.CreateEntryFromFile(entry.FullName, entryName, CompressionLevel.NoCompression); bytesSent += (new FileInfo(entry.FullName)).Length; } } // return the number of bytes sent return(bytesSent); } }
public virtual async Task <ExportFilesResult> ExportData(SmugglerExportOptions <FilesConnectionStringOptions> exportOptions) { Operations.Configure(Options); Operations.Initialize(Options); var result = new ExportFilesResult { FilePath = exportOptions.ToFile, LastFileEtag = Options.StartFilesEtag, LastDeletedFileEtag = Options.StartFilesDeletionEtag, }; if (result.FilePath != null) { result.FilePath = Path.GetFullPath(result.FilePath); } if (Options.Incremental) { if (Directory.Exists(result.FilePath) == false) { if (File.Exists(result.FilePath)) { result.FilePath = Path.GetDirectoryName(result.FilePath) ?? result.FilePath; } else { Directory.CreateDirectory(result.FilePath); } } if (Options.StartFilesEtag == Etag.Empty) { ReadLastEtagsFromFile(result); } result.FilePath = Path.Combine(result.FilePath, SystemTime.UtcNow.ToString("yyyy-MM-dd-HH-mm-0", CultureInfo.InvariantCulture) + ".ravenfs-incremental-dump"); if (File.Exists(result.FilePath)) { var counter = 1; while (true) { result.FilePath = Path.Combine(Path.GetDirectoryName(result.FilePath), SystemTime.UtcNow.ToString("yyyy-MM-dd-HH-mm", CultureInfo.InvariantCulture) + "-" + counter + ".ravenfs-incremental-dump"); if (File.Exists(result.FilePath) == false) { break; } counter++; } } } SmugglerExportException lastException = null; bool ownedStream = exportOptions.ToStream == null; var stream = exportOptions.ToStream ?? File.Create(result.FilePath); try { await DetectServerSupportedFeatures(exportOptions.From); } catch (WebException e) { throw new SmugglerExportException("Failed to query server for supported features. Reason : " + e.Message) { LastEtag = Etag.Empty, File = result.FilePath }; } try { // used to synchronize max returned values for put/delete operations var maxEtags = Operations.FetchCurrentMaxEtags(); try { // We use PositionWrapperStream due to: // http://connect.microsoft.com/VisualStudio/feedbackdetail/view/816411/ziparchive-shouldnt-read-the-position-of-non-seekable-streams using (var positionStream = new PositionWrapperStream(stream, leaveOpen: true)) using (var archive = new ZipArchive(positionStream, ZipArchiveMode.Create, leaveOpen: true)) { await ExportFiles(archive, result.LastFileEtag, maxEtags.LastFileEtag); await ExportConfigurations(archive); } } catch (SmugglerExportException ex) { result.LastFileEtag = ex.LastEtag; ex.File = result.FilePath; lastException = ex; } if (Options.Incremental) { WriteLastEtagsToFile(result, Path.GetDirectoryName(result.FilePath)); } if (lastException != null) { throw lastException; } return(result); } finally { if (ownedStream && stream != null) { stream.Dispose(); } } }