/// <summary> /// Implements the Extract method which extracts a backup archive. A DataWriter must be already assigned and configured or an /// exception will be raised. /// </summary> public override void Extract(CancellationToken token) { // Initialize Close(); Progress.FilePosition = 0; Progress.RunningCompressed = 0; Progress.RunningUncompressed = 0; Progress.Status = ExtractionStatus.Running; Progress.LastException = null; ProgressEventArgs args; try { // Read the file header JpaArchiveHeader archiveHeader = ReadArchiveHeader(); // Invoke event at start of extraction args = new ProgressEventArgs(Progress); OnProgressEvent(args); while ((CurrentPartNumber != null) && (Progress.FilePosition < archiveHeader.TotalLength)) { JpaFileHeader fileHeader = ReadFileHeader(); // See https://www.codeproject.com/articles/742774/cancel-a-loop-in-a-task-with-cancellationtokens if (token.IsCancellationRequested) { token.ThrowIfCancellationRequested(); } ProcessDataBlock(fileHeader.CompressedSize, fileHeader.UncompressedSize, fileHeader.CompressionType, fileHeader.EntityType, fileHeader.EntityPath, token); args = new ProgressEventArgs(Progress); OnProgressEvent(args); } } catch (OperationCanceledException cancelledException) { // The operation was cancelled. Set the state to Idle and reset the extraction. Close(); Progress.FilePosition = 0; Progress.RunningCompressed = 0; Progress.RunningUncompressed = 0; Progress.Status = ExtractionStatus.Idle; Progress.LastException = cancelledException; // Invoke an event notifying susbcribers about the cancelation. args = new ProgressEventArgs(Progress); OnProgressEvent(args); return; } catch (Exception errorException) { // Any other exception. Close the option file and set the status to error. Close(); Progress.Status = ExtractionStatus.Error; Progress.LastException = errorException; // Invoke an event notifying of the error state args = new ProgressEventArgs(Progress); OnProgressEvent(args); return; } // Invoke an event signaling the end of extraction Progress.Status = ExtractionStatus.Finished; args = new ProgressEventArgs(Progress); OnProgressEvent(args); }
/// <summary> /// Reads the Entity Description Block in the current position of the JPA archive /// </summary> /// <returns>The entity description block (file header)</returns> public JpaFileHeader ReadFileHeader() { JpaFileHeader fileHeader = new JpaFileHeader(); fileHeader.Signature = ReadAsciiString(3); if (fileHeader.Signature != "JPF") { throw new InvalidArchiveException(String.Format(Language.ResourceManager.GetString("ERR_FORMAT_INVALID_HEADER_AT_POSITION"), CurrentPartNumber, InputStream.Position - 3)); } fileHeader.BlockLength = ReadUShort(); fileHeader.LengthOfEntityPath = ReadUShort(); fileHeader.EntityPath = ReadUtf8String(fileHeader.LengthOfEntityPath); fileHeader.EntityType = (TEntityType)Enum.ToObject(typeof(TEntityType), ReadSByte()); fileHeader.CompressionType = (TCompressionType)Enum.ToObject(typeof(TCompressionType), ReadSByte()); fileHeader.CompressedSize = ReadULong(); fileHeader.UncompressedSize = ReadULong(); fileHeader.EntityPermissions = ReadULong(); ushort standardEntityBlockLength = (ushort)(21 + fileHeader.LengthOfEntityPath); if (fileHeader.BlockLength > standardEntityBlockLength) { // We need to loop while we have remaining header bytes ushort remainingBytes = (ushort)(fileHeader.BlockLength - standardEntityBlockLength); while (remainingBytes > 0) { // Get the extra header signature byte[] headerSignature = ReadBytes(2); // The next two bytes tell us how long this header is, including the signature ushort extraHeaderLength = ReadUShort(); remainingBytes -= extraHeaderLength; if ((headerSignature[0] == 0x00) && (headerSignature[1] == 0x01)) { // Timestamp extra field fileHeader.TimeStamp = ReadLong(); } else { throw new InvalidArchiveException(String.Format(Language.ResourceManager.GetString("ERR_FORMAT_INVALID_EXTRA_HEADER_AT_POSITION"), CurrentPartNumber, InputStream.Position - 3)); } } } // Invoke the OnEntityEvent event. We need to do some work to get there, through... // -- Create a new archive information record EntityInformation info = new EntityInformation(); // -- Incorporate bits from the file header info.CompressedSize = fileHeader.CompressedSize; info.UncompressedSize = fileHeader.UncompressedSize; info.StoredName = fileHeader.EntityPath; // -- Get the absolute path of the file info.AbsoluteName = ""; if (DataWriter != null) { info.AbsoluteName = DataWriter.GetAbsoluteFilePath(fileHeader.EntityPath); } // -- Get some bits from the currently open archive file info.PartNumber = CurrentPartNumber ?? 1; info.PartOffset = InputStream.Position; // -- Create the event arguments object EntityEventArgs args = new EntityEventArgs(info); // -- Finally, invoke the event OnEntityEvent(args); // Lastly, return the read file header return(fileHeader); }