/* i-net software provides programming examples for illustration only, without warranty either expressed or implied, including, but not limited to, the implied warranties of merchantability and/or fitness for a particular purpose. This programming example assumes that you are familiar with the programming language being demonstrated and the tools used to create and debug procedures. i-net software support professionals can help explain the functionality of a particular procedure, but they will not modify these examples to provide added functionality or construct procedures to meet your specific needs. � i-net software 1998-2013 */ using System; using System.Collections.Generic; using System.IO; using System.Text; using Inet.Viewer.Resources; using System.Diagnostics; namespace Inet.Viewer.Data { /// <summary> /// Progress class for the export. This class sends the export request to the Engine, receives the chunks and saves it into one or more files. /// Also, it displays the status of the export in the progress and status bar. /// </summary> /// <seealso cref= "Progress"/> public class ExportProgress : Progress { private const string PropFile = "file"; private const char SlashChar = '/'; private const char DotChar = '.'; /// <summary> /// The original report data instance. May not be used directly, instead <seealso cref="ReportDataCopy"/> should be used. /// </summary> private readonly IRenderData originalData; // Connection to the exported report private IRenderData copiedData; // Paramters that have been set in the Export Dialog private Dictionary<string, string> exportParameter; // Buffer to read the chunks private byte[] chunkBuffer; private int chunkBufferIndex; private int chunkBufferSize; private List<string> fileNames; /// <summary> /// Creates a new export progress and set it to indeterminate. This method use the default value for all not specified properties. /// For a list of properties see interface <seealso cref="IRenderData"/> or the /// i-net Clear Reports documentation at: https://www.inetsoftware.de/documentation/clear-reports/online-help/features/report-url-parameters. /// </summary> /// <param name="parent"> IReportView, used for showError </param> /// <param name="data"> IReportData for the report that will be exported </param> /// <param name="parameters"> Properties specified in the export dialog or with API </param> /// <seealso cref= "IReportView.Export(ExportFormat, string)"/> /// <seealso cref= "IReportView.Export(Dictionary{string, string})"/> public ExportProgress(Action<Exception> parent, IRenderData data, Dictionary<string, string> parameters) : base(parent, Progress.ProgressType.Export) { // First set Progressbar with unknown max-value (Indeterminate=true) // When amount of chunks is available switch Progressbar to know max value (Indeterminate=false) this.Indeterminate = true; // we used to copy here, but this can take long in situations like Ad-Hoc where we are working with a serialized remote ReportData // so instead we now copy lazily this.originalData = data; this.exportParameter = parameters; this.ProgressMode = Data.ProgressMode.Continuous; } /// <summary> /// <inheritDoc/> /// </summary> public override string Name { get { return strings.ProgressExport; } } /// <summary> /// Stops this export progress and stops the rendering process (export) for this report. </summary> public override void Cancel() { ReportDataCopy.Stop(); Status = Progress.ProgressStatus.Canceled; } /// <summary> /// FOR INTERNAL USE ONLY /// /// Gets ExportChunkCount and all Chunks from server and saves them accoring to the format and depending on the settings of the format /// </summary> protected override void Run() { OnProgressChanged(); fileNames = new List<string>(); FileInfo exportFile; // file that will be created for the xport. For HTML (if layout was set) and for SVG this is only the first file string fileName; // path for the to be created (for all formats). For HTML and SVG includingt the sub-directories for more files try { IRenderData data = ReportDataCopy; string format = exportParameter[URLRenderData.ParameterExportFmt]; // get amount of chunks. For this the report might need to be rendered on the server it is not in its cache. int exportCount = data.GetExportChunkCount(exportParameter); if (Status == Progress.ProgressStatus.Canceled) { return; } // change status of the ProgressBar, as now the max value is available Indeterminate = false; // if length is unknown, remove more chunks until the stream is finished if (exportCount == 0) { this.ProgressMode = Data.ProgressMode.Continuous; exportCount = int.MaxValue; } else { this.ProgressMode = Data.ProgressMode.Steps; } // set total amount of steps for ProgressBar TotalProgress = exportCount; fileName = exportParameter[PropFile]; if (format.StartsWith("htm") || format.StartsWith("svg")) { // HTML / SVG Export exportFile = ExportMultiFile(format, fileName); } else if (format.Equals(ReportInfo.FormatJPG) || format.Equals(ReportInfo.FormatGIF) || format.Equals(ReportInfo.FormatPNG) || format.Equals(ReportInfo.FormatBMP)) { exportFile = ExportFilePerPage(fileName, exportCount); } else { // for all other formats (e.g. pdf, rtf, ...) exportFile = ExportOneFile(fileName, data, exportCount); } Status = Progress.ProgressStatus.Completed; // Start application for choosen export format if (exportParameter.ContainsKey("exportInApplication") && exportParameter["exportInApplication"] != null && exportParameter["exportInApplication"].Equals("true")) { if (exportFile != null) { ExportInApplication(exportFile); } } } catch (Exception t) { ViewerUtils.PrintStackTrace(t); // Don't show error message if was canceled (e.g. after pressing the stop button) if (Status == Progress.ProgressStatus.Canceled) { ViewerUtils.Log("Export: Export canceled by user"); } else { ShowError(t); Status = Progress.ProgressStatus.Error; this.Cancel(); } } } /// <summary> /// Get the generated file names on the export. </summary> /// <returns> the file names after the run, else null</returns> public virtual List<string> FileNames { get { return fileNames; } } /// <summary> /// Opens the exported file in a suitable application /// </summary> /// <param name="file"> the file that should be opened </param> private void ExportInApplication(FileInfo file) { try { System.Diagnostics.Process.Start(file.FullName); } catch (Exception) { try { Process.Start("rundll32.exe", Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "shell32.dll") + ",OpenAs_RunDLL " + file.FullName); } catch (Exception e) { ShowError(new ViewerException(strings.Export_InApplicationFailed, e)); } } } /// <summary> /// Read the next 4 bytes from idx on and return it as integer value </summary> /// <param name="buffer">byte array to read from</param> /// <param name="idx">index of the position the specified byte array</param> /// <returns>the read integer value</returns> private int ReadInt(byte[] buffer, int idx) { return (buffer[idx + 0] & 0xFF) + ((buffer[idx + 1] & 0xFF) << 8) + ((buffer[idx + 2] & 0xFF) << 16) + (buffer[idx + 3] << 24); } /// <summary> /// Needed for ExportMultiFile to check if there are enough bytes left iin the local buffer. /// If not get the next chunk</summary> /// <param name="needed"> Amount of needed bytes </param> /// <param name="throwException"> When set to true a ViewerException is thrown, if not enough data are available</param> /// <exception cref="ViewerException"> Wenn alle Daten gefetcht und nicht gen�gend Daten vorhanden </exception> /// <returns> true, if there are more data </returns> private bool EnsureReadBufferSize(int needed, bool throwException) { int oldDataSize = chunkBufferSize - chunkBufferIndex; if (oldDataSize < needed) { byte[] chunk = ReportDataCopy.NextExportChunk(); this.ProgressCount = this.ProgressCount + 1; if (chunk == null) { if (throwException) { throw new ViewerException(strings.ErrorEexport_UnexpectedEnd); } else { return false; } } if (chunkBuffer == null) { // first call chunkBuffer = chunk; chunkBufferIndex = 0; chunkBufferSize = chunk.Length; } else { if (chunkBuffer.Length - oldDataSize < chunk.Length) { // increase buffer byte[] newPuffer = new byte[oldDataSize + chunk.Length]; Array.Copy(chunkBuffer, chunkBufferIndex, newPuffer, 0, oldDataSize); chunkBuffer = newPuffer; } else { Array.Copy(chunkBuffer, chunkBufferIndex, chunkBuffer, 0, oldDataSize); } chunkBufferIndex = 0; chunkBufferSize = oldDataSize; Array.Copy(chunk, 0, chunkBuffer, chunkBufferSize, chunk.Length); chunkBufferSize += chunk.Length; } } return true; } /// <summary> /// Export formats that contain only one file (e.g. pdf, rtf, ps) /// </summary> /// <param name="fileName">the exported file name</param> /// <param name="data">the report data</param> /// <param name="exportCount">the number of chunks</param> /// <returns></returns> private FileInfo ExportOneFile(string fileName, IRenderData data, int exportCount) { FileInfo exportFile; exportFile = new FileInfo(fileName); fileNames.Add(exportFile.FullName); FileStream fos = new FileStream(exportFile.FullName, FileMode.Create); for (int i = 1; i <= exportCount; i++) { byte[] pageData = data.NextExportChunk(); if (pageData != null) { fos.Write(pageData, 0, pageData.Length); } else { break; } // number of the step that was processed for the ProgressBar this.ProgressCount = i; } fos.Close(); return exportFile; } /// <summary> /// Exports formats that contain more than one file, e.g. export in HTML and SVG /// </summary> /// <param name="format"> the export format </param> /// <param name="outputDir"> path for the to exported files. For HTML und SVG it should also contain the name of the subdirectory for the files /// </param> /// <returns> First file that was created for HTML or SVG export</returns> /// <exception cref="IOException"> If an error with the creation of the file has occurred </exception> private FileInfo ExportMultiFile(string format, string outputDir) { chunkBufferIndex = 0; FileInfo firstFile = null; FileStream fos; string subDirName = string.Empty; StringBuilder filePath; bool directoryCreated = false; bool firstFileCreated = false; while (chunkBufferSize > chunkBufferIndex || TotalProgress > ProgressCount) { // Read filename length EnsureReadBufferSize(4, true); int length = ReadInt(chunkBuffer, chunkBufferIndex); if (length == -1) { // eof break; } if (length > 512) { // A problem occured if filename longer than 512 string text = System.Text.Encoding.UTF8.GetString(chunkBuffer, 0, chunkBufferSize); throw new ViewerException(0, strings.ErrorMessage_Export_WrongData, format, null, null, null, 0, text); } chunkBufferIndex += 4; // set index to the beginning of the block, that contains the filename // read filename EnsureReadBufferSize(length, true); // file name was encoded with UTF8, it need to be decoded here with UTF8 string filename = System.Text.Encoding.UTF8.GetString(chunkBuffer, chunkBufferIndex, length); if (!firstFileCreated) { string layout = null; exportParameter.TryGetValue("layout", out layout); if (layout == null) { outputDir = outputDir.Substring(0, outputDir.LastIndexOf(SlashChar) + 1) + filename; } DirectoryInfo dir = new DirectoryInfo(outputDir); dir.Parent.Create(); firstFile = new FileInfo(outputDir); fileNames.Add(firstFile.FullName); fos = new FileStream(firstFile.FullName, FileMode.Create); // create directory with the name of the first html file (without extensions) subDirName = filename.Substring(0, filename.LastIndexOf(DotChar)); firstFileCreated = true; } else { // all other files in subdirectory filePath = new StringBuilder(outputDir.Substring(0, outputDir.LastIndexOf(SlashChar) + 1)); filePath.Append(subDirName); if (!directoryCreated) { Directory.CreateDirectory(filePath.ToString()); directoryCreated = true; } filePath.Append(SlashChar); filePath.Append(filename); string strFilePath = filePath.ToString(); fileNames.Add(strFilePath); fos = new FileStream(strFilePath, FileMode.Create); } chunkBufferIndex += length; // set index to the beginning of the block which contains the length of the file content // read file length EnsureReadBufferSize(4, true); length = ReadInt(chunkBuffer, chunkBufferIndex); chunkBufferIndex += 4; // set index to the beginning of the block which contains the file content // Write file while (length > 0) { EnsureReadBufferSize(1, true); // There must be at least one byte in the buffer to avoid a endless loop int available = Math.Min(chunkBufferSize - chunkBufferIndex, length); fos.Write(chunkBuffer, chunkBufferIndex, available); chunkBufferIndex += available; length -= available; } fos.Close(); } return firstFile; } /// <summary> /// Export for formats where for each page a file will be created like the Image Renderer. </summary> /// <param name="fileName">name of the output file to export</param> /// <param name="exportCount"> amount of chunks from IReportData </param> /// <returns>first file that was created for html or svg export </returns> /// <exception cref="IOException"> If an error with the creation of the file has occurred </exception> /// <seealso cref="ExportMultiFile"/> private FileInfo ExportFilePerPage(string fileName, int exportCount) { IRenderData data = ReportDataCopy; byte[] pageData = data.NextExportChunk(); if (pageData != null && pageData.Length > 2 && pageData[0] == 'P' && pageData[1] == 'K' && !fileName.ToLower().EndsWith(".zip")) { string zipFileName = fileName + ".zip"; if (File.Exists(zipFileName)) { int c = 1; do { zipFileName = fileName + "(" + (c++) + ").zip"; } while (File.Exists(zipFileName)); } fileName = zipFileName; } FileInfo exportFile = new FileInfo(fileName); fileNames.Add(exportFile.FullName); FileStream output = new FileStream(exportFile.FullName, FileMode.Create); for (int i = 1; i <= exportCount; i++) { if (pageData == null) { break; } output.Write(pageData, 0, pageData.Length); this.ProgressCount++; if (i >= exportCount) { break; } pageData = data.NextExportChunk(); } output.Close(); return exportFile; } /// <summary> /// Gets the ReportData copy (copy so we can do more than one export at the same time ). /// synchronized so we never create more than the one copy for this instance </summary> /// <returns> the copy of the IReportData we are using </returns> private IRenderData ReportDataCopy { get { if (copiedData == null) { copiedData = (IRenderData)originalData.Clone(); } return copiedData; } } /// <summary> /// Converts the ExportFormat enm into strings for the paramter url. /// </summary> /// <param name="format">the export format</param> /// <returns>the export format as string</returns> public static string ExportFormatToString(ExportFormat format) { switch (format) { case ExportFormat.BMP: return "bmp"; case ExportFormat.CSV: return "csv"; case ExportFormat.DATA: return "csv"; case ExportFormat.GIF: return "gif"; case ExportFormat.HTML: return "htm"; case ExportFormat.JPG: return "jpg"; case ExportFormat.ODS: return "ods"; case ExportFormat.PDF: return "pdf"; case ExportFormat.PNG: return "png"; case ExportFormat.PS: return "ps"; case ExportFormat.PS2: return "ps2"; case ExportFormat.PS3: return "ps3"; case ExportFormat.RTF: return "rtf"; case ExportFormat.SVG: return "svg"; case ExportFormat.TXT: return "txt"; case ExportFormat.XLS: return "xls"; case ExportFormat.XML: return "xml"; case ExportFormat.None: return string.Empty; default: return null; } } } }