Click here to Skip to main content
15,885,366 members
Articles / Programming Languages / C#

LumiSoft MailServer

Rate me:
Please Sign up or sign in to vote.
3.79/5 (22 votes)
17 Nov 2006CPOL1 min read 321.6K   4.9K   74  
Full featured SMTP/POP3/IMAP server
using System;
using System.IO;
using System.Collections;

namespace LumiSoft.Data.lsDB
{
	/// <summary>
	/// lsDB database record.
	/// </summary>
	public class LDB_Record
	{
		private DbFile     m_pOwnerDb        = null;
		private DataPage   m_pDataPage       = null;
		private int[]      m_ColumnValueSize = null;

		/// <summary>
		/// Default constructor.
		/// </summary>
		/// <param name="ownerDb">Table that owns this row.</param>
		/// <param name="rowStartDataPage">Data page on what row starts.</param>
		internal LDB_Record(DbFile ownerDb,DataPage rowStartDataPage)
		{
			m_pOwnerDb = ownerDb;
			m_pDataPage = rowStartDataPage;

			ParseRowInfo();
		}


		#region static method CreateRecord

		/// <summary>
		/// Creates record. Contains record info + record values.
		/// </summary>
		/// <param name="ownerDb">Roecord owner table.</param>
		/// <param name="rowValues">Row values what to store to record.</param>
		/// <returns></returns>
		internal static byte[] CreateRecord(DbFile ownerDb,object[] rowValues)
		{
			if(ownerDb.Columns.Count != rowValues.Length){
				throw new Exception("LDB_Record.CreateRecord m_pOwnerDb.Columns.Count != rowValues.Length !");
			}

			// Convert values to internal store format
			ArrayList rowByteValues = new ArrayList();
			for(int i=0;i<rowValues.Length;i++){
				rowByteValues.Add(ConvertToInternalData(ownerDb.Columns[i],rowValues[i]));
			}

			/* RowInfo structure			
				(4 bytes) * columnCount - holds column data data length
				xx bytes columns values
			*/

			MemoryStream msRecord = new MemoryStream();
			// Write values sizes
			for(int i=0;i<rowByteValues.Count;i++){
				msRecord.Write(ldb_Utils.IntToByte(((byte[])rowByteValues[i]).Length),0,4);
			}

			// Write values
			for(int i=0;i<rowByteValues.Count;i++){
				byte[] val = (byte[])rowByteValues[i];
				msRecord.Write(val,0,val.Length);
			}

			return msRecord.ToArray();
		}

		#endregion


		#region method ParseRowInfo

		/// <summary>
		/// Parse row info.
		/// </summary>
		private void ParseRowInfo()
		{
			/* RowInfo structure		
				(4 bytes) * columnCount - holds column data data length
				xx bytes columns values
			*/

			m_ColumnValueSize = new int[m_pOwnerDb.Columns.Count];
			byte[] columnValueSizes = m_pDataPage.ReadData(0,4 * m_pOwnerDb.Columns.Count);
			for(int i=0;i<m_pOwnerDb.Columns.Count;i++){
				m_ColumnValueSize[i] = ldb_Utils.ByteToInt(columnValueSizes,i * 4);
			}
		}

		#endregion

		#region method GetColumnData

		/// <summary>
		/// Gets specified column data.
		/// </summary>
		/// <param name="columnIndex">Column index.</param>
		/// <returns></returns>
		private object GetColumnData(int columnIndex)
		{
			// Get column data start offset
			int columnStartOffset = 4 * m_pOwnerDb.Columns.Count;
			for(int i=0;i<columnIndex;i++){
				columnStartOffset += m_ColumnValueSize[i];
			}

			int dataLength = m_ColumnValueSize[columnIndex];
			int startDataPage = (int)Math.Floor(columnStartOffset / (double)m_pOwnerDb.DataPageDataAreaSize);
			int offsetInStartDataPage = columnStartOffset - (startDataPage * m_pOwnerDb.DataPageDataAreaSize);
	
			int dataOffset = 0;
			int currentDataPageIndex = 0;
			byte[] columnData = new byte[dataLength];
			DataPage currentDataPage = this.DataPage;
			while(dataOffset < dataLength){
				// We haven't reached to data page on what data starts, just go to next continuing data page
				if(currentDataPageIndex < startDataPage){
					// Get next continuing data page
					currentDataPage = new DataPage(m_pOwnerDb.DataPageDataAreaSize,m_pOwnerDb,currentDataPage.NextDataPagePointer);
					currentDataPageIndex++;
				}
				// We need all this data page data + addtitional data pages data
				else if((dataLength - dataOffset + offsetInStartDataPage) > currentDataPage.StoredDataLength){
					currentDataPage.ReadData(columnData,dataOffset,m_pOwnerDb.DataPageDataAreaSize - offsetInStartDataPage,offsetInStartDataPage);
					dataOffset += m_pOwnerDb.DataPageDataAreaSize - offsetInStartDataPage;

					// Get next continuing data page
					currentDataPage = new DataPage(m_pOwnerDb.DataPageDataAreaSize,m_pOwnerDb,currentDataPage.NextDataPagePointer);
					currentDataPageIndex++;

					offsetInStartDataPage = 0;
				}
				// This data page has all data we need
				else{
					currentDataPage.ReadData(columnData,dataOffset,dataLength - (int)dataOffset,offsetInStartDataPage);
					dataOffset += dataLength - (int)dataOffset;

					offsetInStartDataPage = 0;
				}				
			}
		
