/// <summary> /// Appends a new metadata section to the end of the file. /// </summary> public Task WriteMetadata(ArchiveMetadataProvider metadata) { if (metadata == null) throw new ArgumentNullException(nameof(metadata)); int metadataCount = metadata.GetCount(); if (metadataCount < 0) throw new InvalidOperationException(nameof(ArchiveMetadataProvider) + " returned negative count."); // TODO: wait for completion of pending writes mMetadataPosition = mAppendPosition; mArchiveStream.Position = mAppendPosition; // TODO: we'll want to write the metadata into a stream, compress it, calculate the checksum on the fly // TODO: we'll probably also want to have a scratch buffer to assembly vectors with no allocation overhead and in a single iteration // (a scratch buffer allows to record the vector in a single iteration and if it was unnecessary it doesn't need to be forwarded to the actual stream) var subStreamCount = mDecoderSections.Sum(x => x != null ? x.Streams.Length : 0); WriteToken(ArchiveMetadataToken.Header); if (mDecoderSections.Count > 0) { WriteToken(ArchiveMetadataToken.MainStreams); WritePackInfo(); WriteUnpackInfo(); WriteSubStreamsInfo(); WriteToken(ArchiveMetadataToken.End); } if (subStreamCount > 0) { WriteToken(ArchiveMetadataToken.Files); WriteNumber(metadataCount); #region Types { int emptyStreamCount = 0; for (int i = 0; i < metadataCount; i++) if (!metadata.HasStream(i)) emptyStreamCount++; if (emptyStreamCount > 0) { WriteBitVectorWithHeader(ArchiveMetadataToken.EmptyStream, Enumerable.Range(0, metadataCount) .Select(x => !metadata.HasStream(x)), metadataCount); if (Enumerable.Range(0, metadataCount).Where(x => !metadata.HasStream(x)).Any(x => !metadata.IsDirectory(x))) WriteBitVectorWithHeader(ArchiveMetadataToken.EmptyFile, Enumerable.Range(0, metadataCount) .Where(x => !metadata.HasStream(x)) .Select(x => !metadata.IsDirectory(x)), emptyStreamCount); if (Enumerable.Range(0, metadataCount).Where(x => !metadata.HasStream(x)).Any(x => metadata.IsDeleted(x))) WriteBitVectorWithHeader(ArchiveMetadataToken.Anti, Enumerable.Range(0, metadataCount) .Where(x => !metadata.HasStream(x)) .Select(x => metadata.IsDeleted(x)), emptyStreamCount); } } #endregion #region Names { bool hasNames = false; int nameSize = 1; for (int i = 0; i < subStreamCount; i++) { var name = metadata.GetName(i); if (!string.IsNullOrEmpty(name)) { hasNames = true; nameSize += (name.Length + 1) * 2; } else { nameSize += 2; } } if (hasNames) { WritePadding(2 + GetNumberSize(nameSize), 16); WriteToken(ArchiveMetadataToken.Name); WriteNumber(nameSize); WriteByte(0); System.Diagnostics.Debug.Assert((mArchiveStream.Position & 15) == 0); for (int i = 0; i < subStreamCount; i++) { var name = metadata.GetName(i); foreach (char ch in name) { WriteByte((byte)ch); WriteByte((byte)(ch >> 8)); } WriteByte(0); WriteByte(0); } } } #endregion WriteDateVector(Enumerable.Range(0, metadataCount).Select(x => metadata.GetCreationDate(x)), ArchiveMetadataToken.CTime); WriteDateVector(Enumerable.Range(0, metadataCount).Select(x => metadata.GetLastAccessDate(x)), ArchiveMetadataToken.ATime); WriteDateVector(Enumerable.Range(0, metadataCount).Select(x => metadata.GetLastWriteDate(x)), ArchiveMetadataToken.MTime); // TODO: what does the start position mean? it doesn't seem to be what I thought it was. WriteUInt64Vector(Enumerable.Range(0, metadataCount).Select(x => default(ulong?)), ArchiveMetadataToken.StartPos); WriteUInt32Vector(Enumerable.Range(0, metadataCount).Select(x => { var attr = metadata.GetAttributes(x); return attr.HasValue ? (uint)attr.Value : default(uint?); }), ArchiveMetadataToken.WinAttributes); WriteToken(ArchiveMetadataToken.End); } WriteToken(ArchiveMetadataToken.End); mMetadataLength = mArchiveStream.Position - mMetadataPosition; // TODO: this is only a temporary implementation; the checksum can be calculated on the fly in the final implementation { var buffer = new byte[0x1000]; mArchiveStream.Position = mMetadataPosition; var checksum = CRC.kInitCRC; int offset = 0; while (offset < mMetadataLength) { var fetched = mArchiveStream.Read(buffer, 0, (int)Math.Min(mMetadataLength - offset, buffer.Length)); if (fetched <= 0 || fetched > mMetadataLength - offset) throw new InternalFailureException(); offset += fetched; checksum = CRC.Update(checksum, buffer, 0, fetched); } mMetadataChecksum = new Checksum((int)CRC.Finish(checksum)); } return Utilities.CompletedTask; }
/// <summary> /// Appends a new metadata section to the end of the file. /// </summary> public Task WriteMetadata(ArchiveMetadataProvider metadata) { if (metadata == null) { throw new ArgumentNullException(nameof(metadata)); } int metadataCount = metadata.GetCount(); if (metadataCount < 0) { throw new InvalidOperationException(nameof(ArchiveMetadataProvider) + " returned negative count."); } // TODO: wait for completion of pending writes mMetadataPosition = mAppendPosition; mArchiveStream.Position = mAppendPosition; // TODO: we'll want to write the metadata into a stream, compress it, calculate the checksum on the fly // TODO: we'll probably also want to have a scratch buffer to assembly vectors with no allocation overhead and in a single iteration // (a scratch buffer allows to record the vector in a single iteration and if it was unnecessary it doesn't need to be forwarded to the actual stream) var subStreamCount = mDecoderSections.Sum(x => x != null ? x.Streams.Length : 0); WriteToken(ArchiveMetadataToken.Header); if (mDecoderSections.Count > 0) { WriteToken(ArchiveMetadataToken.MainStreams); WritePackInfo(); WriteUnpackInfo(); WriteSubStreamsInfo(); WriteToken(ArchiveMetadataToken.End); } if (subStreamCount > 0) { WriteToken(ArchiveMetadataToken.Files); WriteNumber(metadataCount); #region Types { int emptyStreamCount = 0; for (int i = 0; i < metadataCount; i++) { if (!metadata.HasStream(i)) { emptyStreamCount++; } } if (emptyStreamCount > 0) { WriteBitVectorWithHeader(ArchiveMetadataToken.EmptyStream, Enumerable.Range(0, metadataCount) .Select(x => !metadata.HasStream(x)), metadataCount); if (Enumerable.Range(0, metadataCount).Where(x => !metadata.HasStream(x)).Any(x => !metadata.IsDirectory(x))) { WriteBitVectorWithHeader(ArchiveMetadataToken.EmptyFile, Enumerable.Range(0, metadataCount) .Where(x => !metadata.HasStream(x)) .Select(x => !metadata.IsDirectory(x)), emptyStreamCount); } if (Enumerable.Range(0, metadataCount).Where(x => !metadata.HasStream(x)).Any(x => metadata.IsDeleted(x))) { WriteBitVectorWithHeader(ArchiveMetadataToken.Anti, Enumerable.Range(0, metadataCount) .Where(x => !metadata.HasStream(x)) .Select(x => metadata.IsDeleted(x)), emptyStreamCount); } } } #endregion #region Names { bool hasNames = false; int nameSize = 1; for (int i = 0; i < subStreamCount; i++) { var name = metadata.GetName(i); if (!string.IsNullOrEmpty(name)) { hasNames = true; nameSize += (name.Length + 1) * 2; } else { nameSize += 2; } } if (hasNames) { WritePadding(2 + GetNumberSize(nameSize), 16); WriteToken(ArchiveMetadataToken.Name); WriteNumber(nameSize); WriteByte(0); System.Diagnostics.Debug.Assert((mArchiveStream.Position & 15) == 0); for (int i = 0; i < subStreamCount; i++) { var name = metadata.GetName(i); foreach (char ch in name) { WriteByte((byte)ch); WriteByte((byte)(ch >> 8)); } WriteByte(0); WriteByte(0); } } } #endregion WriteDateVector(Enumerable.Range(0, metadataCount).Select(x => metadata.GetCreationDate(x)), ArchiveMetadataToken.CTime); WriteDateVector(Enumerable.Range(0, metadataCount).Select(x => metadata.GetLastAccessDate(x)), ArchiveMetadataToken.ATime); WriteDateVector(Enumerable.Range(0, metadataCount).Select(x => metadata.GetLastWriteDate(x)), ArchiveMetadataToken.MTime); // TODO: what does the start position mean? it doesn't seem to be what I thought it was. WriteUInt64Vector(Enumerable.Range(0, metadataCount).Select(x => default(ulong?)), ArchiveMetadataToken.StartPos); WriteUInt32Vector(Enumerable.Range(0, metadataCount).Select(x => { var attr = metadata.GetAttributes(x); return(attr.HasValue ? (uint)attr.Value : default(uint?)); }), ArchiveMetadataToken.WinAttributes); WriteToken(ArchiveMetadataToken.End); } WriteToken(ArchiveMetadataToken.End); mMetadataLength = mArchiveStream.Position - mMetadataPosition; // TODO: this is only a temporary implementation; the checksum can be calculated on the fly in the final implementation { var buffer = new byte[0x1000]; mArchiveStream.Position = mMetadataPosition; var checksum = CRC.kInitCRC; int offset = 0; while (offset < mMetadataLength) { var fetched = mArchiveStream.Read(buffer, 0, (int)Math.Min(mMetadataLength - offset, buffer.Length)); if (fetched <= 0 || fetched > mMetadataLength - offset) { throw new InternalFailureException(); } offset += fetched; checksum = CRC.Update(checksum, buffer, 0, fetched); } mMetadataChecksum = new Checksum((int)CRC.Finish(checksum)); } return(Utilities.CompletedTask); }