1
0
mirror of https://github.com/akpaevj/OneSTools.FileDatabase.git synced 2026-04-24 19:13:53 +02:00

Добавьте файлы проекта.

This commit is contained in:
Акпаев Евгений Александрович
2021-01-03 00:22:00 +03:00
parent 0d24dcf3a5
commit 3ced2c585c
21 changed files with 1393 additions and 0 deletions
+31
View File
@@ -0,0 +1,31 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30804.86
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OneSTools.FileDatabase", "OneSTools.FileDatabase\OneSTools.FileDatabase.csproj", "{EDF41082-9B3F-4AA2-9E60-C18901AFC25A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OneSTools.FileDatabaseTestApp", "OneSTools.FileDatabaseTestApp\OneSTools.FileDatabaseTestApp.csproj", "{40FCEDB2-4C4B-4419-90BB-1BD80840AC05}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{EDF41082-9B3F-4AA2-9E60-C18901AFC25A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EDF41082-9B3F-4AA2-9E60-C18901AFC25A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EDF41082-9B3F-4AA2-9E60-C18901AFC25A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EDF41082-9B3F-4AA2-9E60-C18901AFC25A}.Release|Any CPU.Build.0 = Release|Any CPU
{40FCEDB2-4C4B-4419-90BB-1BD80840AC05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{40FCEDB2-4C4B-4419-90BB-1BD80840AC05}.Debug|Any CPU.Build.0 = Debug|Any CPU
{40FCEDB2-4C4B-4419-90BB-1BD80840AC05}.Release|Any CPU.ActiveCfg = Release|Any CPU
{40FCEDB2-4C4B-4419-90BB-1BD80840AC05}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {37516B47-10E9-4C36-B852-487DB3568E24}
EndGlobalSection
EndGlobal
@@ -0,0 +1,104 @@
using OneSTools.FileDatabase.HighLevel;
using System;
using System.Collections.Generic;
using System.IO;
using OneSTools.FileDatabase.LowLevel;
using System.Linq;
using OneSTools.FileDatabase.LowLevel.Pages;
using OneSTools.FileDatabase.LowLevel.Files;
using OneSTools.BracketsFile;
using System.Text;
using System.Data.Common;
using System.Collections.ObjectModel;
using System.Data;
namespace OneSTools.FileDatabase
{
/// <summary>
/// Provides properties and methods for reading 1C file database data
/// </summary>
public class FileDatabaseConnection : IDisposable
{
private readonly string _path;
private FileDatabaseStream _stream;
private HeaderPage _headerPage;
private FreePagesPage _freePagesPage;
private DatabaseDescriptionFile _databaseDescriptionFile;
/// <summary>
/// The flag of database opening
/// </summary>
public bool Opened { get; private set; } = false;
/// <summary>
/// Version of the database file
/// </summary>
public string Version => _headerPage?.Version;
/// <summary>
/// Locale of the database
/// </summary>
public string Language => _databaseDescriptionFile?.Language;
/// <summary>
/// A collection of database tables
/// </summary>
public ReadOnlyCollection<Table> Tables { get; private set; } = null;
public FileDatabaseConnection(string path)
=> _path = path;
/// <summary>
/// Open database file
/// </summary>
public void Open()
{
if (!File.Exists(_path))
throw new FileNotFoundException("Cannot find a database file", _path);
var stream = new FileStream(_path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
_stream = new FileDatabaseStream(stream);
ReadStructure();
Opened = true;
}
private void ReadStructure()
{
_headerPage = new HeaderPage();
_headerPage.Read(_stream);
_stream.PageSize = _headerPage.PageSize;
_stream.CurrentPage = 1;
_freePagesPage = new FreePagesPage();
_freePagesPage.Read(_stream);
_databaseDescriptionFile = new DatabaseDescriptionFile(_stream);
var tables = new List<Table>();
for (int i = 0; i < _databaseDescriptionFile.TablesCount; i++)
{
var tableDefinitionData = _databaseDescriptionFile.ReadTableDefinitionData();
var tableDefinitionStr = Encoding.UTF8.GetString(tableDefinitionData);
var tableDefinitionNode = BracketsParser.ParseBlock(tableDefinitionStr);
var table = new Table(_stream, tableDefinitionNode);
tables.Add(table);
}
Tables = tables.AsReadOnly();
}
/// <summary>
/// Close database file
/// </summary>
public void Close()
{
Opened = false;
_stream?.Dispose();
}
public void Dispose()
=> Close();
}
}
+85
View File
@@ -0,0 +1,85 @@
using System;
using OneSTools.BracketsFile;
namespace OneSTools.FileDatabase.HighLevel
{
public class Field
{
/// <summary>
/// Real size of the value (bytes)
/// </summary>
internal int MaxSize { get; private set; }
/// <summary>
/// Internal name of the field
/// </summary>
public string Name { get; private set; }
/// <summary>
/// Type of the field's value
/// </summary>
public FieldType Type { get; private set; }
/// <summary>
/// If the flag is True a value can be null
/// </summary>
public bool Nullable { get; private set; }
/// <summary>
/// Length of the value
/// </summary>
public int Length { get; private set; }
/// <summary>
/// "Numeric" value only - position of a point in a value
/// </summary>
public int Precision { get; private set; }
public string CaseSensitive { get; private set; }
internal void Read(BracketsNode node)
{
Name = node[0];
var type = (string)node[1];
Type = type switch
{
"B" => FieldType.Binary,
"L" => FieldType.Logical,
"N" => FieldType.Numeric,
"NC" => FieldType.NChar,
"NVC" => FieldType.NVarChar,
"RV" => FieldType.RowVersion,
"NT" => FieldType.NText,
"I" => FieldType.Image,
"DT" => FieldType.DateTime,
_ => throw new Exception($"{type} is unknown field type"),
};
Nullable = node[2];
Length = node[3];
Precision = node[4];
CaseSensitive = node[5];
CalculateRealSize();
}
public override string ToString()
=> $"{Name} ({Type})";
private void CalculateRealSize()
{
MaxSize = Type switch
{
FieldType.Binary => Length,
FieldType.Logical => 1,
FieldType.Numeric => (Length + 1) / 2 + (Length + 1) % 2,
FieldType.NChar => Length * 2,
FieldType.NVarChar => Length * 2 + 2,
FieldType.RowVersion => 16,
FieldType.NText => 8,
FieldType.Image => 8,
FieldType.DateTime => 7,
_ => throw new NotImplementedException($"There is no alghorithm to calculate size of \"{Type}\" type"),
};
if (Nullable)
MaxSize++;
}
}
}
@@ -0,0 +1,42 @@
namespace OneSTools.FileDatabase.HighLevel
{
public enum FieldType
{
/// <summary>
/// Fixed-length binary data
/// </summary>
Binary,
/// <summary>
/// Boolean value
/// </summary>
Logical,
/// <summary>
/// Fixed point decimal
/// </summary>
Numeric,
/// <summary>
/// Fixed-length Unicode string
/// </summary>
NChar, // Unicode string
/// <summary>
/// Variable-length Unicode string
/// </summary>
NVarChar,
/// <summary>
/// Version of the row
/// </summary>
RowVersion,
/// <summary>
/// Unlimited-length Unicode string (UTF-16)
/// </summary>
NText,
/// <summary>
/// Unlimited-length binary data
/// </summary>
Image,
/// <summary>
/// Date and time
/// </summary>
DateTime
}
}
+40
View File
@@ -0,0 +1,40 @@
using OneSTools.BracketsFile;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace OneSTools.FileDatabase.HighLevel
{
public class Index
{
/// <summary>
/// Internal name of the index
/// </summary>
public string Name { get; private set; }
/// <summary>
/// Collection of the index fields
/// </summary>
public ReadOnlyCollection<IndexField> Fields { get; private set; } = null;
internal void Read(BracketsNode node)
{
var fields = new List<IndexField>();
Name = (string)node[0];
for (int i = 2; i < node.Count; i++)
{
var indexField = new IndexField();
indexField.Read(node[i]);
fields.Add(indexField);
}
Fields = fields.AsReadOnly();
}
public override string ToString()
{
return Name;
}
}
}
@@ -0,0 +1,27 @@
using OneSTools.BracketsFile;
namespace OneSTools.FileDatabase.HighLevel
{
public class IndexField
{
/// <summary>
/// Internal name of the index field
/// </summary>
public string Name { get; private set; }
/// <summary>
/// The length of the value
/// </summary>
public int Length { get; private set; }
internal void Read(BracketsNode node)
{
Name = node[0];
Length = node[1];
}
public override string ToString()
{
return Name;
}
}
}
+120
View File
@@ -0,0 +1,120 @@
using OneSTools.BracketsFile;
using System.Collections.Generic;
using System.IO;
using System;
using OneSTools.FileDatabase.LowLevel;
using System.Collections.ObjectModel;
namespace OneSTools.FileDatabase.HighLevel
{
public class Table
{
internal int MaxRowSize { get; private set; }
internal uint DataFilePage { get; private set; }
internal uint UnlimitedLengthDataFilePage { get; private set; }
internal uint IndexFilePage { get; private set; }
/// <summary>
/// Internal name of the table
/// </summary>
public string Name { get; private set; }
/// <summary>
/// Collection of table fields
/// </summary>
public ReadOnlyCollection<Field> Fields { get; private set; } = null;
/// <summary>
/// Collection of table indexes
/// </summary>
public ReadOnlyCollection<Index> Indexes { get; private set; } = null;
public bool RecordLock { get; private set; }
/// <summary>
/// Collection of table rows
/// </summary>
public IReadOnlyList<object[]> Rows { get; private set; } = null;
internal Table(FileDatabaseStream stream, BracketsNode node)
{
Name = node[0];
for (int i = 2; i < node.Count; i++)
{
var currentNode = node[i];
string nodeName = currentNode[0];
switch (nodeName)
{
case "Fields":
ReadFields(currentNode);
break;
case "Indexes":
ReadIndexes(currentNode);
break;
case "Recordlock":
RecordLock = currentNode[1];
break;
case "Files":
DataFilePage = currentNode[1];
UnlimitedLengthDataFilePage = currentNode[2];
IndexFilePage = currentNode[3];
break;
default:
throw new Exception($"{nodeName} is unknown table description node");
}
}
Rows = new TableRows(stream, this);
}
private void ReadFields(BracketsNode node)
{
var fields = new List<Field>();
for (int i = 1; i < node.Count; i++)
{
var field = new Field();
field.Read(node[i]);
if (field.Type == FieldType.RowVersion)
{
fields.Insert(0, field);
}
else
fields.Add(field);
MaxRowSize += field.MaxSize;
}
// add "free row" mark length
MaxRowSize++;
// add "short version" data length
if (fields.Count > 0 && fields[0].Type == FieldType.RowVersion && RecordLock)
MaxRowSize += 8;
Fields = fields.AsReadOnly();
}
private void ReadIndexes(BracketsNode node)
{
var indexes = new List<Index>();
if (node.Count > 1)
{
for (int i = 1; i < node.Count; i++)
{
var index = new Index();
index.Read(node[i]);
indexes.Add(index);
}
}
Indexes = indexes.AsReadOnly();
}
public override string ToString()
{
return Name;
}
}
}
@@ -0,0 +1,342 @@
using System.Collections.Generic;
using System.Text;
using System;
using OneSTools.FileDatabase.LowLevel;
using OneSTools.FileDatabase.LowLevel.Files;
using System.Buffers.Binary;
using System.Collections;
using System.Collections.ObjectModel;
using System.Globalization;
namespace OneSTools.FileDatabase.HighLevel
{
internal class TableRows : IReadOnlyList<object[]>
{
private readonly FileDatabaseStream _stream;
private readonly Table _table;
private DataFile _dataFile;
private UnlimitedLengthDataFile _unlimitedLengthDataFile;
internal TableRows(FileDatabaseStream stream, Table table)
{
_stream = stream;
_table = table;
}
public object[] this[int index]
{
get
{
if (index >= Count)
throw new IndexOutOfRangeException();
else
{
return Get(index + 1);
}
}
}
public int Count
{
get
{
InitializeFiles();
return (int)(_dataFile?.RootPage.DataLength / Convert.ToUInt64(_table.MaxRowSize));
}
}
public IEnumerator<object[]> GetEnumerator()
{
for (int i = 0; i < Count; i++)
{
yield return Get(i + 1);
}
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
private void InitializeFiles()
{
if (_dataFile == null)
{
if (_table.DataFilePage != 0)
{
_dataFile = new DataFile(_stream, _table.DataFilePage, _table.MaxRowSize, _table.Fields.Count > 0 && _table.Fields[0].Type != FieldType.RowVersion && _table.RecordLock);
if (_dataFile.HasData())
{
_dataFile.GoToDataStartingPosition();
_dataFile.GoToRow(1);
}
}
if (_table.UnlimitedLengthDataFilePage != 0)
{
_unlimitedLengthDataFile = new UnlimitedLengthDataFile(_stream, _table.UnlimitedLengthDataFilePage);
if (_unlimitedLengthDataFile.HasData())
_dataFile.GoToDataStartingPosition();
}
}
}
private object[] Get(int rowNumber)
{
InitializeFiles();
_dataFile.GoToRow(rowNumber);
var values = new object[_table.Fields.Count];
var rawData = _dataFile.ReadRow();
if (rawData == null)
return null;
else
{
var currentOffset = 0;
for (int i = 0; i < _table.Fields.Count; i++)
{
var field = _table.Fields[i];
var fieldData = rawData[currentOffset..(currentOffset + field.MaxSize)];
currentOffset += field.MaxSize;
values[i] = field.Type switch
{
FieldType.Binary => ReadBinary(fieldData, field.Nullable),
FieldType.Logical => ReadLogical(fieldData, field.Nullable),
FieldType.Numeric => ReadNumericValue(fieldData, field.Precision, field.Nullable),
FieldType.NChar => ReadNChar(fieldData, field.Nullable),
FieldType.NVarChar => ReadNVarChar(fieldData, field.Nullable),
FieldType.RowVersion => ReadRowVersion(fieldData, field.Nullable),
FieldType.NText => ReadNText(fieldData, field.Nullable),
FieldType.Image => ReadImage(fieldData, field.Nullable),
FieldType.DateTime => ReadDateTime(fieldData, field.Nullable),
_ => throw new Exception($"Reading value for a field with type {field.Type} is not implemented")
};
}
return values;
}
}
private byte[] ReadBinary(byte[] data, bool nullable)
{
if (!HasValue(data, nullable))
return null;
else
{
var valueData = GetValueData(data, nullable);
return valueData;
}
}
private bool? ReadLogical(byte[] data, bool nullable)
{
if (!HasValue(data, nullable))
return null;
else
{
var valueData = GetValueData(data, nullable);
return valueData[0] != 0;
}
}
private decimal? ReadNumericValue(byte[] data, int precision, bool nullable)
{
if (!HasValue(data, nullable))
return null;
else
{
var valueData = GetValueData(data, nullable);
var doubleStr = new StringBuilder();
var negative = ReadTetrad(valueData[0]) == 0;
if (negative)
doubleStr.Append('-');
doubleStr.Append(ReadTetrad(valueData[0], true));
for (int i = 1; i < valueData.Length; i++)
{
doubleStr.Append(ReadTetrad(valueData[i]));
doubleStr.Append(ReadTetrad(valueData[i], true));
}
if (precision > 0)
doubleStr.Insert(doubleStr.Length - 1 - precision, '.');
// remove last zero, I don't know what is it
doubleStr.Remove(doubleStr.Length - 1, 1);
return decimal.Parse(doubleStr.ToString(), new NumberFormatInfo() { NumberDecimalSeparator = "." });
}
}
private void AddNumberToNumeric(StringBuilder doubleStr, int value)
{
// don't add leading zeros
if (value != 0)
doubleStr.Append(value);
else if (doubleStr.Length > 0)
doubleStr.Append(value);
}
private string ReadNChar(byte[] data, bool nullable)
{
if (!HasValue(data, nullable))
return null;
else
{
var valueData = GetValueData(data, nullable);
return Encoding.Unicode.GetString(valueData);
}
}
private string ReadNVarChar(byte[] data, bool nullable)
{
if (!HasValue(data, nullable))
return null;
else
{
var valueData = GetValueData(data, nullable);
var length = BinaryPrimitives.ReadUInt16LittleEndian(valueData);
if (length == 0)
return "";
else
return Encoding.Unicode.GetString(valueData[2..(length * 2 + 2)]);
}
}
private DateTime? ReadDateTime(byte[] data, bool nullable)
{
if (!HasValue(data, nullable))
return null;
else
{
var valueData = GetValueData(data, nullable);
var year1 = ReadTetrad(valueData[0]);
var year2 = ReadTetrad(valueData[0], true);
var year3 = ReadTetrad(valueData[1]);
var year4 = ReadTetrad(valueData[1], true);
var year = int.Parse($"{year1}{year2}{year3}{year4}");
var month1 = ReadTetrad(valueData[2]);
var month2 = ReadTetrad(valueData[2], true);
var month = int.Parse($"{month1}{month2}");
var day1 = ReadTetrad(valueData[3]);
var day2 = ReadTetrad(valueData[3], true);
var day = int.Parse($"{day1}{day2}");
var hour1 = ReadTetrad(valueData[4]);
var hour2 = ReadTetrad(valueData[4], true);
var hour = int.Parse($"{hour1}{hour2}");
var minute1 = ReadTetrad(valueData[5]);
var minute2 = ReadTetrad(valueData[5], true);
var minute = int.Parse($"{minute1}{minute2}");
var second1 = ReadTetrad(valueData[6]);
var second2 = ReadTetrad(valueData[6], true);
var second = int.Parse($"{second1}{second2}");
if (year == 0 && month == 0 && day == 0 && hour == 0 && minute == 0 && second == 0)
return DateTime.MinValue;
else
return new DateTime(year, month, day, hour, minute, second);
}
}
private string ReadRowVersion(byte[] data, bool nullable)
{
if (!HasValue(data, nullable))
return null;
else
{
var valueData = GetValueData(data, nullable);
var v1 = BinaryPrimitives.ReadUInt32LittleEndian(valueData[..5]);
var v2 = BinaryPrimitives.ReadUInt32LittleEndian(valueData[5..9]);
var v3 = BinaryPrimitives.ReadUInt32LittleEndian(valueData[9..13]);
var v4 = BinaryPrimitives.ReadUInt32LittleEndian(valueData[12..]);
return $"{v1}.{v2}.{v3}.{v4}";
}
}
private string ReadNText(byte[] data, bool nullable)
{
var valueData = ReadUnlimitedData(data, nullable);
if (valueData is null)
return null;
else
return Encoding.Unicode.GetString(valueData);
}
private byte[] ReadImage(byte[] data, bool nullable)
{
var valueData = ReadUnlimitedData(data, nullable);
if (valueData is null)
return null;
else
return valueData;
}
private byte[] ReadUnlimitedData(byte[] data, bool nullable)
{
if (!HasValue(data, nullable))
return null;
var valueData = GetValueData(data, nullable);
var blockNumber = BinaryPrimitives.ReadUInt32LittleEndian(valueData);
var dataLength = BinaryPrimitives.ReadUInt32LittleEndian(valueData[4..]);
_unlimitedLengthDataFile.GoToBlock(blockNumber);
var valueRawData = _unlimitedLengthDataFile.ReadBlockChain();
return valueRawData[..(int)dataLength];
}
private bool HasValue(byte[] data, bool nullable)
{
if (nullable)
return (bool)ReadLogical(data, false);
else
return true;
}
private byte[] GetValueData(byte[] data, bool nullable)
{
if (nullable)
return data[1..];
else
return data;
}
private int ReadTetrad(byte b, bool second = false)
{
if (second)
return b & 0b_0000_1111;
else
return b >> 4;
}
}
}
@@ -0,0 +1,55 @@
using System;
using System.IO;
namespace OneSTools.FileDatabase.LowLevel
{
internal class FileDatabaseStream : IDisposable
{
private readonly BinaryReader reader;
public long Position
{
get => reader.BaseStream.Position;
set => reader.BaseStream.Position = value;
}
public uint PageSize { get; set; }
public uint CurrentPage
{
get => (uint)(Position / PageSize);
set => reader.BaseStream.Position = PageSize * value;
}
public uint PositionOnPage => (uint)(Position - (CurrentPage * PageSize));
public FileDatabaseStream(Stream stream)
{
reader = new BinaryReader(stream);
}
public byte[] ReadBytes(int count)
=> reader.ReadBytes(count);
public int ReadBytes(byte[] buffer, int offset, int count)
{
var forReading = MaxBytesCanTake(count);
return reader.Read(buffer, offset, (int)forReading);
}
public long SkipBytes(long count)
{
var forSkip = MaxBytesCanTake(count);
reader.BaseStream.Position += forSkip;
return forSkip;
}
private long MaxBytesCanTake(long requestedCount)
=> Math.Min(((CurrentPage + 1) * PageSize) - Position, requestedCount);
public void Dispose()
{
reader?.Dispose();
}
}
}
@@ -0,0 +1,44 @@
using System;
namespace OneSTools.FileDatabase.LowLevel.Files
{
internal class DataFile : InnerFile
{
private readonly int _rowSize;
private readonly bool _hasShortVersion;
public DataFile(FileDatabaseStream stream, uint rootPageNumber, int rowSize, bool hasShortVersion) : base(stream, rootPageNumber)
{
_rowSize = rowSize;
_hasShortVersion = hasShortVersion;
}
public void GoToRow(int number)
{
GoToDataStartingPosition();
SkipBytes(_rowSize * number);
}
public byte[] ReadRow()
{
while (!EndOfDataStream)
{
var data = ReadBytes(_rowSize);
// it's not a free row
if (data[0] == 0)
{
_dataStreamPosition += Convert.ToUInt64(_rowSize);
if (_hasShortVersion)
return data[9..];
else
return data[1..];
}
}
return null;
}
}
}
@@ -0,0 +1,57 @@
using System;
using System.Buffers.Binary;
using System.Text;
using System.Linq;
namespace OneSTools.FileDatabase.LowLevel.Files
{
internal class DatabaseDescriptionFile: UnlimitedLengthDataFile
{
private int _currentTableStartBlock = -1;
public string Language { get; private set; }
public uint TablesCount { get; private set; }
public uint[] TablesStartBlocks { get; private set; }
public DatabaseDescriptionFile(FileDatabaseStream stream) : base(stream, 2)
{
GoToDataStartingPosition();
SkipFirstBlock();
var ddfHeaderData = ReadBlockChain();
Language = Encoding.UTF8.GetString(ddfHeaderData[..(Array.IndexOf(ddfHeaderData, (byte)0))]);
TablesCount = BinaryPrimitives.ReadUInt32LittleEndian(ddfHeaderData[32..36]);
TablesStartBlocks = new uint[TablesCount];
for (int i = 0; i < TablesCount; i++)
{
var startIndex = 36 + i * 4;
var number = BinaryPrimitives.ReadUInt32LittleEndian(ddfHeaderData[startIndex..(startIndex + 4)]);
if (number == 0)
break;
TablesStartBlocks[i] = number;
}
}
public byte[] ReadTableDefinitionData()
{
GoToNextTableStartBlock();
return ReadBlockChain();
}
private void GoToNextTableStartBlock()
{
_currentTableStartBlock++;
if (TablesStartBlocks.Length <= _currentTableStartBlock)
throw new Exception("There are no more data pages in the root page list");
else
GoToBlock(TablesStartBlocks[_currentTableStartBlock]);
}
}
}
@@ -0,0 +1,155 @@
using OneSTools.FileDatabase.LowLevel.Pages;
using System;
using System.Collections.Generic;
using System.Linq;
namespace OneSTools.FileDatabase.LowLevel.Files
{
internal abstract class InnerFile
{
protected readonly FileDatabaseStream _stream;
private uint _rootPageNumber;
private readonly List<uint> _dataPages = new List<uint>();
protected int _dataPage = -1;
private long _position = 0;
protected ulong _dataStreamPosition = 0;
public RootPage RootPage { get; private set; }
public List<IndexPage> IndexPages { get; private set; }
public bool EndOfDataStream => _dataStreamPosition >= RootPage.DataLength;
public InnerFile(FileDatabaseStream stream, uint rootPageNumber)
{
_stream = stream;
_rootPageNumber = rootPageNumber;
ReadRootPage();
if (RootPage.Type == InnerFileType.Full)
{
ReadIndexPages();
IndexPages.ForEach(c => _dataPages.AddRange(c.PageNumbers));
}
else
_dataPages.AddRange(RootPage.PageNumbers);
}
public bool HasData()
=> RootPage.DataLength > 0;
public void GoToDataStartingPosition()
{
if (!HasData())
throw new Exception("The file doesn't contain data");
_dataPage = -1;
GoToNextDataPage();
}
protected byte[] ReadBytes(int count)
{
_stream.Position = _position;
if (_dataPage == -1)
GoToNextDataPage();
var buffer = new byte[count];
// Loop till read requested quantity
var read = 0;
while (read < count)
{
read += _stream.ReadBytes(buffer, read, count - read);
if (read < count)
GoToNextDataPage();
}
if (PageReadingCompleted())
GoToNextDataPage();
_position = _stream.Position;
return buffer;
}
protected void SkipBytes(long count)
{
_stream.Position = _position;
if (_dataPage == -1)
GoToNextDataPage();
// Loop till read requested quantity
long skipped = 0;
while (skipped < count)
{
skipped += _stream.SkipBytes(count - skipped);
if (skipped < count)
GoToNextDataPage();
}
if (PageReadingCompleted())
GoToNextDataPage();
_position = _stream.Position;
}
private void ReadRootPage()
{
_stream.CurrentPage = _rootPageNumber;
RootPage = new RootPage();
RootPage.Read(_stream);
_position = _stream.Position;
}
private void ReadIndexPages()
{
IndexPages = new List<IndexPage>();
foreach (var pageNumber in RootPage.PageNumbers)
{
if (pageNumber == 0)
break;
else
{
_stream.CurrentPage = pageNumber;
var indexPage = new IndexPage();
indexPage.Read(_stream);
IndexPages.Add(indexPage);
}
}
_position = _stream.Position;
}
private bool PageReadingCompleted()
=> _dataPages[_dataPage] != _stream.CurrentPage;
private void GoToNextDataPage()
{
_dataPage++;
if (_dataPages.Count <= _dataPage)
throw new Exception("There are no more data pages in the root page list");
else
{
var dataPage = _dataPages[_dataPage];
_stream.CurrentPage = dataPage;
}
_position = _stream.Position;
}
}
}
@@ -0,0 +1,8 @@
namespace OneSTools.FileDatabase.LowLevel.Files
{
public enum InnerFileType
{
Short = 0xFD1C,
Full = 0x01FD1C
}
}
@@ -0,0 +1,54 @@
using System.Buffers.Binary;
using System.Collections.Generic;
using System;
namespace OneSTools.FileDatabase.LowLevel.Files
{
internal class UnlimitedLengthDataFile : InnerFile
{
private const int UNLIMITED_DATA_BLOCK_MAX_SIZE = 256;
public UnlimitedLengthDataFile(FileDatabaseStream stream, uint rootPageNumber) : base(stream, rootPageNumber)
{
}
public void SkipFirstBlock()
{
// It skips the first block, cause it's a first block of the chain of free blocks
SkipBytes(UNLIMITED_DATA_BLOCK_MAX_SIZE);
}
public void GoToBlock(uint blockNumber)
{
GoToDataStartingPosition();
SkipBytes(blockNumber * UNLIMITED_DATA_BLOCK_MAX_SIZE);
}
public byte[] ReadBlockChain()
{
var buffer = new List<byte>();
while (true)
{
var nextBlock = BinaryPrimitives.ReadUInt32LittleEndian(ReadBytes(4));
var blockDatalength = BinaryPrimitives.ReadUInt16LittleEndian(ReadBytes(2));
if (blockDatalength > 0)
{
var blockData = ReadBytes(blockDatalength);
buffer.AddRange(blockData);
}
if (nextBlock == 0)
break;
else
GoToBlock(nextBlock);
}
return buffer.ToArray();
}
}
}
@@ -0,0 +1,37 @@
using System;
using System.Buffers.Binary;
namespace OneSTools.FileDatabase.LowLevel.Pages
{
internal class FreePagesPage
{
public uint Type { get; private set; }
public uint PagesCount { get; private set; }
public int Version { get; private set; }
public int[] PageNumbers { get; private set; }
public void Read(FileDatabaseStream reader)
{
var type = BinaryPrimitives.ReadUInt32LittleEndian(reader.ReadBytes(4));
if (!(type is 0x0000FF1C))
throw new Exception($"{type} is not a type of \"Free pages\" block");
Type = type;
PagesCount = BinaryPrimitives.ReadUInt32LittleEndian(reader.ReadBytes(4));
Version = BinaryPrimitives.ReadInt32LittleEndian(reader.ReadBytes(4));
PageNumbers = new int[PagesCount];
for (int i = 0; i < PagesCount; i++)
{
var number = BinaryPrimitives.ReadInt32LittleEndian(reader.ReadBytes(4));
if (number == 0)
break;
PageNumbers[i] = number;
}
}
}
}
@@ -0,0 +1,35 @@
using System;
using System.Buffers.Binary;
using System.Text;
namespace OneSTools.FileDatabase.LowLevel.Pages
{
internal class HeaderPage
{
public string Signature { get; private set; }
public string Version { get; private set; }
public uint PagesCount { get; private set; }
public uint PageSize { get; private set; }
public void Read(FileDatabaseStream reader)
{
var signature = Encoding.UTF8.GetString(reader.ReadBytes(8));
if (signature != "1CDBMSV8")
throw new Exception("This file is not a 1C database");
var versionData = reader.ReadBytes(4);
var version = $"{versionData[0]}.{versionData[1]}.{versionData[2]}.{versionData[3]}";
if (version != "8.3.8.0")
throw new Exception($"{version} is unknown version");
Signature = signature;
Version = version;
PagesCount = BinaryPrimitives.ReadUInt32LittleEndian(reader.ReadBytes(4));
// skip 4 bytes of unknown value
reader.ReadBytes(4);
PageSize = BinaryPrimitives.ReadUInt32LittleEndian(reader.ReadBytes(4));
}
}
}
@@ -0,0 +1,30 @@
using System.Buffers.Binary;
using System.Collections.Generic;
namespace OneSTools.FileDatabase.LowLevel.Pages
{
internal class IndexPage
{
public ulong DataLength { get; private set; }
public uint[] PageNumbers { get; private set; }
public virtual void Read(FileDatabaseStream reader)
{
DataLength = BinaryPrimitives.ReadUInt64LittleEndian(reader.ReadBytes(8));
var pageNumbers = new List<uint>();
while (true)
{
var pageNumber = BinaryPrimitives.ReadUInt32LittleEndian(reader.ReadBytes(4));
if (pageNumber == 0)
break;
pageNumbers.Add(pageNumber);
}
PageNumbers = pageNumbers.ToArray();
}
}
}
@@ -0,0 +1,41 @@
using OneSTools.FileDatabase.LowLevel.Files;
using System.Buffers.Binary;
using System.Collections.Generic;
namespace OneSTools.FileDatabase.LowLevel.Pages
{
internal class RootPage
{
public InnerFileType Type { get; private set; }
public string Version { get; private set; }
public ulong DataLength { get; private set; }
public uint[] PageNumbers { get; private set; }
public void Read(FileDatabaseStream reader)
{
Type = (InnerFileType)BinaryPrimitives.ReadUInt32LittleEndian(reader.ReadBytes(4));
var v1 = BinaryPrimitives.ReadUInt32LittleEndian(reader.ReadBytes(4));
var v2 = BinaryPrimitives.ReadUInt32LittleEndian(reader.ReadBytes(4));
var v3 = BinaryPrimitives.ReadUInt32LittleEndian(reader.ReadBytes(4));
Version = $"{v1}.{v2}.{v3}";
DataLength = BinaryPrimitives.ReadUInt64LittleEndian(reader.ReadBytes(8));
var pageNumbers = new List<uint>();
while (true)
{
var pageNumber = BinaryPrimitives.ReadUInt32LittleEndian(reader.ReadBytes(4));
if (pageNumber == 0)
break;
pageNumbers.Add(pageNumber);
}
PageNumbers = pageNumbers.ToArray();
}
}
}
@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<AssemblyName>OneSTools.FileDatabase</AssemblyName>
<RootNamespace>OneSTools.FileDatabase</RootNamespace>
<Authors>Akpaev Evgeny</Authors>
<Company>Akpaev Evgeny</Company>
<Description>Библиотека для чтения данных файловых информационных баз 1С</Description>
<Copyright>Akpaev Evgeny</Copyright>
<PackageIcon>onestools_icon_nuget.png</PackageIcon>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="OneSTools.BracketsFile" Version="2.1.4" />
<PackageReference Include="System.Memory" Version="4.5.4" />
</ItemGroup>
<ItemGroup>
<None Include="..\..\onestools_icon_nuget.png">
<Pack>True</Pack>
<PackagePath></PackagePath>
</None>
</ItemGroup>
</Project>
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AssemblyName>OneSTools.FileDatabaseTestApp</AssemblyName>
<RootNamespace>OneSTools.FileDatabaseTestApp</RootNamespace>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\OneSTools.FileDatabase\OneSTools.FileDatabase.csproj" />
</ItemGroup>
</Project>
+46
View File
@@ -0,0 +1,46 @@
using OneSTools.FileDatabase;
using System;
using System.Collections.Generic;
using System.Linq;
using OneSTools.FileDatabase.HighLevel;
namespace OneSTools.FileDatabaseTestApp
{
class Program
{
static void Main(string[] args)
{
var filePath = @"C:\Users\akpaev.e.ENTERPRISE\Desktop\Новая папка (2)\1Cv8.1CD";
var filePath2 = @"C:\Users\akpaev.e.ENTERPRISE\Documents\InfoBase10\1Cv8.1CD";
using var database = new FileDatabaseConnection(filePath2);
database.Open();
var table = database.Tables.FirstOrDefault(c => c.Name == "_Document38");
if (table != null)
{
foreach (var values in table.Rows)
{
for (int i = 0; i < table.Fields.Count; i++)
{
var field = table.Fields[i];
var value = values[i];
// Or another one what you need
if (field.Type == FieldType.Numeric)
{
var typedValue = (decimal?)value;
}
if (field.Type == FieldType.NChar
|| field.Type == FieldType.NText
|| field.Type == FieldType.NVarChar)
{
var typedValue = (string)value;
}
}
}
}
}
}
}