			// Convert to column data type
			return ConvertFromInternalData(m_pOwnerDb.Columns[columnIndex],columnData);
		}

		#endregion

		#region method UpdateRecord

		/// <summary>
		/// Updates this record values.
		/// </summary>
		/// <param name="rowValues">Row new values.</param>
		private void UpdateRecord(object[] rowValues)
		{
			bool unlock = true;
			// Table is already locked, don't lock it
			if(m_pOwnerDb.TableLocked){
				unlock = false;
			}
			else{
				m_pOwnerDb.LockTable(15);
			}

			// Create new record
			byte[] rowData = LDB_Record.CreateRecord(m_pOwnerDb,rowValues);

			DataPage[] dataPages = this.DataPages;
			// Clear old data ?? do we need that			
		//	for(int i=0;i<dataPages.Length;i++){
		//		dataPages[i].Data = new byte[1000];
		//	}

			// We haven't enough room to store row, get needed data pages
			if((int)Math.Ceiling(rowData.Length / (double)m_pOwnerDb.DataPageDataAreaSize) > dataPages.Length){
				int dataPagesNeeded = (int)Math.Ceiling(rowData.Length / (double)m_pOwnerDb.DataPageDataAreaSize) - dataPages.Length;
				DataPage[] additionalDataPages = m_pOwnerDb.GetDataPages(dataPages[dataPages.Length - 1].Pointer,dataPagesNeeded);

				// Append new data pages to existing data pages chain
				dataPages[dataPages.Length - 1].NextDataPagePointer = additionalDataPages[0].Pointer;

				DataPage[] newVal = new DataPage[dataPages.Length + additionalDataPages.Length];
				Array.Copy(dataPages,0,newVal,0,dataPages.Length);
				Array.Copy(additionalDataPages,0,newVal,dataPages.Length,additionalDataPages.Length);
				dataPages = newVal;
			}

			// Store new record
			DbFile.StoreDataToDataPages(m_pOwnerDb.DataPageDataAreaSize,rowData,dataPages);

			// Update row info
			ParseRowInfo();

			if(unlock){
				m_pOwnerDb.UnlockTable();
			}
		}

		#endregion


		#region static method ConvertToInternalData

		/// <summary>
		/// Converts data to specied column internal store data.
		/// </summary>
		/// <param name="coulmn">Column where to store data.</param>
		/// <param name="val">Data to convert.</param>
		/// <returns></returns>
		internal static byte[] ConvertToInternalData(LDB_DataColumn coulmn,object val)
		{
			if(val == null){
				throw new Exception("Null values aren't supported !");
			}

			if(coulmn.DataType == LDB_DataType.Bool){
				if(val.GetType() != typeof(bool)){
					throw new Exception("Column '" + coulmn.ColumnName + "' requires datatype of bool, but value contains '" +val.GetType().ToString() + "' !");
				}

				return new byte[]{Convert.ToByte((bool)val)};
			}
			else if(coulmn.DataType == LDB_DataType.DateTime){
				if(val.GetType() != typeof(DateTime)){
					throw new Exception("Column '" + coulmn.ColumnName + "' requires datatype of DateTime, but value contains '" + val.GetType().ToString() + "' !");
				}

				/* Data structure
					1 byte day
					1 byte month
					4 byte year (int)
					1 byte hour
					1 byte minute
					1 byte second
				*/

				DateTime d = (DateTime)val;
				byte[] dateBytes = new byte[13];
				// day
				dateBytes[0] = (byte)d.Day;
				// month
				dateBytes[1] = (byte)d.Month;
				// year
				Array.Copy(ldb_Utils.IntToByte(d.Year),0,dateBytes,2,4);
				// hour
				dateBytes[6] = (byte)d.Hour;
				// minute
				dateBytes[7] = (byte)d.Minute;
				// second
				dateBytes[8] = (byte)d.Second;

				return dateBytes;
			}				
			else if(coulmn.DataType == LDB_DataType.Long){
				if(val.GetType() != typeof(long)){
					throw new Exception("Column '" + coulmn.ColumnName + "' requires datatype of Long, but value contains '" + val.GetType().ToString() + "' !");
				}

				return ldb_Utils.LongToByte((long)val);
			}								
			else if(coulmn.DataType == LDB_DataType.Int){
				if(val.GetType() != typeof(int)){
					throw new Exception("Column '" + coulmn.ColumnName + "' requires datatype of Int, but value contains '" + val.GetType().ToString() + "' !");
				}

				return ldb_Utils.IntToByte((int)val);
			}
			else if(coulmn.DataType == LDB_DataType.String){
				if(val.GetType() != typeof(string)){
					throw new Exception("Column '" + coulmn.ColumnName + "' requires datatype of String, but value contains '" + val.GetType().ToString() + "' !");
				}

				return System.Text.Encoding.UTF8.GetBytes(val.ToString());
			}
			else{
				throw new Exception("Invalid column data type, never must reach here !");
			}
		}

