/// <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> /// Read the main header of the archive. /// </summary> /// <returns> /// The main header of the archive /// </returns> public JpaArchiveHeader ReadArchiveHeader() { JpaArchiveHeader archiveHeader = new JpaArchiveHeader(); // Initialize parts counter archiveHeader.TotalParts = 1; // Open the first part Open(1); archiveHeader.Signature = ReadAsciiString(3); if (archiveHeader.Signature != "JPA") { throw new InvalidArchiveException(String.Format(Language.ResourceManager.GetString("ERR_FORMAT_INVALID_FILE_TYPE_SIGNATURE"), "JPA")); } archiveHeader.HeaderLength = ReadUShort(); archiveHeader.MajorVersion = ReadByte(); archiveHeader.MinorVersion = ReadByte(); archiveHeader.FileCount = ReadULong(); archiveHeader.UncompressedSize = ReadULong(); archiveHeader.CompressedSize = ReadULong(); if (archiveHeader.HeaderLength > 19) { // We need to loop while we have remaining header bytes ushort remainingBytes = (ushort)(archiveHeader.HeaderLength - 19); while (remainingBytes > 0) { // Do we have an extra header? The next three bytes must be JP followed by 0x01 and the header type byte[] headerSignature = ReadBytes(4); if ((headerSignature[0] != 0x4a) || (headerSignature[1] != 0x50) || (headerSignature[2] != 0x01)) { throw new InvalidArchiveException(Language.ResourceManager.GetString("ERR_FORMAT_INVALID_JPA_EXTRA_HEADER")); } // The next two bytes tell us how long this header is, without the 4 byte signature and type but WITH the header length field ushort extraHeaderLength = ReadUShort(); // Subtract the read bytes from the remaining bytes in the header. remainingBytes -= (ushort)(4 + extraHeaderLength); // Read the extra header switch (headerSignature[3]) { case 0x01: // Spanned Archive Marker header archiveHeader.TotalParts = ReadUShort(); break; default: // I have no idea what this is! throw new InvalidArchiveException(Language.ResourceManager.GetString("ERR_FORMAT_INVALID_JPA_EXTRA_HEADER")); } } } // Invoke the archiveInformation event. We need to do some work to get there, through... // -- Create a new archive information record ArchiveInformation info = new ArchiveInformation(); // -- Get the total archive size by looping all of its parts info.ArchiveSize = 0; for (int i = 1; i <= Parts; i++) { FileInfo fi = new FileInfo(ArchivePath); info.ArchiveSize += (ulong)fi.Length; } archiveHeader.TotalLength = info.ArchiveSize; // -- Incorporate bits from the file header info.FileCount = archiveHeader.FileCount; info.UncompressedSize = archiveHeader.UncompressedSize; info.CompressedSize = archiveHeader.CompressedSize; // -- Create the event arguments object ArchiveInformationEventArgs args = new ArchiveInformationEventArgs(info); // -- Finally, invoke the event OnArchiveInformationEvent(args); // Lastly, return the read archive header return(archiveHeader); }