		#endregion

		#region static method ConvertFromInternalData

		/// <summary>
		/// Converts internal data to .NET data type.
		/// </summary>
		/// <param name="coulmn">Column what data it is.</param>
		/// <param name="val">Internal data value.</param>
		/// <returns></returns>
		internal static object ConvertFromInternalData(LDB_DataColumn coulmn,byte[] val)
		{
			if(coulmn.DataType == LDB_DataType.Bool){
				return Convert.ToBoolean(val[0]);
			}
			else if(coulmn.DataType == LDB_DataType.DateTime){
				/* Data structure
					1 byte day
					1 byte month
					4 byte year (int)
					1 byte hour
					1 byte minute
					1 byte second
				*/
				
				byte[] dateBytes = new byte[13];
				// day
				int day = val[0];
				// month
				int month = val[1];
				// year
				int year = ldb_Utils.ByteToInt(val,2);
				// hour
				int hour = val[6];
				// minute
				int minute = val[7];
				// second
				int second = val[8];

				return new DateTime(year,month,day,hour,minute,second);
			}				
			else if(coulmn.DataType == LDB_DataType.Long){
				return ldb_Utils.ByteToLong(val,0);
			}								
			else if(coulmn.DataType == LDB_DataType.Int){
				return ldb_Utils.ByteToInt(val,0);
			}
			else if(coulmn.DataType == LDB_DataType.String){
				return System.Text.Encoding.UTF8.GetString(val);
			}
			else{
				throw new Exception("Invalid column data type, never must reach here !");
			}
		}

		#endregion


		#region Properties Implementation
	
		/// <summary>
		/// Gets or set all data column values.
		/// </summary>
		public object[] Values
		{
			get{
				object[] retVal = new object[m_pOwnerDb.Columns.Count];
				for(int i=0;i<m_pOwnerDb.Columns.Count;i++){
					retVal[i] = this[i];
				}

				return retVal;
			}

			set{ 
				UpdateRecord(value); 
			}
		}

		/// <summary>
		/// Gets or sets specified data column value.
		/// </summary>
		public object this[int columnIndex]
		{
			get{
				if(columnIndex < 0){
					throw new Exception("The columnIndex can't be negative value !");
				}
				if(columnIndex > m_pOwnerDb.Columns.Count){
					throw new Exception("The columnIndex out of columns count !");
				}

				return GetColumnData(columnIndex); 
			}

			set{
				if(columnIndex < 0){
					throw new Exception("The columnIndex can't be negative value !");
				}
				if(columnIndex > m_pOwnerDb.Columns.Count){
					throw new Exception("The columnIndex out of columns count !");
				}

				// Get current row values
                object[] rowValues = this.Values;
				// Update specified column value
				rowValues[columnIndex] = value;
				// Update record
				this.UpdateRecord(rowValues);
			}
		}

		/// <summary>
		/// Gets or sets specified data column value.
		/// </summary>
		public object this[string columnName]
		{
			get{
				int index = m_pOwnerDb.Columns.IndexOf(columnName);
				if(index == -1){
					throw new Exception("Table doesn't contain column '" + columnName + "' !");
				}

				return this[index]; 
			}

			set{
				int index = m_pOwnerDb.Columns.IndexOf(columnName);
				if(index == -1){
					throw new Exception("Table doesn't contain column '" + columnName + "' !");
				}

				this[index] = value; 
			}
		}

		/// <summary>
		/// Gets or sets specified data column value.
		/// </summary>
		public object this[LDB_DataColumn column]
		{
			get{ 
				int index = m_pOwnerDb.Columns.IndexOf(column);
				if(index == -1){
					throw new Exception("Table doesn't contain column '" + column.ColumnName + "' !");
				}

				return this[index]; 
			}

			set{
				int index = m_pOwnerDb.Columns.IndexOf(column);
				if(index == -1){
					throw new Exception("Table doesn't contain column '" + column.ColumnName + "' !");
				}

				this[index] = value; 
			}
		}


		/// <summary>
		/// Gets data page on what row starts.
		/// </summary>
		internal DataPage DataPage
		{
			get{ return m_pDataPage; }
		}

		/// <summary>
		/// Gets data pages held by this row.
		/// </summary>
		internal DataPage[] DataPages
		{
			get{ 
				ArrayList dataPages = new ArrayList();
				DataPage page = m_pDataPage;
				dataPages.Add(page);
				while(page.NextDataPagePointer > 0){
					page =  new DataPage(m_pOwnerDb.DataPageDataAreaSize,m_pOwnerDb,page.NextDataPagePointer);
					dataPages.Add(page);
				}

				DataPage[] retVal = new DataPage[dataPages.Count];
				dataPages.CopyTo(retVal);

                return retVal;
			}
		}

		#endregion

	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Estonia Estonia
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions